web_dorsal : rôle webapps dynamiques (2e brique plateforme, 100% natif)
Runtime (venv/paquets) + service systemd + nginx local, derrière l'edge, ZÉRO conteneur. Chaque app : git clone (depth 1) → venv + pip → unité systemd durcie (app en localhost, user webapp, NoNewPrivileges/PrivateTmp) → vhost nginx local qui la fronte. data_dir persistant pour l'état local (SQLite) ; BD réseau via resoudre_base sinon. Éprouvé bout-en-bout sur web-frontal-01 par le rôle : monregistraire (FastAPI+ uvicorn+SQLite) cloné de Forgejo, servi via l'edge en HTTPS step-ca, API OK. Avec web_frontal, la plateforme webapp native est complète. lint production: 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
72b7579c6e
commit
eee7dbd108
9 changed files with 271 additions and 0 deletions
15
playbooks/groupes/web_dorsal.yml
Normal file
15
playbooks/groupes/web_dorsal.yml
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
---
|
||||
- name: Appliquer le groupe web_dorsal
|
||||
hosts: web_dorsal
|
||||
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_dorsal
|
||||
44
roles/web_dorsal/README.md
Normal file
44
roles/web_dorsal/README.md
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
# 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
|
||||
+ systemd + nginx, **zéro conteneur**. Codifié depuis le spike du 2026-07-05
|
||||
(monregistraire : FastAPI + uvicorn + SQLite).
|
||||
|
||||
## Patron
|
||||
Pour chaque app déclarée dans `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 :
|
||||
NoNewPrivileges, PrivateTmp) ;
|
||||
4. **vhost nginx local** qui fronte l'app (`server_name` → `127.0.0.1:<port>`).
|
||||
|
||||
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)
|
||||
```yaml
|
||||
web_dorsal_apps:
|
||||
- nom: registraire
|
||||
server_name: "registraire.{{ domaine_interne }}"
|
||||
depot: "https://forge.{{ domaine_interne }}/…/monregistraire.git"
|
||||
branche: main
|
||||
port: 8001
|
||||
commande: ".venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8001"
|
||||
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>]`.
|
||||
|
||||
## BD
|
||||
Les apps à état **local** (SQLite) : `data_dir` persistant. Les apps à **BD réseau**
|
||||
(PostgreSQL) : déclarer la base dans `bases-donnees.yml` et passer le DSN via `env`
|
||||
(resoudre_base) — même motif que les autres services Set-OPS.
|
||||
|
||||
## Runtimes
|
||||
Éprouvé sur **Python (FastAPI/uvicorn)**. Node/autres : adapter `commande` + les
|
||||
paquets de runtime (le patron systemd + nginx + git reste identique).
|
||||
|
||||
## Validation
|
||||
`ansible-lint` profil production + `--syntax-check`. Éprouvé sur web-frontal-01
|
||||
(monregistraire servi via l'edge, HTTPS step-ca, API fonctionnelle).
|
||||
23
roles/web_dorsal/defaults/main.yml
Normal file
23
roles/web_dorsal/defaults/main.yml
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
---
|
||||
# =========================================================================
|
||||
# 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"
|
||||
|
||||
# Liste des webapps servies par ce noeud. Chaque entree :
|
||||
# nom identifiant court (dossier + service + vhost)
|
||||
# server_name FQDN servi (doit etre expose via applications.yml -> edge, port 80)
|
||||
# depot URL du depot git de l'app (souverain, ex. Forgejo)
|
||||
# branche branche a deployer (defaut: main)
|
||||
# port port local ecoute par l'app (127.0.0.1:<port>)
|
||||
# commande ExecStart, relatif au dossier de l'app (ex: .venv/bin/uvicorn app.main:app --host 127.0.0.1 --port 8001)
|
||||
# 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: []
|
||||
16
roles/web_dorsal/handlers/main.yml
Normal file
16
roles/web_dorsal/handlers/main.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
- name: Recharger nginx web_dorsal
|
||||
ansible.builtin.systemd:
|
||||
name: nginx
|
||||
state: reloaded
|
||||
when: not ansible_check_mode
|
||||
|
||||
- name: Redemarrer les webapps
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item.nom }}"
|
||||
state: restarted
|
||||
daemon_reload: true
|
||||
loop: "{{ web_dorsal_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.nom }}"
|
||||
when: not ansible_check_mode
|
||||
6
roles/web_dorsal/meta/empreinte.yml
Normal file
6
roles/web_dorsal/meta/empreinte.yml
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
# Empreinte de dimensionnement (propriete du role). Webapp dynamique (runtime
|
||||
# Python/Node + service) : memoire moderee. Ajuster selon les apps hebergees.
|
||||
coeurs: 1
|
||||
memoire_mo: 1024
|
||||
disque_go: 10
|
||||
16
roles/web_dorsal/meta/flux.yml
Normal file
16
roles/web_dorsal/meta/flux.yml
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
---
|
||||
# Flux reseau du dorsal web (propriete du role). Voir docs/flux-conception.md.
|
||||
# L'app ecoute en LOCALHOST (pas de flux inter-noeud) ; le nginx local la fronte.
|
||||
flux:
|
||||
- sens: ingress
|
||||
port: 80
|
||||
protocole: tcp
|
||||
pair: edge
|
||||
chiffrement: clair
|
||||
raison: "Front nginx local des webapps, proxie par l'edge (TLS termine a l'edge)."
|
||||
- sens: egress
|
||||
port: 443
|
||||
protocole: tcp
|
||||
pair: flotte
|
||||
chiffrement: tls
|
||||
raison: "git clone/pull du depot de chaque app (Forgejo souverain) au deploiement."
|
||||
116
roles/web_dorsal/tasks/main.yml
Normal file
116
roles/web_dorsal/tasks/main.yml
Normal file
|
|
@ -0,0 +1,116 @@
|
|||
---
|
||||
- name: Paquets (python venv/pip, git, nginx)
|
||||
ansible.builtin.apt:
|
||||
name:
|
||||
- python3-venv
|
||||
- python3-pip
|
||||
- git
|
||||
- nginx
|
||||
state: present
|
||||
update_cache: true
|
||||
cache_valid_time: 3600
|
||||
|
||||
- name: Utilisateur de service des webapps
|
||||
ansible.builtin.user:
|
||||
name: "{{ web_dorsal_utilisateur }}"
|
||||
system: true
|
||||
home: "{{ web_dorsal_racine }}"
|
||||
shell: /usr/sbin/nologin
|
||||
create_home: false
|
||||
|
||||
- name: Creer la racine des webapps
|
||||
ansible.builtin.file:
|
||||
path: "{{ web_dorsal_racine }}"
|
||||
state: directory
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0755"
|
||||
|
||||
- name: Cloner / mettre a jour chaque app (clone superficiel)
|
||||
ansible.builtin.git:
|
||||
repo: "{{ item.depot }}"
|
||||
dest: "{{ web_dorsal_racine }}/{{ item.nom }}"
|
||||
version: "{{ item.branche | default('main') }}"
|
||||
depth: 1
|
||||
force: true
|
||||
loop: "{{ 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"
|
||||
virtualenv_command: python3 -m venv
|
||||
loop: "{{ web_dorsal_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.nom }}"
|
||||
notify: Redemarrer les webapps
|
||||
|
||||
- name: Repertoire d'etat persistant (apps avec data_dir)
|
||||
ansible.builtin.file:
|
||||
path: "{{ item.data_dir }}"
|
||||
state: directory
|
||||
owner: "{{ web_dorsal_utilisateur }}"
|
||||
group: "{{ web_dorsal_utilisateur }}"
|
||||
mode: "0750"
|
||||
loop: "{{ 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 }}"
|
||||
|
||||
- name: Deployer l'unite systemd de chaque app
|
||||
ansible.builtin.template:
|
||||
src: app.service.j2
|
||||
dest: "/etc/systemd/system/{{ item.nom }}.service"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ web_dorsal_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.nom }}"
|
||||
notify: Redemarrer les webapps
|
||||
|
||||
- name: Deployer le vhost proxy local de chaque app
|
||||
ansible.builtin.template:
|
||||
src: proxy.conf.j2
|
||||
dest: "/etc/nginx/sites-available/{{ item.nom }}"
|
||||
owner: root
|
||||
group: root
|
||||
mode: "0644"
|
||||
loop: "{{ web_dorsal_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.nom }}"
|
||||
notify: Recharger nginx 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_control:
|
||||
label: "{{ item.nom }}"
|
||||
notify: Recharger nginx web_dorsal
|
||||
|
||||
- name: Activer et demarrer chaque webapp
|
||||
ansible.builtin.systemd:
|
||||
name: "{{ item.nom }}"
|
||||
enabled: true
|
||||
state: started
|
||||
daemon_reload: true
|
||||
loop: "{{ web_dorsal_apps }}"
|
||||
loop_control:
|
||||
label: "{{ item.nom }}"
|
||||
when: not ansible_check_mode
|
||||
21
roles/web_dorsal/templates/app.service.j2
Normal file
21
roles/web_dorsal/templates/app.service.j2
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
# 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
|
||||
14
roles/web_dorsal/templates/proxy.conf.j2
Normal file
14
roles/web_dorsal/templates/proxy.conf.j2
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
# Front nginx local de la webapp {{ item.nom }} — genere par Set-OPS (web_dorsal).
|
||||
# L'app ecoute en localhost ; l'edge proxie server_name -> ce vhost -> l'app.
|
||||
server {
|
||||
listen 80;
|
||||
server_name {{ item.server_name }};
|
||||
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:{{ item.port }};
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue