web_frontal : rôle sites statiques (1re brique plateforme webapp, 100% natif)

nginx + sites statiques depuis un dépôt git souverain, derrière l'edge. Pas de
conteneur, pas de runtime, pas de BD — l'éthos libre/natif. Chaque site :
git clone → vhost (headers sécurité + CSP self-only, gzip, try_files). SAN edge
+ plancher auto-dérivés. meta/flux.yml (ingress 80 depuis edge).

Spike prouvé sur web-frontal-01 (site Alliance Boréale servi via edge, HTTPS
step-ca vérifié, CSP en place). NB : le chemin git-clone est codifié mais le
spike a servi via copie locale → à éprouver (pousser le site en Forgejo).
Les apps dynamiques (dorsal systemd) = rôle à venir. ansible-lint production: 0.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-05 18:12:19 -04:00
parent 1f9cc0a0d3
commit f453f77268
8 changed files with 186 additions and 0 deletions

View file

@ -0,0 +1,15 @@
---
- name: Appliquer le groupe web_frontal
hosts: web_frontal
become: true
gather_facts: true
pre_tasks:
- name: Vérifier que la cible est Debian
ansible.builtin.assert:
that:
- ansible_facts.distribution == "Debian"
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- web_frontal

View file

@ -0,0 +1,37 @@
# web_frontal
Hébergement de **sites web statiques** (nginx), derrière l'edge. Première brique de
la **plateforme webapp** de Set-OPS : **100 % natif** — pas de conteneur, pas de
runtime, pas de base de données. Le contenu vient d'un **dépôt git souverain**
(Forgejo). Codifié depuis le spike du 2026-07-05 (site Alliance Boréale).
## Ce que le rôle fait
Pour chaque site déclaré dans `web_frontal_sites` :
1. `git clone`/`pull` du dépôt → `/srv/web/<nom>` ;
2. vhost nginx servant `<sous_dossier>` (ex. `public/`), avec en-têtes de sécurité
+ CSP (défaut : `self` only, aucun tiers, aucun pistage), gzip, cache, `try_files`.
Exposé via `applications.yml` (champ `expose`) → l'edge proxie en HTTPS (TLS step-ca).
Les SAN du cert edge et le plancher /etc/hosts sont **auto-dérivés du plan**.
## Déclaration (group_vars du groupe web_frontal)
```yaml
web_frontal_sites:
- nom: alliance
server_name: "alliance-boreale.{{ domaine_interne }}"
depot: "https://forge.{{ domaine_interne }}/alliance/site-alliance-boreale.git"
branche: main
sous_dossier: public
# csp: "..." # optionnel, sinon web_frontal_csp_defaut
```
Et dans `applications.yml` : une app `groupe: web_frontal, hote: <noeud>, port: 80,
expose: [alliance-boreale.<domaine>]`.
## Portée
Statique seulement (le **frontal**). Les apps **dynamiques** (runtime + service
systemd + BD via resoudre_base) relèveront d'un rôle *dorsal* à venir — même éthos :
natif, systemd, zéro conteneur.
## Validation
`ansible-lint` profil production + `--syntax-check`. Éprouvé sur web-frontal-01
(site servi via l'edge, HTTPS step-ca vérifié, CSP en place).

View file

@ -0,0 +1,23 @@
---
# =========================================================================
# web_frontal — hebergement de sites web STATIQUES (nginx), derriere l'edge.
# Premiere brique de la « plateforme webapp » : 100% natif (pas de conteneur,
# pas de runtime, pas de BD). Le contenu vient d'un depot git souverain.
# Les apps DYNAMIQUES (service systemd + BD) viendront via un r_le dorsal.
# Codifie depuis le spike du 2026-07-05 (site Alliance Boreale).
# =========================================================================
web_frontal_racine_base: "/srv/web"
# En-tetes de securite par defaut (sobriete + vie privee : tout local, pas de tiers).
web_frontal_csp_defaut: >-
default-src 'self'; img-src 'self' data:; style-src 'self'; script-src 'self';
object-src 'none'; base-uri 'self'; frame-ancestors 'self'
# Liste des sites statiques servis par ce noeud. Chaque entree :
# nom identifiant court (dossier + vhost)
# server_name FQDN servi (doit etre expose via applications.yml -> edge)
# depot URL du depot git du site (souverain, ex. Forgejo)
# branche branche a servir (defaut: main)
# sous_dossier sous-dossier du depot a servir comme racine (ex: public)
# csp Content-Security-Policy specifique (optionnel)
web_frontal_sites: []

View file

@ -0,0 +1,6 @@
---
- name: Recharger nginx web_frontal
ansible.builtin.systemd:
name: nginx
state: reloaded
when: not ansible_check_mode

View file

@ -0,0 +1,6 @@
---
# Empreinte de dimensionnement (propriete du role). Site statique servi par
# nginx : tres leger (pas de runtime, pas de BD).
coeurs: 1
memoire_mo: 512
disque_go: 10

View file

@ -0,0 +1,15 @@
---
# Flux reseau du frontal web (propriete du role). Voir docs/flux-conception.md.
flux:
- sens: ingress
port: 80
protocole: tcp
pair: edge
chiffrement: clair
raison: "Contenu statique servi au navigateur via l'edge (TLS termine a l'edge)."
- sens: egress
port: 443
protocole: tcp
pair: flotte
chiffrement: tls
raison: "git clone/pull du depot du site (Forgejo souverain) au deploiement."

View file

@ -0,0 +1,53 @@
---
- name: Installer nginx
ansible.builtin.apt:
name: nginx
state: present
update_cache: true
cache_valid_time: 3600
- name: Retirer le vhost nginx par defaut
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Recharger nginx web_frontal
- name: Creer la racine des sites
ansible.builtin.file:
path: "{{ web_frontal_racine_base }}"
state: directory
owner: root
group: root
mode: "0755"
- name: Cloner / mettre a jour le depot de chaque site
ansible.builtin.git:
repo: "{{ item.depot }}"
dest: "{{ web_frontal_racine_base }}/{{ item.nom }}"
version: "{{ item.branche | default('main') }}"
force: true
loop: "{{ web_frontal_sites }}"
loop_control:
label: "{{ item.nom }}"
- name: Deployer le vhost de chaque site
ansible.builtin.template:
src: site.conf.j2
dest: "/etc/nginx/sites-available/{{ item.nom }}"
owner: root
group: root
mode: "0644"
loop: "{{ web_frontal_sites }}"
loop_control:
label: "{{ item.nom }}"
notify: Recharger nginx web_frontal
- name: Activer le vhost de chaque site
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ item.nom }}"
dest: "/etc/nginx/sites-enabled/{{ item.nom }}"
state: link
loop: "{{ web_frontal_sites }}"
loop_control:
label: "{{ item.nom }}"
notify: Recharger nginx web_frontal

View file

@ -0,0 +1,31 @@
# Site statique {{ item.nom }} — genere par Set-OPS (role web_frontal). Ne pas editer.
# Sert derriere l'edge (TLS termine a l'edge). Racine = depot git / sous-dossier.
server {
listen 80;
server_name {{ item.server_name }};
root {{ web_frontal_racine_base }}/{{ item.nom }}{{ ('/' ~ item.sous_dossier) if (item.sous_dossier | default('')) else '' }};
index index.html;
add_header X-Content-Type-Options "nosniff" always;
add_header X-Frame-Options "SAMEORIGIN" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Content-Security-Policy "{{ (item.csp | default(web_frontal_csp_defaut)) | trim }}" always;
gzip on;
gzip_types text/css application/javascript text/html image/svg+xml;
gzip_min_length 1024;
location /assets/ {
expires 7d;
add_header Cache-Control "public";
}
location / {
try_files $uri $uri/ $uri.html =404;
}
error_page 404 /404.html;
access_log /var/log/nginx/{{ item.nom }}.access.log;
error_log /var/log/nginx/{{ item.nom }}.error.log;
}