Rôles web : renommer web_frontal/dorsal -> serveur_web_frontal/dorsal (convention)

Aligne sur la convention serveur_* de tous les rôles de service (vars incluses).
Le modèle presence-web (pré-existant) utilisait déjà ces noms -> il colle enfin.
Aussi : serveur_web_dorsal ne chown plus le code (reste root, lecture seule pour
l'app = plus sûr + git idempotent, fini le 'propriété douteuse' au pull).
Idempotence prouvée (2e passage changed=0). lint production.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Daniel Allaire 2026-07-05 18:49:31 -04:00
parent c8ccbb5e97
commit af13fc37c9
18 changed files with 80 additions and 88 deletions

View file

@ -1,6 +1,6 @@
---
- name: Appliquer le groupe web_dorsal
hosts: web_dorsal
- name: Appliquer le groupe serveur_web_dorsal
hosts: serveur_web_dorsal
become: true
gather_facts: true
@ -12,4 +12,4 @@
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- web_dorsal
- serveur_web_dorsal

View file

@ -1,6 +1,6 @@
---
- name: Appliquer le groupe web_frontal
hosts: web_frontal
- name: Appliquer le groupe serveur_web_frontal
hosts: serveur_web_frontal
become: true
gather_facts: true
@ -12,4 +12,4 @@
fail_msg: "Ce playbook est prévu pour Debian."
roles:
- web_frontal
- serveur_web_frontal

View file

@ -1,4 +1,4 @@
# web_dorsal
# serveur_web_dorsal
Hébergement de **webapps dynamiques** (runtime + service systemd), derrière l'edge.
Deuxième brique de la **plateforme webapp** de Set-OPS : **100 % natif** — venv/paquets
@ -6,7 +6,7 @@ Deuxième brique de la **plateforme webapp** de Set-OPS : **100 % natif** — ve
(monregistraire : FastAPI + uvicorn + SQLite).
## Patron
Pour chaque app déclarée dans `web_dorsal_apps` :
Pour chaque app déclarée dans `serveur_web_dorsal_apps` :
1. `git clone` (depth 1) → `/srv/webapp/<nom>` ;
2. venv Python + `pip install -r requirements.txt` ;
3. **unité systemd** (l'app écoute en `127.0.0.1:<port>`, user `webapp`, durcie :
@ -16,9 +16,9 @@ Pour chaque app déclarée dans `web_dorsal_apps` :
L'app reste **en localhost** (sûr) ; l'edge proxie `server_name` → nginx local → app.
Exposé via `applications.yml` (port **80**, pas le port interne de l'app).
## Déclaration (group_vars du groupe web_dorsal)
## Déclaration (group_vars du groupe serveur_web_dorsal)
```yaml
web_dorsal_apps:
serveur_web_dorsal_apps:
- nom: registraire
server_name: "registraire.{{ domaine_interne }}"
depot: "https://forge.{{ domaine_interne }}/…/monregistraire.git"
@ -28,7 +28,7 @@ web_dorsal_apps:
env: { IMMOS_DB_PATH: /var/lib/registraire/immos.db }
data_dir: /var/lib/registraire # état local (SQLite), optionnel
```
Et une app `groupe: web_dorsal, hote: <noeud>, port: 80, expose: [registraire.<domaine>]`.
Et une app `groupe: serveur_web_dorsal, hote: <noeud>, port: 80, expose: [registraire.<domaine>]`.
## BD
Les apps à état **local** (SQLite) : `data_dir` persistant. Les apps à **BD réseau**

View file

@ -1,14 +1,14 @@
---
# =========================================================================
# web_dorsal — webapps DYNAMIQUES (runtime + service systemd), derriere l'edge.
# serveur_web_dorsal — webapps DYNAMIQUES (runtime + service systemd), derriere l'edge.
# 2e brique de la plateforme webapp : 100% natif (venv/paquets + systemd + nginx),
# ZERO conteneur. L'app ecoute en localhost ; un vhost nginx local la fronte ;
# l'edge proxie vers ce nginx. BD reseau (PG) via resoudre_base si l'app en a une ;
# les apps a etat local (SQLite) utilisent un data_dir persistant.
# Codifie depuis le spike du 2026-07-05 (monregistraire, FastAPI + uvicorn + SQLite).
# =========================================================================
web_dorsal_racine: "/srv/webapp"
web_dorsal_utilisateur: "webapp"
serveur_web_dorsal_racine: "/srv/webapp"
serveur_web_dorsal_utilisateur: "webapp"
# Liste des webapps servies par ce noeud. Chaque entree :
# nom identifiant court (dossier + service + vhost)
@ -20,4 +20,4 @@ web_dorsal_utilisateur: "webapp"
# requirements fichier pip (defaut: requirements.txt) ; venv cree automatiquement
# env dict de variables d'environnement (secrets via voute)
# data_dir repertoire d'etat persistant (optionnel ; cree, possede par webapp)
web_dorsal_apps: []
serveur_web_dorsal_apps: []

View file

@ -1,5 +1,5 @@
---
- name: Recharger nginx web_dorsal
- name: Recharger nginx serveur_web_dorsal
ansible.builtin.systemd:
name: nginx
state: reloaded
@ -10,7 +10,7 @@
name: "{{ item.nom }}"
state: restarted
daemon_reload: true
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
when: not ansible_check_mode

View file

@ -12,15 +12,15 @@
- name: Utilisateur de service des webapps
ansible.builtin.user:
name: "{{ web_dorsal_utilisateur }}"
name: "{{ serveur_web_dorsal_utilisateur }}"
system: true
home: "{{ web_dorsal_racine }}"
home: "{{ serveur_web_dorsal_racine }}"
shell: /usr/sbin/nologin
create_home: false
- name: Creer la racine des webapps
ansible.builtin.file:
path: "{{ web_dorsal_racine }}"
path: "{{ serveur_web_dorsal_racine }}"
state: directory
owner: root
group: root
@ -29,21 +29,21 @@
- name: Cloner / mettre a jour chaque app (clone superficiel)
ansible.builtin.git:
repo: "{{ item.depot }}"
dest: "{{ web_dorsal_racine }}/{{ item.nom }}"
dest: "{{ serveur_web_dorsal_racine }}/{{ item.nom }}"
version: "{{ item.branche | default('main') }}"
depth: 1
force: true
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
notify: Redemarrer les webapps
- name: Creer le venv + installer les dependances de chaque app
ansible.builtin.pip:
requirements: "{{ web_dorsal_racine }}/{{ item.nom }}/{{ item.requirements | default('requirements.txt') }}"
virtualenv: "{{ web_dorsal_racine }}/{{ item.nom }}/.venv"
requirements: "{{ serveur_web_dorsal_racine }}/{{ item.nom }}/{{ item.requirements | default('requirements.txt') }}"
virtualenv: "{{ serveur_web_dorsal_racine }}/{{ item.nom }}/.venv"
virtualenv_command: python3 -m venv
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
notify: Redemarrer les webapps
@ -52,24 +52,16 @@
ansible.builtin.file:
path: "{{ item.data_dir }}"
state: directory
owner: "{{ web_dorsal_utilisateur }}"
group: "{{ web_dorsal_utilisateur }}"
owner: "{{ serveur_web_dorsal_utilisateur }}"
group: "{{ serveur_web_dorsal_utilisateur }}"
mode: "0750"
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
when: item.data_dir is defined
- name: Donner le code au user de service
ansible.builtin.file:
path: "{{ web_dorsal_racine }}/{{ item.nom }}"
owner: "{{ web_dorsal_utilisateur }}"
group: "{{ web_dorsal_utilisateur }}"
recurse: true
loop: "{{ web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
# Le code reste possede par root (lecture seule pour l'app = plus sur, et git
# reste idempotent : pas de "propriete douteuse" au pull). L'etat va dans data_dir.
- name: Deployer l'unite systemd de chaque app
ansible.builtin.template:
src: app.service.j2
@ -77,7 +69,7 @@
owner: root
group: root
mode: "0644"
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
notify: Redemarrer les webapps
@ -89,20 +81,20 @@
owner: root
group: root
mode: "0644"
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
notify: Recharger nginx web_dorsal
notify: Recharger nginx serveur_web_dorsal
- name: Activer le vhost proxy de chaque app
ansible.builtin.file:
src: "/etc/nginx/sites-available/{{ item.nom }}"
dest: "/etc/nginx/sites-enabled/{{ item.nom }}"
state: link
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
notify: Recharger nginx web_dorsal
notify: Recharger nginx serveur_web_dorsal
- name: Activer et demarrer chaque webapp
ansible.builtin.systemd:
@ -110,7 +102,7 @@
enabled: true
state: started
daemon_reload: true
loop: "{{ web_dorsal_apps }}"
loop: "{{ serveur_web_dorsal_apps }}"
loop_control:
label: "{{ item.nom }}"
when: not ansible_check_mode

View file

@ -0,0 +1,21 @@
# Webapp {{ item.nom }} — unite generee par Set-OPS (role serveur_web_dorsal). Ne pas editer.
[Unit]
Description=Webapp {{ item.nom }} ({{ item.server_name }})
After=network.target
[Service]
Type=simple
User={{ serveur_web_dorsal_utilisateur }}
Group={{ serveur_web_dorsal_utilisateur }}
WorkingDirectory={{ serveur_web_dorsal_racine }}/{{ item.nom }}
{% for k, v in (item.env | default({}) | dictsort) %}
Environment="{{ k }}={{ v }}"
{% endfor %}
ExecStart={{ serveur_web_dorsal_racine }}/{{ item.nom }}/{{ item.commande }}
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target

View file

@ -1,4 +1,4 @@
# Front nginx local de la webapp {{ item.nom }} — genere par Set-OPS (web_dorsal).
# Front nginx local de la webapp {{ item.nom }} — genere par Set-OPS (serveur_web_dorsal).
# L'app ecoute en localhost ; l'edge proxie server_name -> ce vhost -> l'app.
server {
listen 80;

View file

@ -1,4 +1,4 @@
# web_frontal
# serveur_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
@ -6,7 +6,7 @@ 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` :
Pour chaque site déclaré dans `serveur_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`.
@ -14,17 +14,17 @@ Pour chaque site déclaré dans `web_frontal_sites` :
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)
## Déclaration (group_vars du groupe serveur_web_frontal)
```yaml
web_frontal_sites:
serveur_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
# csp: "..." # optionnel, sinon serveur_web_frontal_csp_defaut
```
Et dans `applications.yml` : une app `groupe: web_frontal, hote: <noeud>, port: 80,
Et dans `applications.yml` : une app `groupe: serveur_web_frontal, hote: <noeud>, port: 80,
expose: [alliance-boreale.<domaine>]`.
## Portée

View file

@ -1,15 +1,15 @@
---
# =========================================================================
# web_frontal — hebergement de sites web STATIQUES (nginx), derriere l'edge.
# serveur_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"
serveur_web_frontal_racine_base: "/srv/web"
# En-tetes de securite par defaut (sobriete + vie privee : tout local, pas de tiers).
web_frontal_csp_defaut: >-
serveur_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'
@ -20,4 +20,4 @@ web_frontal_csp_defaut: >-
# 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: []
serveur_web_frontal_sites: []

View file

@ -1,5 +1,5 @@
---
- name: Recharger nginx web_frontal
- name: Recharger nginx serveur_web_frontal
ansible.builtin.systemd:
name: nginx
state: reloaded

View file

@ -12,11 +12,11 @@
ansible.builtin.file:
path: /etc/nginx/sites-enabled/default
state: absent
notify: Recharger nginx web_frontal
notify: Recharger nginx serveur_web_frontal
- name: Creer la racine des sites
ansible.builtin.file:
path: "{{ web_frontal_racine_base }}"
path: "{{ serveur_web_frontal_racine_base }}"
state: directory
owner: root
group: root
@ -25,11 +25,11 @@
- name: Cloner / mettre a jour le depot de chaque site (clone superficiel, contenu courant)
ansible.builtin.git:
repo: "{{ item.depot }}"
dest: "{{ web_frontal_racine_base }}/{{ item.nom }}"
dest: "{{ serveur_web_frontal_racine_base }}/{{ item.nom }}"
version: "{{ item.branche | default('main') }}"
depth: 1
force: true
loop: "{{ web_frontal_sites }}"
loop: "{{ serveur_web_frontal_sites }}"
loop_control:
label: "{{ item.nom }}"
@ -40,17 +40,17 @@
owner: root
group: root
mode: "0644"
loop: "{{ web_frontal_sites }}"
loop: "{{ serveur_web_frontal_sites }}"
loop_control:
label: "{{ item.nom }}"
notify: Recharger nginx web_frontal
notify: Recharger nginx serveur_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: "{{ serveur_web_frontal_sites }}"
loop_control:
label: "{{ item.nom }}"
notify: Recharger nginx web_frontal
notify: Recharger nginx serveur_web_frontal

View file

@ -1,15 +1,15 @@
# Site statique {{ item.nom }} — genere par Set-OPS (role web_frontal). Ne pas editer.
# Site statique {{ item.nom }} — genere par Set-OPS (role serveur_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 '' }};
root {{ serveur_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;
add_header Content-Security-Policy "{{ (item.csp | default(serveur_web_frontal_csp_defaut)) | trim }}" always;
gzip on;
gzip_types text/css application/javascript text/html image/svg+xml;

View file

@ -1,21 +0,0 @@
# Webapp {{ item.nom }} — unite generee par Set-OPS (role web_dorsal). Ne pas editer.
[Unit]
Description=Webapp {{ item.nom }} ({{ item.server_name }})
After=network.target
[Service]
Type=simple
User={{ web_dorsal_utilisateur }}
Group={{ web_dorsal_utilisateur }}
WorkingDirectory={{ web_dorsal_racine }}/{{ item.nom }}
{% for k, v in (item.env | default({}) | dictsort) %}
Environment="{{ k }}={{ v }}"
{% endfor %}
ExecStart={{ web_dorsal_racine }}/{{ item.nom }}/{{ item.commande }}
Restart=on-failure
RestartSec=3
NoNewPrivileges=true
PrivateTmp=true
[Install]
WantedBy=multi-user.target