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:
parent
1f9cc0a0d3
commit
f453f77268
8 changed files with 186 additions and 0 deletions
15
playbooks/groupes/web_frontal.yml
Normal file
15
playbooks/groupes/web_frontal.yml
Normal 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
|
||||
37
roles/web_frontal/README.md
Normal file
37
roles/web_frontal/README.md
Normal 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).
|
||||
23
roles/web_frontal/defaults/main.yml
Normal file
23
roles/web_frontal/defaults/main.yml
Normal 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: []
|
||||
6
roles/web_frontal/handlers/main.yml
Normal file
6
roles/web_frontal/handlers/main.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
- name: Recharger nginx web_frontal
|
||||
ansible.builtin.systemd:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
when: not ansible_check_mode
|
||||
6
roles/web_frontal/meta/empreinte.yml
Normal file
6
roles/web_frontal/meta/empreinte.yml
Normal 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
|
||||
15
roles/web_frontal/meta/flux.yml
Normal file
15
roles/web_frontal/meta/flux.yml
Normal 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."
|
||||
53
roles/web_frontal/tasks/main.yml
Normal file
53
roles/web_frontal/tasks/main.yml
Normal 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
|
||||
31
roles/web_frontal/templates/site.conf.j2
Normal file
31
roles/web_frontal/templates/site.conf.j2
Normal 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;
|
||||
}
|
||||
Loading…
Reference in a new issue