AFF-002 (« Set-OPS s'exploite sans aucune IA ») était ✅ par inspection : ses écarts bloquants soldés et le parcours relu, mais jamais exécutée par un opérateur qui n'est pas l'auteur. Aucune commande locale ne peut la prouver. Ajoute le protocole de l'épreuve humaine : départ à froid depuis le modèle public `socle`, sur la grappe Proxmox de l'opérateur. Règle du silence (N0 journalise / N1 déblocage après 30 min, consigné comme défaut / N2 arrêt sécurité), interdits (aucune IA, aucun dépôt privé, aucune lecture de docs/audit/ qui divulguerait les pièges connus), critères R1→R6 fixés d'avance, périmètre matériel, gabarit de rapport. Le registre peut perdre : la redescente d'AFF-002 en 🟡 ou ❌ selon le verdict est explicitement prévue. Le protocole ne nomme personne — il est réutilisable, et l'identité de l'opérateur vit dans son rapport, sous réserve de consentement. Validé : prouver.py --verifier → CONFORME 16/16. Aucun code touché. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1100 lines
88 KiB
Markdown
1100 lines
88 KiB
Markdown
# CHANGELOG — Set-OPS
|
||
|
||
## 2026-07-21
|
||
|
||
### Ajouté
|
||
- **Protocole d'épreuve de l'opérateur indépendant** (`docs/audit/protocole-operateur-independant.md`).
|
||
Met **AFF-002** (« Set-OPS s'exploite entièrement à la main, sans aucune IA ») à l'épreuve
|
||
d'un sysadmin qui n'est pas l'auteur, sur sa propre grappe Proxmox, à froid depuis le modèle
|
||
public `socle`. Définit : la **règle du silence** (l'observateur journalise, n'aide pas ; trois
|
||
niveaux N0/N1/N2), les **interdits** (aucune IA, aucun accès aux dépôts privés, aucune lecture
|
||
de `docs/audit/` qui divulguerait les pièges connus), les **critères de réussite R1→R6 fixés
|
||
d'avance**, le périmètre matériel et la sécurité, et le **gabarit de rapport**
|
||
(`operateur-independant-AAAA-MM-JJ.md`). Le registre peut **perdre** : le protocole prévoit
|
||
explicitement la redescente d'AFF-002 en 🟡 ou ❌ selon le verdict.
|
||
|
||
### Modifié
|
||
- **`docs/audit/affirmations.md`** — nouvelle limite consignée : **AFF-002 est ✅ *par
|
||
inspection*, pas par démonstration**. Ses écarts bloquants sont soldés, mais aucun opérateur
|
||
indépendant ne l'a exécutée ; ✅ y signifie « plus aucun défaut connu ». Renvoi vers le
|
||
protocole.
|
||
- **`docs/audit/README.md`** — « Les trois pièces » → « Les pièces » : ajout du protocole comme
|
||
quatrième pièce du dispositif (l'épreuve humaine, hors harnais automatisable).
|
||
|
||
Validé : `python3 scripts/prouver.py --verifier` → **CONFORME 16/16** (voûte exportée) ;
|
||
équilibre des blocs de code du nouveau document vérifié. Aucun code touché.
|
||
|
||
## 2026-07-20
|
||
|
||
### Modifié
|
||
- **`make verifier` inclut désormais les preuves (`make prouver`).** `make verifier` se
|
||
termine par `python3 scripts/prouver.py --verifier` : nouveau mode qui exécute toutes les
|
||
preuves du registre (verdict + code de sortie) **sans écrire de rapport**, pour ne pas
|
||
écraser la pièce justificative committée `docs/audit/preuve-<date>.md`. `make verifier`
|
||
échoue donc si une preuve échoue. `make prouver` seul (sans `--verifier`) continue d'écrire
|
||
le rapport horodaté. Vérifié : `make verifier` → CONFORME 16/16 (voûte exportée), aucun
|
||
churn du rapport committé ; `make prouver` écrit toujours. Doc mise à jour
|
||
(`docs/audit/README.md` § « Rapport avec `make verifier` »).
|
||
- **Conformité Phase 2 — 3 incohérences internes corrigées (documentation d'autorité).**
|
||
Aucun code Ansible touché ; le code SSH était déjà conforme à `AGENTS.md`.
|
||
- **`CLAUDE.md` réduit à un pointeur mince** : autorité unique d'`AGENTS.md` + les cinq
|
||
règles absolues (agent unique ; `hosts.yml` généré jamais édité ; aucun secret ;
|
||
confirmation des actions destructives ; rien n'est prêt sans validation). Toute la
|
||
doctrine dupliquée (template, SSH, pare-feu, cloud-init, handlers, Makefile…) retirée.
|
||
- **Contradiction SSH levée** : `CLAUDE.md` prescrivait `PasswordAuthentication yes`
|
||
pendant la construction ; le code applique en réalité `PasswordAuthentication no` +
|
||
`AuthenticationMethods publickey` dès le départ (prouvé, cf. `docs/audit/affirmations.md`
|
||
AFF-037/050). La doctrine SSH périmée de `CLAUDE.md` est supprimée — la duplication en
|
||
était la cause racine.
|
||
- **`AGENTS.md` : « Comportement attendu de Codex » → « …des agents IA »** (la section
|
||
vaut pour tout agent IA, Claude inclus).
|
||
- Suivi dans `docs/audit/affirmations.md` (§ Journal des traitements) : AFF-040, AFF-050,
|
||
AFF-052 résolus.
|
||
|
||
### Corrigé
|
||
- **Conformité Phase 3 — lot A « doc de démarrage » (le parcours QUICKSTART marche seul).**
|
||
- **Modèle absent (AFF-020/021).** `QUICKSTART.md` proposait `cp -r
|
||
exemples/modeles/presence-web …` — modèle **inexistant** dans le dépôt public (seul
|
||
`socle` l'est). Étape 2 réécrite sur `socle` ; les modèles assemblés renvoyés au dépôt
|
||
privé `Set-OPS-modeles`, cohérent avec `exemples/modeles/README.md`.
|
||
- **Chemin de voûte faux + piège de shadowing (AFF-023).** Le chemin `lab/` (QUICKSTART,
|
||
`exemples/vault.exemple.yml`, `docs/config-proxmox.md`) est corrigé en `production/`
|
||
(le modèle `socle` n'a qu'un inventaire `production/` ; `make config` résout
|
||
`lab > principal > production`). **Piège corrigé** : le socle livrait ses intrants en
|
||
`production/group_vars/all.yml` (forme fichier) ; y ajouter `all/vault.yml` (forme
|
||
dossier) fait **ignorer silencieusement** `all.yml` par Ansible (le dossier masque le
|
||
fichier — vérifié empiriquement). Le modèle est converti en forme dossier
|
||
(`group_vars/all/10-intrants.yml`), alignée sur l'instance prouvée. Validé :
|
||
`ansible-inventory --host` charge `domaine_interne` depuis la nouvelle disposition.
|
||
- **Commandes `make` périmées (AFF-080/081/082).** `make help` → `make` ;
|
||
`make syntax-template` → `make syntaxe-modele` (dans `docs/config-proxmox.md`,
|
||
`docs/modeles_vm/debian13-proxmox.md`, `docs/MISE-A-JOUR-CODEX-CLAUDE.md`).
|
||
- **Découvert et consigné (AFF-097, non traité ici) :** `lab/` codé en dur restant dans
|
||
les docs template/clone (`vm-lifecycle`, `procedure-template…`, `proxmox/README`,
|
||
message de `cloner_vm_debian.yml`) — lot séparé à prévoir.
|
||
- **Conformité Phase 3 — lot B « doc (prose) ».**
|
||
- **Prérequis Vault rappelé (AFF-026).** `QUICKSTART.md` : note que `make
|
||
inventaire-verifier` / `make verifier` chargent l'inventaire complet et exigent
|
||
`ANSIBLE_VAULT_PASSWORD_FILE`, sinon « no vault secrets found ».
|
||
- **Sous-dossiers `playbooks/` (AFF-039).** `AGENTS.md` : précisé que seuls `groupes/`,
|
||
`maintenance/`, `modeles_vm/`, `proxmox/` sont peuplés ; les autres sont prospectifs.
|
||
- **`SOLUTION.md` remis au présent (AFF-060/061).** Bannière « document historique »
|
||
(supplanté par README/QUICKSTART/`make`) ; arborescence complétée ; commande de
|
||
construction `--ask-pass` (mot de passe) → `make preparer-modele` (accès par clé).
|
||
- **Découvert et consigné (AFF-098, traitement B, non traité ici) :** contradiction
|
||
fonctionnelle — `make config` écrit le token Proxmox dans `all/vault.yml`, mais
|
||
`cloner_vm_debian.yml` ne le lit que depuis `proxmox.vault.yml`/env. Couplé à AFF-097
|
||
dans un futur lot « voûte Proxmox ».
|
||
|
||
### Corrigé
|
||
- **Conformité Phase 3 — lot « voûte Proxmox » (AFF-098 bug fonctionnel + AFF-097).**
|
||
- **Le clonage lit la voûte unifiée (AFF-098, option a).** `make config` écrit le token
|
||
Proxmox dans `instance/inventories/<env>/group_vars/all/vault.yml`, mais
|
||
`playbooks/proxmox/cloner_vm_debian.yml` (lancé `-i localhost,`) ne le chargeait que
|
||
depuis `proxmox.vault.yml` → `make creer-vm` échouait l'assert `proxmox_api_token_secret`
|
||
pour qui suivait la voûte unifiée. Corrigé : `all/vault.yml` ajouté aux sources de
|
||
secrets du playbook (autoritaire) ; la détection de voûte chiffrée du `Makefile`
|
||
(`cloner-vm`) cherche d'abord `all/vault.yml` puis `proxmox.vault.yml`. `proxmox.vault.yml`
|
||
reste accepté en compatibilité. **Validé** : `--syntax-check` OK, `ansible-lint` 0 échec,
|
||
test fonctionnel (token chargé depuis `all/vault.yml`, assert vert).
|
||
- **Docs Proxmox/template alignées (AFF-097).** `lab/` codé en dur → `production/` + voûte
|
||
unifiée dans `playbooks/proxmox/README.md`, `docs/procedure-template-debian13-proxmox.md`,
|
||
`docs/vm-lifecycle.md`, `docs/modeles_vm/debian13-proxmox.md`. Plus aucune référence
|
||
`inventories/lab/group_vars` dans les fichiers suivis.
|
||
- **Conformité Phase 3 — lot C « `make verifier` vert » (AFF-006).** `ansible-lint` passe de
|
||
**33 échecs à 0** (profil `min` → `production`), donc `make lint` **rc=0**. Trois causes :
|
||
- **`site.yml` généré lint-propre** : `scripts/orchestrer.py` émet un `name:` avant chaque
|
||
`import_playbook` (30× `name[play]`) ; `site.yml` régénéré. Orchestration inchangée.
|
||
- **`risky-shell-pipe`** : `set -o pipefail` + `executable: /bin/bash` sur les deux tâches
|
||
shell à pipe de `playbooks/valider.yml`.
|
||
- **`name[template]`** : Jinja déplacé en fin de `name` dans `supprimer_vm_debian.yml`.
|
||
**Validé** : `make lint` rc=0 ; toutes les étapes de `make verifier` vertes (lint, test,
|
||
site-verifier, flux-verifier, syntaxe) — seule `inventaire-verifier` requiert la voûte de
|
||
l'opérateur (prérequis documenté, AFF-026). Débloque AFF-002 (« exploitable sans IA » → ✅).
|
||
|
||
### Corrigé
|
||
- **Conformité Phase 5 — boucle documentaire (le parcours QUICKSTART marche seul, prouvé).**
|
||
Relecture de cohérence bout-en-bout (README/QUICKSTART/docs/wiki ↔ code final). Deux bogues
|
||
du modèle public/outillage débusqués et corrigés, en plus des alignements de prose :
|
||
- **Modèle `socle` invalide (AFF-099).** `exemples/modeles/socle/plan/domaines.yml` :
|
||
`autorite: interne` (périmé, rejeté par le validateur) → `auto-heberge`. Le modèle valide.
|
||
- **Split-brain d'inventaire (AFF-100).** `scripts/instancier.py` et `scripts/inventory_gui.py`
|
||
retombaient sur `principal/` quand aucun `hosts.yml` n'existe encore ; or le socle est en
|
||
`production/` → la 1ʳᵉ génération écrivait dans `principal/`, à côté des `group_vars` restés
|
||
en `production/`. Corrigé : le repli vise le **répertoire d'inventaire déjà présent** (comme
|
||
`config_proxmox.py`). Instances existantes (avec `hosts.yml`) **inchangées** (non-régression
|
||
vérifiée sur `principal`).
|
||
- **Alignements de prose.** `docs/intrants-communs.md` (`group_vars/all.yml` →
|
||
`all/10-intrants.yml`, forme dossier que le GUI écrit déjà) ; `QUICKSTART.md` étape 8
|
||
(`serveur_postgresql` → `serveur_powerdns`, groupe présent dans le socle).
|
||
- **Preuve** : parcours QUICKSTART rejoué **hors-ligne** sur une copie du socle
|
||
(`instancier generer`→`comparer`→`appliquer`) — écrit `production/hosts.yml`, diff **vide**
|
||
ensuite, 4 validateurs verts. Nouvelle preuve récurrente **P15** dans `make prouver`
|
||
(« modèle public socle valide ») : `make prouver` = **15 OK, 0 échec, 1 sautée**.
|
||
|
||
### Ajouté
|
||
- **Harnais de preuve `make prouver` (Phase 4).** Nouveau `scripts/prouver.py` — un
|
||
**orchestrateur mince** qui rejoue les preuves automatisables du registre en appelant
|
||
l'outillage **existant** (les mêmes scripts que `make verifier` : lint, tests, diff-vide,
|
||
validateurs de registres, cohérence groupes/playbooks, handlers, orchestration, flux,
|
||
syntaxe, existence des runbooks, invariants structurels) — **aucune validation
|
||
réimplémentée**. Produit `docs/audit/preuve-AAAA-MM-JJ.md` : rapport **horodaté,
|
||
rejouable**, reliant chaque preuve aux affirmations couvertes, listant à part les
|
||
déclarations d'intention (⚪). Sort en erreur si une preuve échoue ; la preuve `P15`
|
||
(inventaire Ansible complet) est **sautée** proprement sans mot de passe Vault (prérequis
|
||
AFF-026). Documenté dans `README.md` (une phrase) et `docs/audit/README.md` (mode
|
||
d'emploi complet + comment ajouter une preuve). `affirmations.md` : section « Couverture
|
||
par `make prouver` » reliant chaque ✅ à sa preuve. **1re exécution : 14 preuves OK,
|
||
0 échec, 1 sautée → CONFORME.**
|
||
- **Registre des affirmations (audit de conformité, Phase 1).** Nouveau
|
||
`docs/audit/affirmations.md` : chaque affirmation publique vérifiable du dépôt
|
||
(`README`, `AGENTS`, `CLAUDE`, `QUICKSTART`, `SOLUTION`, `docs/`, `wiki/`, aide du
|
||
`Makefile`, GUI) est tracée vers une **commande de preuve reproductible** et un statut
|
||
(✅ prouvée / 🟡 partielle / ❌ fausse / ⚪ invérifiable localement). **54 affirmations
|
||
enregistrées : 30 ✅, 13 🟡, 8 ❌, 3 ⚪.** Audit **sans aucun correctif** (les
|
||
traitements relèvent des phases suivantes). Preuves exécutées localement, hors
|
||
production : diff-vide du plan, recoupement notify↔handlers, validateurs de registres
|
||
(serveurs/applications/bases/domaines/GUI/orchestrateur/flux), `--syntax-check` de tous
|
||
les playbooks, test unitaire. Dix écarts majeurs classés par risque pour un opérateur
|
||
suivant la doc à la lettre — dont : `QUICKSTART` renvoie à un modèle absent
|
||
(`presence-web`), `make verifier` échoue (ansible-lint : 33 failures), contradiction
|
||
SSH `CLAUDE.md` ↔ code/`AGENTS.md`, chemin de voûte faux, commandes `make` périmées
|
||
dans `docs/`.
|
||
|
||
## 2026-07-07 (soir)
|
||
|
||
### Modifié
|
||
- **Nomenclature `ip-miroir` : longueurs UNIFORMES.** Le VLAN dérivé passe à
|
||
`1000 + index×10 + zone` → **toujours 4 chiffres** (1011..4094), donc le VMID
|
||
(`VLAN·octet·seq`) fait **toujours 9 chiffres**. Longueurs uniformes et mnémotechniques
|
||
(retirer 1000 redonne `index×10+zone`). Les deux tenants sont **convergés** sur le réseau
|
||
fédéré 10/8 : Chezlepro (idx 1) → VLANs 1011-1016 / 10.11.x ; Technolibre (idx 2) →
|
||
VLANs 1021-1026 / 10.12.x. Chezlepro quitte le bac-à-sable plat 192.168.15/VLAN 15.
|
||
|
||
### Ajouté
|
||
- **`make devis-reseau` : config switch dérivée du plan.** Nouveau `scripts/devis_reseau.py`
|
||
qui découvre les instances fédérées (`../*/plan/nomenclature.yml`, schéma `ip-miroir`) et
|
||
émet la config Cisco-like du réseau convergé — **VLANs + SVIs (passerelles) + ACLs d'isolation
|
||
inter-tenant** — dérivée des nomenclatures, jamais saisie à la main (toujours synchrone avec
|
||
le plan). One-shot précédemment ; maintenant reproductible.
|
||
- **GUI : vue « Réseau » (devis switch) + bouton Copier.** Nouvel onglet lecture seule qui
|
||
affiche le devis `make devis-reseau` (VLANs + SVIs + ACLs), via `GET /api/devis-reseau`, avec
|
||
un bouton **Copier** (prêt à coller sur le switch). Un opérateur génère et transmet la config
|
||
sans CLI.
|
||
- **GUI : erreurs de déploiement en LANGAGE CLAIR.** Quand une action (créer/vérifier/déployer)
|
||
échoue, le GUI n'oblige plus à lire le dump Ansible : un encadré résume les tâches en erreur —
|
||
**étape · hôte · type · message clair** — avec un bouton **« Copier »** (pour transmettre le
|
||
résumé à un humain / au mainteneur). Le backend (`_extraire_echec`) parse les `fatal:`/`UNREACHABLE!`,
|
||
privilégie la vraie cause (`stderr` plutôt que le générique « non-zero return code »), gère
|
||
`no_log` (« sortie masquée — secret ») et les injoignables. Éprouvé sur les 4 échecs réels du
|
||
from-zero du jour. `node --check` OK.
|
||
- **GUI : éditeur « Domaines » (6ᵉ onglet éditable).** Le registre des domaines publics — le seul
|
||
sans édition dans le GUI — a désormais son onglet : ajouter/retirer une zone DNS, **autorité**
|
||
(sélecteur `primaire-cache`/`auto-heberge`/`delegue`), **edge**, **DNSSEC**, **mail**, secondaires,
|
||
et affichage des **expositions (FQDN) sous la zone**. Backend : route `/api/domaines`,
|
||
`ecrire_domaines`, validation `valider_domaines` (rejette une autorité inconnue). Éprouvé :
|
||
round-trip backend OK, `node --check` OK, smoke serveur OK. (Le `autorite: interne` périmé des
|
||
instances Chezlepro/Technolibre a été corrigé en `auto-heberge` au passage.)
|
||
- **GUI : persistance des journaux d'exécution.** Chaque action lancée depuis l'interface
|
||
(créer-vm / vérifier / déployer) écrit désormais, **en plus du streaming live**, un journal
|
||
horodaté `<instance>/logs/<hôte>-<action>-<date>.log` (gitignoré). Bonus : si le navigateur se
|
||
déconnecte, l'exécution **continue** et le journal est capturé jusqu'au bout (au lieu d'être
|
||
interrompue) — utile pour un déploiement qu'on ne veut pas voir avorter à la fermeture d'un onglet.
|
||
- **GUI : vues « Flux » et « Couches » (lecture seule).** Deux nouveaux onglets rendent visibles
|
||
dans l'interface deux registres jusque-là en ligne de commande seulement : **Flux** = la matrice
|
||
d'audit réseau (rôle · sens · port · pair · chiffrement coloré · raison, depuis les `meta/flux.yml`),
|
||
et **Couches** = l'ordre de reconstruction (socle → pki → services → apps → agents, avec les groupes
|
||
triés topologiquement par couche). Alimentées par `resoudre_flux`/`orchestrer` via l'API. Éprouvé :
|
||
`node --check` OK, smoke test serveur (63 flux, 6 couches servis).
|
||
|
||
### Modifié
|
||
- **GUI : intégration des intrants de session.** Le panneau « Intrants de base » expose désormais
|
||
**`nftables_admin_ssh`** (type liste, section « Sécurité ») — la garde anti-lockout du pare-feu
|
||
était éditable en fichier mais absente du GUI. Retrait de `vault_step_ca_fingerprint` des secrets
|
||
attendus (empreinte du root CA désormais dérivée dynamiquement, plus un secret). `node --check` OK.
|
||
|
||
## 2026-07-07 — Reconstruction from-zero PROUVÉE (preuve de portabilité « sans réserve »)
|
||
|
||
Les 14 VM du lab (+ sauvegardes + AC) supprimées, puis `make myDay` a reconstruit l'écosystème
|
||
POC **de rien** : 13 hôtes déployés (0 échec), et `make valider` **entièrement vert** (Prometheus
|
||
6/6 UP, 6 vhosts HTTPS, courriel bout-en-bout remis, 6 dépôts restic restaurés) — le tout sous
|
||
pare-feu actif. 4 bugs de portabilité débusqués et corrigés dans le moteur :
|
||
|
||
### Corrigé
|
||
- **client_pki : empreinte du root CA dérivée dynamiquement** (au lieu d'une valeur figée en Vault).
|
||
Une AC régénérée (from-zero) a une empreinte neuve ; le rôle la lit désormais de l'autorité
|
||
elle-même (`step certificate fingerprint`, délégué au nœud step-ca), source de vérité.
|
||
`client_pki_ca_fingerprint_override` permet un épinglage explicite.
|
||
- **serveur_keycloak : assignation de rôle tolérante aux utilisateurs absents.** Sur un annuaire
|
||
vide (from-zero), assigner un rôle à un user inexistant échouait ; on vérifie désormais son
|
||
existence (Keycloak fédère LDAP à la demande) et on saute proprement sinon.
|
||
- **serveur_prometheus : ne scrute que les hôtes ACTIFS** (`client_metrique ∩ hotes_actifs`). Un
|
||
hôte planifié (non déployé) n'est plus une cible morte.
|
||
- **Pare-feu nftables compatible Docker.** Le ruleset résolu (a) remplace **uniquement la table
|
||
`setops_flux`** au lieu de `flush ruleset` (préserve les tables Docker : DNAT/forward des
|
||
conteneurs) et (b) autorise `docker0` + `ct established,related` dans la chaîne `forward`. Sans
|
||
ça, `forward policy drop` coupait Collabora (conteneur). Diagnostic prouvé au niveau paquet.
|
||
|
||
### Ajouté
|
||
- **`playbooks/proxmox/supprimer_vm_debian.yml`** — suppression de VM par VMID (from-zero), avec
|
||
garde-fous : n'agit que sur les VMID présents dans le cluster, refuse si le modèle est ciblé,
|
||
secrets `no_log`.
|
||
|
||
## 2026-07-07
|
||
|
||
### Ajouté
|
||
- **`make valider` — recette d'acceptation fonctionnelle (phase 4, v1).** Vérifie que les services
|
||
*fonctionnent*, pas juste qu'ils sont déployés (complète les `*-verifier` statiques). `playbooks/valider.yml`,
|
||
lecture seule : **cibles Prometheus toutes UP** (API `/targets`) + **vhosts HTTPS exposés répondent**
|
||
(dérivés des `server_name` réels de l'edge, filtrés sur `domaine_interne` — générique) + **courriel
|
||
bout-en-bout** (envoi via le MTA → LDAP → LMTP → Maildir, remise vérifiée par `doveadm` sur le
|
||
compte `testmail`, message de test nettoyé) + **restauration de sauvegarde** (pour chaque nœud
|
||
`client_backup` : restic restaure le dernier snapshot dans un dossier temporaire — lecture seule
|
||
sur le dépôt — et vérifie que des fichiers en sortent). Éprouvé sur la flotte vivante sous pare-feu
|
||
actif : 7 cibles UP, 8 vhosts OK, courriel remis, 6 dépôts restaurables. **La recette a trouvé un
|
||
vrai trou** (collab-01/Nextcloud sans `client_backup_jobs` → service de sauvegarde en échec, fichiers
|
||
non protégés), corrigé côté instance.
|
||
- **Pare-feu nftables activé sur TOUTE la flotte (14 nœuds, activation prudente).** Les 14 hôtes
|
||
actifs tournent sous `policy drop` avec leur ruleset **résolu moindre-privilège** (flux est-ouest
|
||
déclarés autorisés par source, reste refusé). Vérifié en conditions réelles : flux déclarés OPEN
|
||
(keycloak→pg, postfix→dovecot LMTP, prometheus→node_exporter…), flux non déclaré DROP
|
||
(forge→redis), 14/14 `active`+`enabled`+`policy drop`, contrôleur toujours joignable.
|
||
- **Intrant `nftables_admin_ssh`** (garde anti-lockout) — CIDR d'administration TOUJOURS
|
||
autorisés en SSH, indépendamment des flux. Le résolveur (`resoudre_flux.py`) l'injecte en tête
|
||
de chaque ruleset. **Bug de conception rattrapé avant activation** : le contrôleur Ansible arrive
|
||
par VPN (`192.168.255.2`, hors sous-réseau flotte) — sans cette règle, activer = lockout immédiat.
|
||
- **Rollout prudent** : d'abord `infra-pki-01` seul (test dead-man switch `systemd-run`, SSH
|
||
re-vérifié sous drop, puis permanent), puis les 13 autres par lot (dead-man 5 min + sonde des
|
||
flux est-ouest avant de persister). Activation pilotée par `nftables_baseline_enabled: true`
|
||
(group_vars `hotes_actifs` ; le golden template n'y est pas → reste sans pare-feu, voulu).
|
||
- Le déploiement dépose `instance/flux-genere/<hôte>.nft` dans `/etc/nftables.conf` + service
|
||
`enabled` (survit reboot ET futurs `make myDay`).
|
||
|
||
### Corrigé
|
||
- **Détection du coffre Vault : `production/` codé en dur → inventaire réel.** `deployer`,
|
||
`deployer-tout` et `verifier-deploiement` cherchaient le coffre chiffré dans
|
||
`inventories/production/group_vars`, alors qu'une instance en `principal/` (cas courant) n'a pas
|
||
ce chemin → l'invite du mot de passe Vault ne se déclenchait jamais et le déploiement échouait au
|
||
déchiffrement. Corrigé : la garde vise désormais le `group_vars` de l'inventaire **résolu**
|
||
(`$(dir $(INVENTAIRE_PRODUCTION))group_vars`). Vérifié : le coffre de `principal/` est bien détecté.
|
||
|
||
### Modifié
|
||
- **`nftables_baseline` branché sur les flux résolus (reconstruction, phase 0).** Le rôle déploie
|
||
désormais le ruleset **résolu** généré par `make flux` (`instance/flux-genere/<hôte>.nft` — règles
|
||
par source, `ip saddr` = moindre privilège) quand il est présent ; sinon repli sur le gabarit plat.
|
||
**Toujours `nftables_baseline_enabled: false` par défaut → aucune activation** (l'activation reste
|
||
un geste dédié, testé par nœud). Nouveau var `nftables_baseline_ruleset_genere`. Syntax-check OK.
|
||
### Ajouté
|
||
- **Reconstruction from-zero en une commande (reconstruction, phase 3 — outillage).** La création
|
||
de VM était unitaire (`creer-vm HOTE=…`) ; on comble le trou entre *créer* (2a) et *configurer*
|
||
(2b) :
|
||
- **`make flotte-creer CONFIRMER=true`** — boucle `creer-vm` sur tous les hôtes actifs du plan
|
||
(clone Proxmox). Nouvelle sous-commande `inventory_host.py lister-actifs`.
|
||
- **`make reconstruire CONFIRMER=true`** — enchaîne **flotte-creer → attente SSH de la flotte
|
||
(`_attendre-flotte`, `ATTENTE_MAX` réglable) → `deployer-tout`**. La reconstruction complète en
|
||
une commande, **idempotente de bout en bout** : le clone (`proxmox_kvm`) saute une VM déjà
|
||
présente (par nom), le réseau/disque sont `present`/`resized` (grow-only), le déploiement Ansible
|
||
converge. Re-lançable sans risque, qu'il reste des VM ou non.
|
||
- **`make myDay`** repointé sur `reconstruire` (le vrai « bouton rouge » ; n'était qu'un alias de
|
||
`deployer-tout`). Distinction assumée : `deployer-tout` = **converger** la config d'une flotte
|
||
existante (2b, avec `MODE_CHECK=1`) ; `reconstruire`/`myDay` = **créer les VM manquantes puis
|
||
déployer** (2a+2b).
|
||
Gardes `CONFIRMER=true` sur les trois. Non testé contre Proxmox/lab (validé : énumération des 14
|
||
hôtes actifs, refus sans `CONFIRMER`, enchaînement `make -n`).
|
||
|
||
## 2026-07-06
|
||
|
||
### Ajouté
|
||
- **`make wiki-publier` — fin du dernier geste manuel (reconstruction, phase 0).** Le wiki
|
||
pédagogique (`wiki/`, source versionnée) se publie désormais dans le wiki Forgejo par
|
||
`make wiki-publier WIKI_REMOTE=…<dépôt>.wiki.git` : clone superficiel du wiki, synchronisation des
|
||
pages (`wiki/*.md` sauf `README.md` ; suppressions propagées), commit + push seulement s'il y a du
|
||
changement. Refuse sans `WIKI_REMOTE`. Éprouvé de bout en bout contre un dépôt bare local (17
|
||
pages publiées = 17 source, diff vide, README exclu, `_Sidebar` inclus, idempotent au 2e passage).
|
||
|
||
- **Registre des flux réseau complété (reconstruction, phase 0).** Transcription du travail
|
||
zéro-confiance est-ouest dans `meta/flux.yml` : **16 rôles remplis** (step_ca, openldap, powerdns,
|
||
prometheus, loki, redis, rspamd, backup, dovecot, postfix, keycloak, forgejo, grafana, icinga,
|
||
icingaweb2, nextcloud, oauth2_proxy + le socle `serveur_debian` pour le plan de gestion SSH, +
|
||
les 5 clients pki/journal/smtp/backup/unbound). Le registre couvre désormais **29 rôles, 63 flux**
|
||
(qui-parle-à-qui : port, sens, pair, chiffrement, raison) — la base de génération nftables/OPNsense
|
||
et la matrice d'audit. Schéma enrichi (`docs/flux-conception.md`) : valeurs `ssh` (transport SSH,
|
||
restic/backup + SSH de gestion) et `tls-cible` (TLS visé, feuille de route edge→backends).
|
||
**Point critique traité** : SSH (22) déclaré au socle, sinon les nftables générés couperaient
|
||
l'accès Ansible. Validé : schéma conforme (0 erreur) et **matrice cohérente** (tout egress vers un
|
||
service a l'ingress correspondant en face). Reste phase 0 : la cible `make wiki-publier`.
|
||
|
||
- **Résolveur de flux (reconstruction, phase 0 — §Séquence 2).** `scripts/resoudre_flux.py` agrège
|
||
les `meta/flux.yml`, résout les `pair`, et produit **deux artefacts, hors-ligne, sans activation** :
|
||
- **`docs/registre-flux.md`** (généré) — la matrice d'audit source→destination (rôle, sens, port,
|
||
chiffrement, raison) + synthèse chiffrement. Artefact du label de certification.
|
||
- **aperçus nftables par hôte** (`instance/flux-genere/<hôte>.nft`, gitignorés) — règles résolues
|
||
avec IP réelles, `ip saddr` = **moindre privilège** (ex. LMTP 24 sur le mail n'accepte que l'IP
|
||
du nœud Postfix), `policy drop`. **Aperçus inspectables, NON activés** (l'activation reste un
|
||
geste dédié testé par nœud, cf. flux-conception §Activation prudente).
|
||
- Cibles **`make flux`** (registre + aperçus) et **`make flux-verifier`** (schéma + matrice,
|
||
branché dans `make verifier`). Validé : 29 rôles / 63 flux cohérents, 14 aperçus générés.
|
||
Reste : brancher `nftables_baseline` (modèle plat aujourd'hui) sur ces aperçus, et le test lab.
|
||
|
||
- **Orchestrateur ordonné (reconstruction, phase 2).** `playbooks/site.yml` n'est plus un stub :
|
||
c'est désormais un **point d'entrée ordonné généré**, qui déploie l'écosystème **couche par
|
||
couche, dans l'ordre de reconstruction**, sans intervention manuelle. Nouveautés :
|
||
- **`docs/couches-deploiement.yml`** — registre central des couches ordonnées
|
||
(`socle → pki_racine → pki_client → services → apps → agents`), les 30 groupes déployables classés.
|
||
- **`scripts/orchestrer.py`** — trie les groupes par couche (clé primaire) puis **topologiquement
|
||
intra-couche** via `dependances-groupes.yml` (ex. dovecot avant postfix, icingaweb2 après icinga,
|
||
nextcloud en dernier). Génère `site.yml` comme une séquence d'`import_playbook`. Artefact du
|
||
moteur (déterministe, sans donnée d'instance ; Ansible saute les groupes sans hôte actif).
|
||
**Deux gardes anti-dérive** (refus si violé) : bijection univers↔couches (un nouveau rôle non
|
||
classé casse la génération) et aucune arête « en arrière » (un prérequis dans une couche plus
|
||
tardive = classification fausse). Éprouvées par test négatif.
|
||
- **`make site`** (régénère + syntax-check), **`make site-verifier`** (cohérence, branché dans
|
||
`make verifier`), **`make deployer-tout CONFIRMER=true`** (déploiement orchestré de la flotte,
|
||
limité à `hotes_actifs` ; garde `CONFIRMER` car action impactante ; `MODE_CHECK=1` pour l'essai
|
||
idempotent à blanc). Validé : `verifier` OK (30 groupes, aucun cycle/arête arrière),
|
||
`--syntax-check` du `site.yml` généré OK, refus `deployer-tout` sans `CONFIRMER` (rc=2).
|
||
|
||
### Modifié
|
||
- **Audit exhaustif du codé-en-dur (reconstruction, phase 1b).** Balayage complet
|
||
`tasks + templates + defaults` de tous les rôles (noms de tenant, IP, domaines, emails, orgs).
|
||
Résultat : le moteur ne porte plus **aucun** nom de tenant en dur. Corrigé — les labels/slug
|
||
OIDC dérivent désormais de l'intrant `organisation` : `serveur_forgejo_oidc_nom` (slug de
|
||
callback, `organisation | lower | replace(' ','-')`), `serveur_grafana_oidc_nom`,
|
||
`serveur_nextcloud_oidc_nom`, `serveur_nextcloud_theme_nom` (labels d'affichage). Commentaires
|
||
« Se connecter avec Chezlepro » → génériques. Non-régression SSO : `organisation: Chezlepro` →
|
||
slug `chezlepro`, **identique** à l'URI de redirection Keycloak de l'instance (pas de casse).
|
||
Conservés intentionnellement : realm `default('chezlepro')` (décision `identite_realm` actée) et
|
||
le thème visuel **Alliance Boréale** (identité par défaut assumée du réseau, pas un tenant).
|
||
Validé : re-balayage vide, rendu Jinja du slug testé (Chezlepro/Alliance Boréale/Ma Coop),
|
||
`--syntax-check` OK (playbook forgejo via inventaire `principal`).
|
||
- **Audit du graphe de dépendances (reconstruction, phase 1a).** `docs/dependances-groupes.yml`
|
||
gagne les prérequis inter-groupes confirmés dans le code, en vue de l'orchestrateur trié en
|
||
topologie. Ajouts : `serveur_keycloak` → **`serveur_openldap`** (fédération LDAP via
|
||
`resoudre_annuaire_uri`, en plus de PostgreSQL) ; **`serveur_dovecot`** → `serveur_openldap`
|
||
(userdb/passdb LDAP) ; **`serveur_postfix`** → `serveur_dovecot` (remise LMTP au mailstore) ;
|
||
**`serveur_icingaweb2`** → `serveur_icinga` + `serveur_postgresql` + `serveur_openldap`
|
||
(IcingaDB + auth LDAP) ; **`serveur_nextcloud`** → `serveur_postgresql` + `serveur_keycloak`
|
||
(OIDC) + `serveur_collabora` (validation WOPI). Réconciliation `meta/liens.yml` : le seul lien
|
||
structurel (`serveur_postfix` mailstore → Dovecot) coïncide avec le graphe. **Conclusion d'archi :**
|
||
la règle « TLS vérifié ⇒ `client_pki` aux deux bouts » ne devient PAS des arêtes par-groupe
|
||
(client_pki est quasi universel) — c'est une **couche** de l'ordre de reconstruction
|
||
(`socle → step_ca → client_pki → services → apps → agents`) ; `dependances-groupes.yml` ne
|
||
capture que le fin ordonnancement intra-couche. Validé : YAML conforme, **aucun cycle**, tri-topo
|
||
réussi (19 nœuds), chargeur `charger_dependances` accepte (12 groupes, `est_groupe_operationnel`
|
||
OK), tous les groupes ont un rôle.
|
||
|
||
## 2026-07-05
|
||
|
||
### Modifié
|
||
- **VLAN dérivé du tenant (réseau convergé).** `deriver_nomenclature` (schéma `ip-miroir`) dérive
|
||
désormais le VLAN = `index × 10 + zone` — **unique globalement** sur un trunk convergé (chaque
|
||
tenant son bloc de 10 ; 1-9 réservés à l'infra partagée). Le VLAN contenant déjà le tenant (1er
|
||
chiffre = index), le VMID **mène avec le VLAN** (`VLAN·octet·seq`, ≤ 9 chiffres Proxmox). Ex.
|
||
Technolibre (index 2) → VLANs 21-26, VMID 2101101. Le schéma `compact` (lab, sandbox) reste
|
||
inchangé. Champs `vlan:` codés en dur retirés des catégories ip-miroir (désormais dérivés).
|
||
|
||
### Ajouté
|
||
- **Zéro-confiance est-ouest — flux Métriques, Logs et Courriel chiffrés.** Suite du chantier
|
||
(après PostgreSQL) : **métriques** (node_exporter sert en HTTPS via cert step-ca +
|
||
`--web.config.file` + cert-sync owned prometheus ; Prometheus scrape `scheme: https` +
|
||
`tls_config`), **logs** (Loki `http_tls_config` + cert-sync owned loki ; Alloy push `https` +
|
||
`tls_config`), **courriel** (LMTP `edge-mta→infra-mail:24` en `lmtp_tls_security_level=verify` +
|
||
`lmtp_tls_CAfile` ; `client_smtp` en STARTTLS vérifié). Chacun prouvé de bout en bout (200 HTTPS,
|
||
cibles UP, livraison `status=sent`, HTTP rejeté). Motif cert-sync `.path` industrialisé. Reste :
|
||
edge→backends + DNS (DoT).
|
||
- **VMID 9 chiffres mnémotechnique (schéma `ip-miroir`, opt-in).** `vmid_schema: ip-miroir` dans la
|
||
nomenclature → VMID `I·VVV·HHH·NN` (index·VLAN·octet-hôte·séquence) : le VMID *contient* l'IP
|
||
(`10.(10+index).VLAN.hôte`) + le tenant, lisible d'un coup d'œil. Défaut `compact` rétro-compatible
|
||
(instances déployées inchangées).
|
||
- **Instance partenaire Technolibre.** Écosystème complet (12 VM, `etat: planifie`) dans
|
||
`10.12.16.0/20`, **6 zones de sécurité** (Frontière/Identité/Données/Services-infra/Observabilité/
|
||
Applications, un /24 + VLAN chacune), index de fédération 2, VMID ip-miroir. Preuve de portabilité
|
||
d'un tenant.
|
||
|
||
### Modifié
|
||
- **Références par FQDN partout (fin des IP codées en dur).** Décision d'archi : FQDN pour toute
|
||
référence inter-services (non ambigu en fédération, canonique pour TLS ; nom court = hostname OS).
|
||
`resoudre_base` renvoie le FQDN (→ keycloak/forgejo/icinga) ; `client_journal_loki_url` dérivé du
|
||
groupe `serveur_loki` ; defaults db_host IP morts nettoyés. **Aucune IP littérale dans les defaults.**
|
||
- **Découplage du tenant d'origine.** Realm SSO centralisé sur l'intrant **`identite_realm`** (défaut
|
||
`chezlepro` ; les 4 rôles keycloak/forgejo/grafana/oauth2_proxy en dérivent ; exposé dans la GUI).
|
||
Vars brandées renommées génériques : `chezlepro_timezone→fuseau_horaire`,
|
||
`chezlepro_organisation→organisation`. Le moteur ne porte plus le nom d'un tenant.
|
||
- **Modèles d'instance rafraîchis.** `integral` régénéré depuis le cas prouvé (6 zones, fonctions
|
||
éprouvées `data-sql`/`id-ldap`/`id-sso`/`sup`, ip-miroir, nouveaux intrants) ; `socle`/`identite`/
|
||
`observabilite`/`forge` réalignés sur le même moule (prouvés : dérivation + `valider_serveurs`).
|
||
`presence-web` marqué **aspirationnel** (rôles web-frontal/dorsal absents) plutôt que faussement prêt.
|
||
|
||
## 2026-07-04
|
||
|
||
### Ajouté
|
||
- **Zéro-confiance : flux PostgreSQL entièrement chiffré et vérifié.** PG sert désormais son **cert
|
||
step-ca** (vérifiable contre root_ca) au lieu du snakeoil, et **refuse** toute connexion non-TLS
|
||
du réseau (`hostssl` dans pg_hba). Les 3 clients passent en **verify-full** : keycloak
|
||
(`db-url-properties sslmode=verify-full`), forgejo (`SSL_MODE=verify-full` + `PGSSLROOTCERT`),
|
||
IcingaDB (`tls: true` + `ca`). Prérequis posés : `client_pki` sur data-sql-01 (cert), et
|
||
**`root_ca.crt` en 0644** (cert public, requis par les clients TLS non-root). cert-sync PG
|
||
(motif `.path`, owned postgres) + reload de l'instance `postgresql@NN-main`. Vars :
|
||
`serveur_postgresql_tls_actif`/`_tls_force`, `serveur_*_db_sslmode`/`_ca`. Prouvé de bout en bout
|
||
(cert Set-OPS CA servi, apps 200, non-TLS rejeté « aucun chiffrement », TLS accepté).
|
||
|
||
### Corrigé
|
||
- **PG : détection de version robuste (collision avec un répertoire non-numérique).** Placer le
|
||
`tls_dir` sous `/etc/postgresql/` faisait choisir `tls` comme « version » de cluster
|
||
(`find | sort | last`) → configs déployées au mauvais endroit (verrou hostssl inopérant).
|
||
Corrigé : détection filtrée aux dossiers **numériques** (`^[0-9]+$`) + `tls_dir` déplacé sous
|
||
`/var/lib/postgresql/tls`.
|
||
- **Renouvellement de cert : recharger le VRAI consommateur (bug latent de flotte).** Le
|
||
`cert-renewer@.service` (client_pki) renouvelait le cert sur disque mais son `ExecStartPost`
|
||
rechargeait un service nommé *d'après le cert* (`%i` = FQDN), inexistant → **nginx (et postfix,
|
||
dovecot, slapd) n'étaient jamais rechargés** et servaient l'**ancien cert jusqu'à expiration**.
|
||
Symptôme vécu : cert edge expiré en mémoire (renouvelé sur disque), échec TLS de l'échange
|
||
code→jeton OIDC → **login Grafana/SSO cassé** (tous les services derrière l'edge). Correctif :
|
||
`client_pki_reload_services` (liste des vrais consommateurs), câblée par groupe (edge→nginx,
|
||
mail→postfix/dovecot, annuaire→slapd). Appliqué + vérifié sur les 4 hôtes. Fix immédiat de
|
||
l'incident : `systemctl reload nginx` sur l'edge.
|
||
|
||
### Ajouté
|
||
- **Doc à jour : unité wiki « Autorisation & RBAC », leçon renouvellement, runbooks.** Fermeture
|
||
des dettes de doc : nouvelle **unité wiki authZ/RBAC** (pendant d'*Identité & SSO*, avec l'exemple
|
||
Grafana), **section « le renouvellement est un système »** versée dans l'unité *PKI* (comparer cert
|
||
servi vs fichier ; recharger le consommateur), et **`docs/runbooks-exploitation.md`** (cert expiré,
|
||
RBAC Grafana, branding Forgejo). 15 unités wiki désormais.
|
||
- **UI des logs Loki : dashboard Grafana provisionné.** Loki n'a pas d'UI ; son UI est Grafana.
|
||
Ajout d'un **dashboard « Journaux de la flotte »** (dossier Set-OPS) : sélecteur d'hôte multi +
|
||
filtre regex insensible à la casse + panneau logs + débit par hôte. Référence Loki par une
|
||
**variable de datasource** (`ds_loki`), pas un UID codé en dur (leçon : ajouter un uid explicite à
|
||
une datasource déjà provisionnée casse le démarrage de Grafana). **Visible par les Viewers** (dont
|
||
testmail) sans accès Explore. Prouvé sur obs-01 (dashboard chargé, Grafana actif).
|
||
- **RBAC via SSO : rôle de realm → niveau Grafana.** Machinerie *additive et idempotente* dans
|
||
`serveur_keycloak` (`rbac-oidc.yml`) : rôles de realm (`serveur_keycloak_realm_roles`), **mapper
|
||
`roles`** sur les clients choisis (`_role_mapper_clients`, rôles de realm → claim `roles` dans
|
||
ID token + userinfo), **assignations** rôle→utilisateur (`_role_assignments`). Côté Grafana,
|
||
`role_attribute_path` (`grafana-admin→Admin`, `grafana-editor→Editor`, sinon Viewer). kcadm **à
|
||
chaud, zéro coupure SSO**. Prouvé (idempotence `changed=0`) sur id-sso-01 : rôles créés, mapper
|
||
présent, `testmail` = `grafana-editor` (→ Explore). Illustre l'**authZ** (vs authN du SSO).
|
||
|
||
## 2026-07-03
|
||
|
||
### Ajouté
|
||
- **Identité visuelle Alliance Boréale sur Forgejo (léger, officiel).** Branding via le dossier
|
||
**`custom/`** de Forgejo (mécanisme *officiel* — pas de fork, résistant aux MAJ) : accent **aurore
|
||
par variables CSS** (`--color-primary`…, aucune classe interne touchée), **logo/favicon étoile**
|
||
(réutilisés du thème Keycloak), **page d'accueil brandée** (`home.tmpl` : hero aurore + accroche),
|
||
**thème sombre** par défaut, **nom + méta**. Codifié dans `serveur_forgejo`
|
||
(`serveur_forgejo_branding`, `_app_name`, `_theme`), déployé dans `{{ data }}/custom/`. **Prouvé**
|
||
sur forge-01 : accueil rend (200, « Forge Chezlepro »), `alliance.css` servi (cyan aurore), lint OK.
|
||
- **Wiki pédagogique Forgejo — 14 unités d'apprentissage.** Set-OPS comme *compagnon pédagogique* :
|
||
chaque service = une lentille sur un fondamental TIC, méthodes génériques (on apprend OIDC, pas
|
||
Keycloak). Source versionnée dans `wiki/`, publiée dans le wiki Forgejo (`eregion`). Moule à 4
|
||
temps (concept → Set-OPS → transférable → à toi de jouer, avec *casse-répare*).
|
||
- **GUI : boutons cohérents.** « Pousser » (surchargé : clonait *et* déployait) → « 🖥 Créer la VM »
|
||
pour le clone, « Déployer » partout pour le déploiement, dry-run obligatoire partout. GUI 100 %
|
||
française.
|
||
- **Agents d'observabilité/ops éprouvés — observabilité *flotte-complète*.** Les 3 intégrations
|
||
« agent » (liaisons nœud × optionnelles) déployées sur 4 nœuds (obs-01, data-sql-01, id-ldap-01,
|
||
forge-01) et **prouvées** : `client_metrique` (node_exporter → **Prometheus scrape les 4 cibles,
|
||
toutes UP**) ; `client_journal` (journald → **Loki reçoit les logs des 4 nœuds**) ; `client_smtp`
|
||
(msmtp → **courriel système d'un nœud relayé par l'edge-MTA et livré**). Comble le trou : les
|
||
*serveurs* d'observabilité (Grafana/Prometheus/Loki) étaient prouvés, mais pas la collecte
|
||
fleet-wide — désormais Grafana voit toute la flotte. Cibles corrigées (bac à sable) : Loki→obs-01,
|
||
relais→edge-mta-01 (pas infra-mail-01, qui est le store Dovecot sans SMTP :25).
|
||
- **Thème de connexion Keycloak à l'identité Alliance Boréale.** Thème de login `alliance-boreale`
|
||
(`roles/serveur_keycloak/files/themes/`, `parent=keycloak` + overlay CSS) reprenant l'identité du
|
||
site de l'Alliance (extraite de `site-alliance-boreale`) : **ciel nocturne aurore** (`#05060f`/
|
||
`#0a0d24` + dégradés), **carte glassmorphism**, **logo étoile aurore** (le `favicon.svg` du site),
|
||
**bouton dégradé aurore** (teal→cyan, pilule), liens cyan, **police système** (souveraineté, zéro
|
||
dépendance externe). Déployé dans `{{ keycloak_home }}/themes/`, appliqué au realm via
|
||
`kcadm ... -s loginTheme` (var `serveur_keycloak_login_theme`, idempotent), Keycloak rechargé
|
||
(`flush_handlers` avant la config realm). **Prouvé** : la page de login charge `alliance.css`
|
||
(HTTP 200) + le `logo.svg` (200), `loginTheme=alliance-boreale` actif sur `chezlepro`.
|
||
**Constellation animée en fond** (`scripts=js/constellation.js`) : le JS crée son propre ciel
|
||
(canvas + aurore, le template n'en ayant pas) — étoiles scintillantes qui dérivent, liens de
|
||
constellation cyan, blob d'aurore ondulant ; respecte `prefers-reduced-motion`. Prouvé :
|
||
`constellation.js` référencé + servi (200).
|
||
**Console de compte thémée aussi** (thème `account`, `parent=keycloak.v3`) : overlay CSS
|
||
surchargeant les variables PatternFly 5 (fond aurore, cartes en verre, accent aurore) + la même
|
||
constellation animée. Var `serveur_keycloak_account_theme` via `kcadm -s accountTheme`. Prouvé :
|
||
console charge (HTTP 200, `keycloak.v3` intact), `account.css` servi (200).
|
||
- **Soumission courriel `:587` interne (authentifiée) — la boucle souveraine est bouclée.**
|
||
Postfix (`edge-mta`) sert la **soumission `:587`** (bloc `master.cf` : STARTTLS requis, `SMTP AUTH`,
|
||
seuls les authentifiés relaient) ; l'auth SASL est **déléguée à Dovecot** (`infra-mail`, passdb LDAP
|
||
prouvé) via un **auth-listener réseau** (`service auth { inet_listener sasl }`, port 12345). Aucune
|
||
sortie internet : interne→interne uniquement (l'externe = déliverabilité, Étape B). **Prouvé** (swaks) :
|
||
`testmail` s'authentifie (`235 Authentication successful`), Postfix accepte (`250 queued`), et le
|
||
courriel est **livré dans la boîte** (LMTP→Dovecot). Le courriel souverain fait maintenant **recevoir
|
||
ET envoyer**. Vars : `serveur_dovecot_sasl_reseau`, `serveur_postfix_submission_actif`. Pièges :
|
||
Dovecot 2.4 exige un **nom** de section `inet_listener` ; ajouter un service `master.cf` (nouveau
|
||
listener) → handler **restart** (pas reload) ; et une config cassée peut bloquer un redéploiement si
|
||
la synchro cert/restart précède le template (corriger la config à la main pour débloquer).
|
||
- **Sauvegardes applicatives (logiques) — `serveur_backup` + `client_backup` (restic), Tier 0 prouvé.**
|
||
Choix : sauvegarder la **donnée d'état** (non régénérable) plutôt que les VM (reconstructibles par
|
||
le code + le template). Outil **restic** (chiffrement côté client, déduplication, rétention).
|
||
`serveur_backup` (nœud `backup-01`) = cible SFTP/SSH (utilisateur `restic`, clé autorisée, dépôts
|
||
sous `/srv/restic/<nœud>`). `client_backup` (intégration par nœud) = restic + **jobs déclaratifs**
|
||
(`client_backup_jobs` : `{nom, commande?, chemins}`), clé SSH + mot de passe restic en voûte,
|
||
script + **timer systemd** (quotidien) + rétention `forget --prune`. **Prouvé de bout en bout**
|
||
sur le **Tier 0** (`infra-pki-01` → `/etc/step-ca`, l'ancre de confiance) : sauvegarde **hors-nœud**
|
||
vers `backup-01`, puis **restauration byte-identique** des clés CA (`root_ca_key`,
|
||
`intermediate_ca_key`, `ca.json`). Piège corrigé : le plancher `/etc/hosts` d'un nœud existant
|
||
ignore un nœud nouvellement ajouté → rafraîchir le socle.
|
||
- **Sauvegardes Tier 1 généralisées — 5 nœuds, restauration prouvée.** `client_backup` étendu (jobs
|
||
déclaratifs en host_vars) à : `data-sql-01` (`pg_dumpall` — keycloak/forgejo/icingadb),
|
||
`id-ldap-01` (`slapcat` LDIF — les identités), `infra-mail-01` (`/var/vmail` — les boîtes),
|
||
`forge-01` (`/var/lib/forgejo` + `/etc/forgejo` — dépôts Git ; la BD est déjà couverte par PG).
|
||
**Prouvé par restauration** : dump PostgreSQL restauré contient bien les 3 bases (`CREATE DATABASE
|
||
forgejo/icingadb/keycloak`) ; LDIF restauré contient `testmail`. Les 5 dépôts restic (pki, sql,
|
||
ldap, mail, forge) sont hors-nœud sur `backup-01`, chiffrés. Ajouter un service à sauvegarder =
|
||
déclarer un job. Reste : cible **offsite** (3-2-1, Étape B — le dépôt n'est qu'une URL swappable).
|
||
- **Consolidation — binding `annuaire` (`resoudre_annuaire`) + retrait de la cruft.**
|
||
*Cruft* : supprimés les 8 dossiers-catégories inertes (`roles/{applications,backup,database,
|
||
identity,monitoring,proxmox,storage,web}/`, README seuls) et les 5 playbooks-échafaudages `debug`
|
||
sans rôle (nextcloud, collabora, client_supervision, web_frontal, web_dorsal) ; l'intention reste
|
||
documentée dans `docs/catalogue-services.md`. *Binding annuaire* : nouveau rôle utilitaire partagé
|
||
`resoudre_annuaire` (comme `resoudre_base`) qui **dérive** la connexion OpenLDAP du `domaine_interne`
|
||
+ un hôte d'annuaire surchargeable — LE seul endroit où le nom d'hôte de l'annuaire est fixé, au lieu
|
||
d'être répété. Facts `resoudre_annuaire_{uri,port,base_dn,users_dn,bind_dn,bind_password}` (secret
|
||
déréférencé, `no_log`). **Migrés + prouvés (config neutre, `changed=0`)** : `serveur_keycloak`
|
||
(fédération LDAP — testmail token HTTP 200), `serveur_dovecot` + `serveur_postfix` (flux courriel
|
||
Postfix→LDAP→LMTP→Dovecot **livré de bout en bout**). Piège appris : les *defaults* d'un rôle inclus
|
||
ne persistent pas hors de son exécution — publier via `set_fact`.
|
||
- **Binding annuaire complété — `icingaweb2` + `client_ldap` migrés vers `resoudre_annuaire`.** Fin des
|
||
2 loose ends : `serveur_icingaweb2` (connexion LDAP dormante en mode SSO) résout via
|
||
`resoudre_annuaire` (redéploiement `changed=0`, SSO intact) ; `client_ldap` (SSSD, dormant) ne pointe
|
||
plus sur un `idm-01` périmé. **Plus AUCUN rôle ne code en dur l'hôte d'annuaire** — un seul point de
|
||
vérité (`resoudre_annuaire`).
|
||
- **Rôle `serveur_oauth2_proxy` — passerelle SSO OIDC générique (Keycloak) + Icinga Web 2 au SSO.**
|
||
oauth2-proxy (v7.15.3, binaire GitHub) place Keycloak **devant** n'importe quelle app sans OIDC
|
||
natif : elle reçoit l'utilisateur authentifié via en-tête, en auth `external`. Rôle paramétrable
|
||
(client, secret voûte, redirect, upstream, cookie voûte) — **réutilisable** pour toute app
|
||
OIDC-less. Éprouvé sur `sup-01` **devant Icinga Web 2** : client Keycloak `icingaweb2`,
|
||
oauth2-proxy `:4180` (exposé par l'edge) → upstream nginx local `:8080` → icingaweb2
|
||
`backend = external` (REMOTE_USER depuis `X-Forwarded-Preferred-Username`). **Prouvé** (flux
|
||
authorization code headless) : `testmail` → oauth2-proxy → Keycloak → **icingaweb2 `/dashboard`,
|
||
connecté** (« Se connecter avec Chezlepro », comme Grafana/Forgejo). Ferme le gap LDAP-direct
|
||
d'icingaweb2. **Réglages appris** : `insecure_oidc_allow_unverified_email` (les users LDAP n'ont
|
||
pas `email_verified` ; IdP interne de confiance) ; en reverse-proxy oauth2-proxy passe
|
||
`X-Forwarded-*` (pas `X-Auth-Request-*`) ; **handler nginx en `restart` (pas `reload`)** car un
|
||
changement d'adresse d'écoute n'est pas pris par un reload gracieux.
|
||
- **Rôle `serveur_icingaweb2` — Icinga Web 2 (UI native) + module IcingaDB : éprouvé.** App PHP
|
||
(php8.4-fpm) servie par un **nginx local**, exposée par l'edge (`icinga.lab.chezlepro.internal`,
|
||
auto-dérivé : vhost + cert SAN + A PowerDNS + alias plancher). Config **par fichiers `.ini`**
|
||
(config/resources/authentication/roles + module `icingadb`), pas d'assistant de setup. Base
|
||
IcingaDB via `resoudre_base` (registre). **Auth LDAP direct** vers OpenLDAP (LDAPS, `client_pki`
|
||
sur `sup-01`) — icingaweb2 n'a pas d'OIDC natif ; SSO-par-proxy = raffinement futur. **Prouvé** :
|
||
`testmail` (LDAP) se connecte (`/dashboard`), et le **module IcingaDB affiche la supervision**
|
||
(hôte `icinga`). Déploiement `failed=0` (le rôle est bon ; les frictions étaient dans le
|
||
simulateur de login curl : contrôle de cookie `_checkCookie`, champs `uid`/`submit_login`,
|
||
valeur CSRF avant `name`).
|
||
- **Module BPM (Business Process) éprouvé + codifié — pile Icinga complète.** `serveur_icingaweb2`
|
||
installe + active `icingaweb2-module-businessprocess` (`serveur_icingaweb2_modules`), crée le
|
||
répertoire des processus (éditable via l'UI, groupe `icingaweb2`, setgid) et **sème des processus
|
||
métier en IaC** (`serveur_icingaweb2_bpm_processes`, nom → contenu `.conf`). Format des feuilles
|
||
`host;service` (éprouvé via les fixtures du module). **Prouvé** : un processus « Supervision
|
||
Chezlepro » (agrège load/procs/swap/ping4/ssh du host `icinga` en logique ET) **rend un état**
|
||
dans l'UI (`testmail` connecté), **avec le backend IcingaDB** (pas d'IDO). Rôle re-prouvé (reset
|
||
→ recrée le processus, idempotent). BPM n'est **pas remplaçable par Grafana** (roll-up d'impact
|
||
métier). Pile Icinga = **moteur + Web 2 + BPM**, complète.
|
||
- **Cœur Icinga éprouvé (supervision active).** `serveur_icinga` (cœur : `icinga2` + `icingadb` +
|
||
`icingadb-redis`) déployé sur `sup-01` (🔧→⭐), base `icingadb` PostgreSQL via le registre.
|
||
**Prouvé** : 3 services actifs, et le moteur **supervise** — IcingaDB peuplée (1 hôte, 12
|
||
services, résultats de checks persistés en base). Zéro bug de déploiement (rôle bien bâti).
|
||
**Icinga Web 2 + module BPM restent différés** (phases dédiées : UI native + vues d'impact
|
||
métier ; le BPM n'est pas remplaçable par Grafana). Confirme aussi le **DRY `resoudre_base`**
|
||
sur icinga (les 3 rôles consommateurs validés).
|
||
- **Forgejo branché au SSO OIDC (« Se connecter avec Chezlepro ») — 2e app SSO, prouvée.**
|
||
`serveur_forgejo` (10.0.0) éprouvé sur `forge-01` (🔧→⭐), adossé à PostgreSQL (base `forgejo`
|
||
auto-provisionnée depuis le registre), exposé par l'edge (vhost + cert SAN + A PowerDNS + alias
|
||
plancher, tout auto-dérivé de `expose`). **Source OAuth2** vers Keycloak
|
||
(`forgejo admin auth add-oauth`, idempotent, realm `chezlepro`), client OIDC `forgejo`
|
||
enregistré via `serveur_keycloak_clients`. Auto-enregistrement OIDC (`[oauth2_client]
|
||
ENABLE_AUTO_REGISTRATION` + `ALLOW_ONLY_EXTERNAL_REGISTRATION` : identités depuis l'annuaire
|
||
seulement). **Prouvé** (flux authorization code headless) : `testmail` (LDAP) se connecte,
|
||
**compte auto-créé** (`testmail@lab.chezlepro.internal`), atterrit sur le tableau de bord.
|
||
**5 bugs de 1er déploiement corrigés** : dépendance périmée `serveur_sendmail`→`serveur_postfix`
|
||
(le vrai MTA) ; `app.ini` doit appartenir au user `git` (Forgejo persiste des secrets générés) ;
|
||
ordre admin/migrations (`flush_handlers` + `wait_for` avant `admin user create`) ; `HTTP_ADDR`
|
||
`127.0.0.1`→`0.0.0.0` (l'edge nginx est sur un autre hôte, 502 sinon) ; auto-enregistrement OIDC.
|
||
- **DRY : rôle utilitaire partagé `resoudre_base` (résolution BD depuis le registre).** Le bloc
|
||
copié-collé dans `serveur_keycloak`, `serveur_forgejo` et `serveur_icinga` (charger le registre,
|
||
filtrer par consommateur, déréférencer le secret via `lookup('vars', ...)`, résoudre hôte/port)
|
||
est extrait dans `roles/resoudre_base` (facts `resoudre_base_entree/db_password/db_host/db_port`,
|
||
`no_log`). Les 3 rôles l'incluent (`include_role`) et adoptent les facts. Le secret **ne quitte
|
||
toujours pas le rôle** (déréférencé au déploiement). **Fait « sur la preuve »** : re-déploiement
|
||
keycloak + forgejo `failed=0`, idempotent, `testmail` token Keycloak HTTP 200. Ferme le reste
|
||
noté de la Phase 2 des bindings (cf. `docs/bindings-conception.md`).
|
||
- **PowerDNS — A d'exposition auto-dérivés (le DNS de la Phase 3 des bindings).** `serveur_powerdns`
|
||
génère désormais, dans la zone interne, un enregistrement A pour chaque FQDN d'exposition (champ
|
||
`expose` des applications) vers l'**edge qui le sert** (`domaines.edge`) — via
|
||
`expositions_des_applications` (même source que les vhosts nginx et les SANs du cert edge).
|
||
Déclarer `expose` produit maintenant **vhost + SAN de cert + enregistrement DNS**, tout dérivé.
|
||
**Prouvé** : `dig @infra-dns-01 grafana.lab.chezlepro.internal` et `keycloak.…` → `192.168.15.21`
|
||
(edge). Option `serveur_powerdns_publier_expositions` (défaut true). **Limite / reste** : PowerDNS
|
||
est **autoritatif, pas récursif** — pour que les nœuds *utilisent* ces A sans casser la résolution
|
||
Internet, il faut un **récursif** (pdns-recursor : forward de la zone interne + récursion du reste)
|
||
ou garder le plancher `/etc/hosts`. Ne PAS repointer naïvement `client_dns` vers l'autoritatif.
|
||
- **`hosts_statiques` — alias d'exposition dans le plancher `/etc/hosts` (résolution client, sûre).**
|
||
Le plancher pose désormais, sur **chaque nœud**, `<IP edge> <FQDN exposé>` pour chaque `expose`
|
||
(dérivé de `domaines.edge`, même source que nginx/PowerDNS). Indépendant du DNS, aucun risque de
|
||
couper la résolution (choix retenu vs pdns-recursor). Chargement du plan **best-effort**
|
||
(`stat` **`delegate_to: localhost`** + `become: false` — les registres vivent sur le nœud de
|
||
contrôle ; ignoré si le plan est absent, ex. préparation du template). **Prouvé** : `/etc/hosts`
|
||
d'obs-01 régénéré avec `keycloak`/`grafana` → edge (ligne manuelle éliminée), `getent` OK, et le
|
||
**flux SSO Grafana fonctionne via la résolution du plancher** (`login: testmail`). Boucle Phase 3
|
||
fermée : déclarer `expose` → **vhost + SAN cert + A PowerDNS + alias plancher**, tout dérivé.
|
||
Bugs corrigés en chemin : `serveur_loki` (groupe `loki` manquant), `stat` sur cible→contrôle,
|
||
`become` inutile sur le contrôle.
|
||
- **Rôle `client_unbound` — résolveur local (DNS dynamique) : éprouvé sur un nœud.** Unbound par
|
||
nœud (`127.0.0.1`) avec **stub-zone** vers l'autoritatif interne (PowerDNS) + **récursion**
|
||
Internet (ou forward via `client_unbound_transitaires`). Alternative *dynamique* au plancher
|
||
`/etc/hosts` statique, sans casser Internet. Bascule de `/etc/resolv.conf` **protégée**
|
||
(`client_unbound_apply` + `client_unbound_confirm`) **et validée AVANT** (Unbound doit résoudre
|
||
interne + Internet, sinon pas de bascule → nœud jamais coupé). **Prouvé sur data-sql-01** (rayon
|
||
d'impact minimal) : `dig @127.0.0.1 keycloak/id-sso-01.lab.chezlepro.internal` → PowerDNS,
|
||
`deb.debian.org` → récursion, `apt` OK. Rôle sûr par défaut (`apply: false` : installe Unbound
|
||
sans toucher au resolver). Rollout flotte = opt-in par nœud. **Note direction** : OPNsense
|
||
embarque Unbound → à terme, l'Unbound *réseau* peut vivre sur l'appliance de bordure (nœud
|
||
public, Étape B) ; le rôle par-nœud reste portable et complémentaire (cache local).
|
||
- **Bindings — Phase 1 : résolveur de liens dans `instancier.py` (relations service→service
|
||
déclaratives).** Une application déclare ses `liens: [{vers, role}]` dans
|
||
`plan/applications.yml` ; chaque rôle décrit les liens qu'il accepte dans `meta/liens.yml`
|
||
(`setops_liens.accepte`, comme `meta/empreinte.yml`). `instancier` résout la cible (FQDN interne
|
||
**dérivé de la nomenclature** + `domaine_interne`), substitue les gabarits (`{cible.fqdn}`,
|
||
`{cible.hote}`, `{cible.ip}`) et **injecte les variables en host_vars du consommateur**.
|
||
Validation : rôle accepteur, cible existante, genre attendu. **Migration prouvée** : les liens
|
||
mail Postfix→Dovecot (`mailstore`) et Postfix→rspamd (`milter`) passent de group_vars codés en
|
||
dur à des liens déclaratifs — `make instancier` donne **DIFF VIDE** (mêmes variables générées),
|
||
puis les group_vars sont retirés. La topologie mail devient déclarative et portable.
|
||
Cf. `docs/bindings-conception.md`. Suite : bases (Phase 2), exposition/domaines (Phase 3), GUI (Phase 4).
|
||
- **Bindings — Phase 2 (bases) : constat + réconciliation de la note (`docs/bindings-conception.md` §5/§9).**
|
||
Inspection du code réel : le binding app→base **existe déjà** — **côté base** (`consommateur`/`portee`
|
||
dans `bases-donnees.yml`), résolu **dans le rôle** au déploiement (`include_vars` + filtre +
|
||
`lookup('vars', secret)`), sur 4 rôles (postgresql, forgejo, keycloak, icinga). **Délibérément
|
||
conservé** (le secret ne quitte jamais le rôle) — ne PAS dupliquer en app-side/instancier. Deux
|
||
directions assumées : app→app côté app (instancier), app→base côté base (registre). Reste (reporté
|
||
à l'épreuve de Keycloak) : factoriser le bloc de résolution copié-collé en include partagé (DRY).
|
||
- **`docs/carte-set-ops.md` — carte d'orientation (index + mécanismes transverses).** Après audit
|
||
du dépôt : point d'entrée « à lire d'abord » (index du corpus, ~22 docs), et **catalogue des
|
||
mécanismes** dispersés dans le code (les 2 directions de binding, pont de cert, résolution BD
|
||
par registre, socle-first, sûreté check-mode, voûte, dimensionnement) avec **où ils vivent**.
|
||
But : ne plus re-découvrir l'existant. Constat : la **cruft était déjà inventoriée** dans
|
||
`catalogue-services.md` (rôles-catégories inertes, échafaudages) — non dupliquée, référencée.
|
||
`catalogue-services.md` « État d'implémentation » **rafraîchi** (rôles éprouvés sur VM réelles :
|
||
socle, PKI, LDAP, DNS, nginx, pile courriel). Pointeur ajouté depuis `architecture-set-ops.md`.
|
||
- **`serveur_postgresql` et `serveur_keycloak` éprouvés sur VM réelles (🔧→⭐).** PostgreSQL
|
||
déployé (data-sql-01), écoute réseau + pg_hba VLAN, et **provisionne la base `keycloak` depuis
|
||
le registre** (`bases-donnees.yml`) — **binding app→base prouvé en réel** (base + rôle créés,
|
||
mot de passe = `vault_bd_keycloak`). Keycloak 26.0.7 déployé (id-sso-01), **mode prod**,
|
||
connecté à PostgreSQL (**87 tables** du realm master écrites), **token admin obtenu** (auth
|
||
adossée à la BD). Lacunes connues (documentées `catalogue-services.md`) : fédération LDAP et
|
||
edge nginx pas encore câblés. Reste : DRY du bloc de résolution BD (keycloak/forgejo/icinga).
|
||
- **`serveur_keycloak` — fédération LDAP (modèle d'identité A) : automatisée et prouvée.** Le rôle
|
||
configure, via `kcadm` (idempotent), un realm applicatif (`serveur_keycloak_realm`, déf.
|
||
`chezlepro`) et un **provider de stockage LDAP READ_ONLY** vers OpenLDAP (LDAPS, `uid`/`entryUUID`,
|
||
`inetOrgPerson`). TLS LDAPS validé via le **truststore système** (`truststore-paths` →
|
||
`/etc/ssl/certs/ca-certificates.crt`, racine step_ca posée par **client_pki**, désormais requis
|
||
sur le nœud). Secrets par `environment` + `no_log`. **Éprouvé avant codification** puis prouvé
|
||
par le rôle : un utilisateur LDAP (`testmail`) obtient un token via le realm (HTTP 200), et le
|
||
redéploiement est **idempotent** (`changed=0`). Nouveau : `tasks/federation-ldap.yml`. Reste :
|
||
edge nginx (accès HTTPS par nom), mappers d'attributs/groupes fins.
|
||
- **Edge nginx + exposition (Phase 3 des bindings) — prouvés avec Keycloak.** Sans changement de
|
||
code : la machinerie `serveur_nginx_publier_expositions` **existait déjà** (lit `expose` des
|
||
applications + `edge` de `domaines.yml`, dérive `amont = http://<IP hôte>:<port>`, génère le
|
||
vhost avec `X-Forwarded-*`). Le bac à sable déclare `keycloak.expose:
|
||
[keycloak.lab.chezlepro.internal]` + le domaine interne `lab.chezlepro.internal` (edge
|
||
`serveur_nginx`). **Prouvé** : le vhost s'auto-génère (`keycloak.lab.chezlepro.internal →
|
||
http://192.168.15.81:8080`), et la découverte OIDC via l'edge renvoie
|
||
`"issuer":"https://keycloak.lab.chezlepro.internal/..."` (les `X-Forwarded` passent, Keycloak
|
||
se sait derrière HTTPS). **Limite connue** : le cert TLS de l'edge est encore le snakeoil
|
||
auto-signé (avertissement navigateur). **Raffinement recommandé** (réutilise l'existant, pas de
|
||
nouveau mécanisme) : ajouter les FQDN d'exposition aux `client_pki_sans` de l'edge (client_pki
|
||
demande + renouvelle déjà le cert d'hôte), puis pointer `serveur_nginx_certificat` sur le cert
|
||
client_pki (`/etc/step/certs/<edge>.crt`).
|
||
- **Cert de l'edge : snakeoil → step_ca (HTTPS valide).** Appliqué le raffinement ci-dessus :
|
||
`client_pki` ajouté à l'edge, ses `client_pki_sans` incluent le FQDN d'exposition
|
||
(`keycloak.lab.chezlepro.internal`), et `serveur_nginx_certificat`/`_cle` pointent sur le cert
|
||
client_pki. **Prouvé** : HTTPS `HTTP 200` avec `ssl_verify_result=0` (chaîne validée contre la
|
||
racine step_ca, nom correct), émetteur `Set-OPS Internal CA`. Sans nouveau code (client_pki +
|
||
group_var). **Gaps notés** : (1) recharger nginx au **renouvellement** du cert (le cert-renewer
|
||
renouvelle en place, nginx ne recharge pas seul — hook à ajouter) ; (2) **auto-dériver** les SANs
|
||
d'exposition de l'edge depuis le plan (au lieu de les lister dans le group_var).
|
||
- **Grafana branché au SSO OIDC (« Se connecter avec Chezlepro ») — prouvé de bout en bout.**
|
||
`serveur_grafana` : config OIDC via `GF_AUTH_GENERIC_OAUTH_*` (client confidentiel `grafana`,
|
||
realm `chezlepro`, secret `vault_grafana_oidc`). `serveur_loki` + `serveur_prometheus` +
|
||
`serveur_grafana` déployés sur `obs-01` (🔧→⭐). **Bug de rôle corrigé** : `serveur_loki` créait
|
||
le répertoire en `group: loki` alors que le paquet crée l'utilisateur en `nogroup` sans groupe
|
||
`loki` → ajout de la création du groupe. **Prouvé (flux authorization code headless, via l'edge
|
||
HTTPS)** : `testmail` (user LDAP) se connecte à Grafana par le SSO — `/api/user` renvoie
|
||
`login: testmail`, email et nom **fédérés depuis LDAP**. Chaîne complète LDAP → Keycloak →
|
||
Grafana. **Gaps notés (pour rendre 100 % déclaratif)** : (1) l'**enregistrement du client OIDC**
|
||
dans Keycloak a été fait via `kcadm` **à la main** (à codifier — rôle grafana ou liste de clients
|
||
côté keycloak) ; (2) la **résolution** `keycloak.…internal → edge` sur obs-01 est un `/etc/hosts`
|
||
manuel (**PowerDNS devrait porter les A d'exposition** — chaînon récurrent) ; (3) mapping de
|
||
rôles Grafana (tous Viewer par défaut).
|
||
- **`serveur_keycloak` — enregistrement des clients OIDC codifié (gap précédent fermé).** Le rôle
|
||
gère une **liste déclarative** `serveur_keycloak_clients` (`clientId`, `redirect_uris`,
|
||
`web_origins`, `secret`) et enregistre chaque client confidentiel via **kcadm idempotent**
|
||
(`tasks/clients-oidc.yml`, create-si-absent, `no_log`). Décision : **côté Keycloak** (les creds
|
||
admin restent dans le seul rôle Keycloak, pas répandus dans chaque rôle app) ; le `secret`
|
||
référence la même variable de voûte que l'app. **Prouvé** : client `grafana` supprimé → rôle →
|
||
recréé → `testmail` se connecte à Grafana (`login: testmail`) ; redéploiement **idempotent**
|
||
(`changed=0`). Le déploiement de Grafana au SSO est désormais **autonome**.
|
||
|
||
## 2026-07-02
|
||
|
||
### Décidé
|
||
- **Bindings — conception des relations app/base/serveur/domaine (`docs/bindings-conception.md`).**
|
||
Les relations service→service sont aujourd'hui codées en dur, éparpillées dans des group_vars
|
||
(ex. Postfix→Dovecot/rspamd/LDAP), ce qui casse la portabilité multi-tenant. Direction retenue :
|
||
**liens déclarés côté application** (`liens: [{vers, role}]`), résolus par `instancier.py` en
|
||
variables Ansible ; chaque rôle décrit les liens qu'il accepte dans `meta/liens.yml` (comme
|
||
`meta/empreinte.yml`) ; FQDN cible **dérivé de la nomenclature** (jamais codé en dur). Domaines
|
||
publics traités comme lien `exposition` (écrit sur l'edge). Réconcilie l'existant (bases
|
||
`consommateur`, `domaines.edge`). Preuve de migration ciblée : les 3 liens mail. Implémentation
|
||
à suivre (phasée).
|
||
- **Licence : passage de CC BY-NC-SA 4.0 à AGPLv3.** Les licences Creative Commons ne sont pas
|
||
faites pour du logiciel (position de CC elle-même) et la clause **NonCommercial contredisait
|
||
le principe fondateur « tout est libre »** — en plus de bloquer les artisans/coopératives
|
||
visés. `LICENSE` remplacé par le **texte officiel intégral de l'AGPLv3** (verbatim, non
|
||
modifié). Attribution + modèle **libre + services/certification** documentés dans le `README`
|
||
(méthode d'attribution recommandée, sans toucher au texte de la licence). L'AGPLv3 protège la
|
||
souveraineté (anti-captation propriétaire en SaaS) **sans interdire l'usage commercial**.
|
||
- **Architecture d'identité/SSO (`docs/identite-sso.md`).** Modèle A : **OpenLDAP source de
|
||
vérité**, **Keycloak fédéré** (SSO web OIDC, MFA, self-service), **mail en bind LDAP
|
||
direct**. Une identité, un mot de passe, deux chemins d'auth (web→Keycloak, mail→LDAP),
|
||
tout adossé au même OpenLDAP. Sert la portabilité multi-tenant (chaque tenant = son LDAP +
|
||
son Keycloak fédéré). Pas Keycloak-source (casse-tête mail + moins portable).
|
||
- **Service courriel : pivot de Stalwart vers Postfix + Dovecot + rspamd.** La Phase 1
|
||
Stalwart (`serveur_stalwart`) avait été prototypée et déployée (v0.16.11, install +
|
||
démarrage en mode récupération). Le prototypage a révélé un projet **trop jeune/volatil
|
||
pour un pilier mail critique** : config cassée entre 0.15 et 0.16, outil IaC
|
||
`stalwart config apply` **annoncé mais non livré** dans le binaire, API REST supprimée
|
||
(JMAP), gros backlog. Pivot vers la stack **mature Postfix/Dovecot/rspamd**, en prime
|
||
**100 % configurable par fichiers** (alignée au modèle déclaratif Set-OPS). Le rôle
|
||
`serveur_stalwart` est **retiré** (git en garde la trace) ; `docs/courriel-conception.md`
|
||
mis à jour. Réévaluer Stalwart ~2028.
|
||
|
||
### Ajouté
|
||
- **`docs/pouvoirs-set-ops.md` — bilan des capacités du moteur.** Inventaire structuré
|
||
(moteur/plan, plan de contrôle GUI, socle durci, piliers d'infrastructure, services outillés,
|
||
patrons d'ingénierie), distinguant honnêtement « prouvé sur cluster réel » de « outillé ».
|
||
- **Rôle `serveur_rspamd` (rspamd 3.x) — antispam + DKIM, en milter sur Postfix.** Installé
|
||
sur le nœud edge-mta (avec Postfix), backend **Redis** local, worker proxy en **mode milter
|
||
auto-scan** (`:11332`), **signature DKIM** sortante (clé générée par le rôle de façon
|
||
idempotente ; enregistrement DNS public affiché pour l'Étape B). Config par surcharges
|
||
`/etc/rspamd/local.d/`. Postfix branché via `smtpd_milters` (option `serveur_postfix_rspamd_milter`,
|
||
`milter_default_action = accept` → tolérant si rspamd indisponible). **Prouvé** : un courriel
|
||
traversant le milter ressort **scanné** (`rspamc stat` : 1) et **signé DKIM** (`DKIM-Signature:
|
||
d=…`), puis livré et lu en IMAP. **Étape A (courriel interne) complète** : dovecot + postfix + rspamd.
|
||
- **Flux courriel interne PROUVÉ de bout en bout (Étape A).** Envoi → Postfix (`edge-mta`,
|
||
validation LDAP) → LMTP réseau → Dovecot (mail-store) → boîte Maildir → **lu en IMAP**
|
||
(auth LDAP, TLS step_ca) : `status=sent`, message lu (sujet + corps). Réglages Dovecot 2.4
|
||
qui débloquent la remise LMTP : `userdb static { static_allow_all_users = yes }` (sinon
|
||
NOTFOUND pour l'expéditeur/raw-mail-user externe), `mail_inbox_path =` vidé (le défaut mbox
|
||
`/var/mail` root refusait l'autocréation de l'INBOX), Maildir explicite (`mail_home` +
|
||
`mail_path = %{home}/Maildir`), chemin par nom d'utilisateur (home identique côté LMTP
|
||
local-part et IMAP adresse complète ; mono-domaine, multi-domaine = raffinement Étape B).
|
||
- **Rôle `serveur_postfix` (Postfix 3.x) — MTA du nœud edge-mta.** Réception `:25`, cartes
|
||
**LDAP** (validation des boîtes via l'attribut `mail`), remise **LMTP réseau** vers le nœud
|
||
mail-store Dovecot (`virtual_transport = lmtp:inet:[…]:24`), TLS via **step_ca** (pont de
|
||
cert), aucune boîte locale. Config `main.cf` + carte `ldap-mailboxes.cf`, validée par
|
||
`postfix check`. Secret de bind : `vault_openldap_admin`. Nécessite `serveur_postfix_mailstore_hote`
|
||
(FQDN du mail-store). Validé statiquement ; déploiement réel à suivre.
|
||
- **Rôle `serveur_dovecot` (Dovecot 2.4) — déployé et prouvé.** IMAP `:993`/`:143` +
|
||
LMTP, **auth/annuaire LDAP** (vers `serveur_openldap`, filtre `mail`), stockage Maildir
|
||
(user système `vmail`), **TLS via step_ca** (pont de cert + resync au renouvellement),
|
||
neutralisation de l'auth système par défaut. Config en drop-in **syntaxe Dovecot 2.4**
|
||
(`mail_driver`, `ssl_server_cert_file`, `passdb ldap`/`userdb static`, `%{user}`),
|
||
**validée par `doveconf`** au déploiement. Sockets d'intégration Postfix **conditionnels**
|
||
(rendus si l'utilisateur `postfix` est co-localisé). Prouvé : `doveadm auth test` — bon
|
||
mot de passe accepté, mauvais refusé, sur cert step_ca. Secret de bind : `vault_openldap_admin`.
|
||
- **`serveur_openldap` durci pour la prod : TLS via step_ca + organisation en intrant.**
|
||
- **TLS (LDAPS + STARTTLS)** : le certificat d'hôte step_ca (déposé par `client_pki`,
|
||
`root:root 600`) est synchronisé vers un emplacement lisible par `openldap` (`/etc/ldap/tls`)
|
||
par un script + une unité `path` systemd qui **re-synchronise et recharge slapd à chaque
|
||
renouvellement** ; `olcTLS*` configuré dans `cn=config`, `SLAPD_SERVICES` expose `ldaps://`.
|
||
Dégrade proprement (slapd en clair local) si `client_pki` n'a pas encore posé le cert.
|
||
- **Organisation** : nouvel intrant `chezlepro_organisation` (remplace le « Exemple Inc » codé).
|
||
- *Écrit en code de prod, éprouvé statiquement ; validation par déploiement réel à suivre.*
|
||
- **`docs/courriel-conception.md`** — cadrage du futur service de courriel souverain :
|
||
full self-host, suite **Stalwart** (adoptée, enveloppée par un rôle mince), topologie
|
||
MX primaire + MX secours, intégration identité (LDAP) / DNS / PKI (Let's Encrypt public
|
||
vs step_ca interne), enregistrements DNS publics, décisions ouvertes (IP/PTR, secours,
|
||
stockage) et phasage. **Conception seulement — aucun rôle livré.**
|
||
|
||
## 2026-07-01
|
||
|
||
### Ajouté
|
||
- **État RÉEL vs plan dans le GUI (sonde de vie + auto-actif).** Le badge `planifié`/
|
||
`actif` décrit l'*intention* du plan, pas l'existence de la VM — d'où la confusion
|
||
« serveur planifié mais vivant ». Deux ajouts :
|
||
- **Sonde de vie** : le GUI teste la joignabilité SSH de chaque hôte en arrière-plan
|
||
(`/api/sondes`, en parallèle) et affiche un état réel — **● vivante** / **● injoignable**
|
||
— sur les tuiles et dans le détail (« Plan : … · Réel : … »), distinct du plan.
|
||
- **Auto-actif** : un hôte qu'on **matérialise** (clone `creer` réussi) ou qu'on
|
||
**déploie** passe automatiquement `actif` dans le plan (matérialisé = actif), puis
|
||
l'inventaire est **régénéré** pour que le changement se voie partout (en-tête inclus).
|
||
- **Compteur « vivantes »** dans l'en-tête (depuis la sonde), à côté de actifs/planifiés.
|
||
|
||
### Corrigé
|
||
- **nginx ne validait pas sur Debian 13 (`server_tokens` en double).** Debian 13 livre
|
||
`server_tokens off;` **actif** dans `/etc/nginx/nginx.conf` (avant : commenté). Le
|
||
drop-in `conf.d/99-setops.conf` du rôle le redéclarait → `nginx -t` échouait
|
||
(« directive is duplicate ») et le déploiement plantait au handler de validation. Le
|
||
rôle `serveur_nginx` neutralise désormais la ligne distro (le drop-in reste l'unique
|
||
source). Trouvé en déployant nginx pour de vrai sur un hôte edge.
|
||
- **Une voûte chiffrée cassait `instancier` / « Appliquer le plan ».** `ansible-inventory
|
||
--list` (utilisé pour la comparaison sémantique du plan) tente de déchiffrer
|
||
`group_vars/all/vault.yml` et échoue sans mot de passe (`exit 4`) — alors que
|
||
l'opération est structurelle, sans secret. `instancier` utilise désormais
|
||
automatiquement le fichier conventionnel `~/.config/setops-vault-pass` (si
|
||
`ANSIBLE_VAULT_PASSWORD_FILE` n'est pas déjà défini).
|
||
- **Secrets des rôles non câblés à la voûte (échafaudage manquant).** Les rôles à
|
||
secrets déclaraient `serveur_X_password: ""` avec, en commentaire seulement, la
|
||
variable de voûte attendue (`{{ vault_X }}`) — sans mapping réel. Résultat : remplir
|
||
la voûte selon `vault.exemple.yml` ne suffisait pas, le secret restait vide et
|
||
l'assertion « secrets requis » échouait. Les 12 secrets des 8 rôles (`serveur_step_ca`,
|
||
`serveur_forgejo`, `serveur_grafana`, `serveur_keycloak`, `serveur_openldap`,
|
||
`serveur_redis`, `client_ldap`, `client_pki`) pointent désormais vers leur variable
|
||
de voûte : `serveur_X_password: "{{ vault_X | default('') }}"`. Chaque instance n'a
|
||
plus qu'à remplir ses `vault_*` dans sa voûte chiffrée ; aucun mapping par instance.
|
||
- **« Vérifier » (dry-run `--check`) échouait faussement sur un hôte frais.** Les tâches
|
||
« démarrer service » et les handlers « redémarrer / recharger / valider » des rôles
|
||
applicatifs touchent un paquet que `--check` n'installe pas réellement → le service
|
||
(ou le fichier de zone/conf) n'existe pas encore → faux `fatal`, qui **bloquait le
|
||
déploiement** (le dry-run doit réussir pour débloquer « Déployer »). Ajout de
|
||
`when: not ansible_check_mode` sur ces tâches et handlers des 13 rôles `serveur_*`
|
||
(29 gardes). En dry-run elles sont sautées ; en vrai déploiement, inchangées.
|
||
- **Le bouton ⚙ « Appliquer le plan » du GUI refusait en silence** dès que le plan
|
||
divergeait de l'inventaire (il appelait `instancier appliquer` **sans** `--force`).
|
||
Résultat : après une édition (disque, état, auto-actif), l'inventaire n'était jamais
|
||
régénéré et les compteurs restaient figés. Le clic « Appliquer » **est** l'intention
|
||
explicite → le GUI force désormais (git reste le filet).
|
||
|
||
- **Bouton « Pousser » dans le GUI — le flux devient 100 % cliquable.** Chaque objet
|
||
du détail se matérialise sur son hôte, en streaming console (avec mot de passe vault
|
||
+ confirmation renforcée en prod) :
|
||
- **Serveur** → `🖥 Pousser` clone la VM depuis le golden template (`make creer-vm`),
|
||
disponible même sur un hôte planifié — comble le trou : le GUI ne clonait pas.
|
||
- **Application** → `Pousser` déploie l'hôte porteur (`make deployer HOTE=<hôte>`).
|
||
- **Base** → `Pousser` déploie l'hôte du serveur de BD (crée la base).
|
||
Nouveaux modes `creer` / `pousser` dans `executer_flux` + routes `/api/creer` et
|
||
`/api/pousser`. Flux complet depuis l'interface : éditer → ⚙ Appliquer → 🖥 Pousser
|
||
→ Vérifier → Déployer.
|
||
- **Premier déploiement RÉEL validé de bout en bout** (cluster asgard) : flux canonique
|
||
plan-piloté clonant + durcissant + faisant tourner un PowerDNS qui résout. Voir les 3
|
||
correctifs ci-dessous.
|
||
|
||
## 2026-06-30
|
||
|
||
### Ajouté
|
||
- **Plancher de résolution `/etc/hosts` (indépendant du DNS).** Nouveau rôle de socle
|
||
`hosts_statiques` (dans `serveur_debian`) : génère `/etc/hosts` sur **chaque** VM
|
||
depuis l'inventaire (nom + FQDN interne → IP réelle). Tout l'écosystème se résout
|
||
par nom **même serveur DNS éteint**, et le bootstrap ne dépend plus du DNS. PowerDNS
|
||
devient une **commodité** (zone/externe/dynamique) ; `client_dns` est rendu tolérant
|
||
(inerte si aucun DNS interne) et sa dépendance à `serveur_powerdns` passe **molle**.
|
||
- **Adressage fédéré : index d'instance.** Le VMID n'est plus codé `9CSNN` en dur :
|
||
il prend le préfixe d'un `index` déclaré en tête de `plan/nomenclature.yml`
|
||
(`{index}{catégorie}{service}{séq}`). Convention : `supernet = 10.(10+index).0.0/16`,
|
||
`VMID = index·CSNN`. Permet à N écosystèmes de **coexister/s'interconnecter** sans
|
||
collision (Chezlepro=1 → `10.11`/`1xxxx`, Technolibre=2 → `10.12`/`2xxxx`). Sans
|
||
index → `9CSNN` (rétro-compatible ; bacs à sable, plages ad-hoc `172.19.x`).
|
||
Code mort retiré (`deriveServeur` JS). Voir `docs/multi-instances.md`.
|
||
- **Doc `docs/multi-instances.md`** : cadrage « un moteur, N écosystèmes » — l'instance
|
||
comme dépôt autonome, la bascule, l'isolation, et le socle multi-tenant.
|
||
- **Bascule d'instance (`make instance-utiliser NOM=…`).** Repointe le symlink
|
||
`instance` vers un autre dépôt d'instance (prod ↔ bac à sable) ;
|
||
`make instance-courante` affiche l'instance montée. Permet d'exploiter plusieurs
|
||
instances (séparation **par instance**) depuis un seul moteur.
|
||
- **Identité des intrants relative à l'inventaire.** Le panneau « Intrants » lit/écrit
|
||
l'identité dans `group_vars/all/10-intrants.yml` de l'inventaire monté (fichier réel
|
||
pour une instance autonome, symlink vers une source partagée sinon — l'écriture suit
|
||
le symlink). Fonctionne donc aussi bien pour une instance « par instance » que pour
|
||
l'ancien partage lab/production.
|
||
- **Inventaire d'instance neutre et configurable (`principal` / `SETOPS_INVENTAIRE`).**
|
||
Le moteur (Makefile + `inventory_gui`, `instancier`, `config_proxmox`, `serveurs`,
|
||
`applications`) ne code plus en dur `inventories/lab` / `inventories/production` :
|
||
il vise **un inventaire par instance**, détecté de façon **rétro-compatible**
|
||
(`principal` > `production` > `lab`) et surchargeable par `SETOPS_INVENTAIRE`. Les
|
||
instances existantes (découpage lab/production) continuent de fonctionner à
|
||
l'identique ; les nouvelles peuvent adopter `inventories/principal/`. Deuxième pierre
|
||
de la séparation **par instance** (la 1re étant le drapeau `setops_production`).
|
||
- **Garde-fou de prudence par instance (`setops_production`).** Le déploiement réel
|
||
est désormais possible sur **toute instance** (un bac à sable déploie sur *son*
|
||
infra lab — il est isolé **et** fonctionnel). Le drapeau `setops_production` dans
|
||
`group_vars/all/` ne **bloque** plus rien : il marque la PRODUCTION pour exiger une
|
||
**confirmation renforcée** au déploiement (bannière/badge rouge « PROD », bouton
|
||
Déployer en rouge, re-saisie du nom d'hôte) ; un bac à sable (`false`) affiche
|
||
« bac à sable » et déploie sans cette étape. Rétro-compatible (à défaut de drapeau,
|
||
ancien repère « inventaire production »). Le GUI affiche toujours quel type
|
||
d'instance est monté.
|
||
|
||
### Modifié
|
||
- **Voûte de secrets unique par environnement.** Fini les voûtes éparpillées : tous
|
||
les secrets de l'instance (token Proxmox + 17 `vault_*` pour PKI, LDAP/SSO, bases,
|
||
forge, observabilité) vivent dans **un seul fichier chiffré**,
|
||
`inventories/<env>/group_vars/all/vault.yml`. Gabarit committé
|
||
`exemples/vault.exemple.yml`. `make config` (`config_proxmox.py`) écrit/édite
|
||
désormais cette voûte (semée depuis le gabarit si absente). `.gitignore` durci
|
||
(`**/vault.yml`). Docs mises à jour (config-proxmox.md avec étapes de migration,
|
||
intrants-communs.md §H, QUICKSTART). Le GUI ne stocke toujours aucun secret.
|
||
|
||
### Corrigé
|
||
- **Trois bugs trouvés au premier déploiement réel (cluster asgard).**
|
||
- **Clonage/Makefile codaient `inventories/lab` en dur** (config Proxmox + voûte) —
|
||
vestige du modèle env qui cassait les instances `principal`. Le playbook de clonage
|
||
et le Makefile détectent maintenant l'inventaire (`lab` > `principal` > `production`).
|
||
- **Redimensionnement disque non idempotent** : quand le disque dérivé du plan est
|
||
plus petit que le golden template, Proxmox refuse (`shrinking disks is not
|
||
supported`) et le clone échouait. Le resize est désormais **grow-only** (tolère le
|
||
cas, la VM garde le disque du template — le dérivé est un minimum).
|
||
- **PowerDNS refusait de démarrer** (`multiple backends 'bind'`) : le rôle
|
||
redéclarait `launch+=bind` que le paquet `pdns-backend-bind` pose déjà. Le rôle ne
|
||
déclare plus `launch` (seulement `bind-config`).
|
||
- **`chezlepro_timezone` n'était appliqué nulle part.** Cet intrant de base global
|
||
était défini mais aucun rôle ne s'en servait. Le rôle `chrony` (appliqué à tout
|
||
hôte via `serveur_debian`) règle désormais le fuseau horaire à partir de
|
||
`chezlepro_timezone` (`chrony_timezone` par défaut, vide = ne pas toucher). Les
|
||
autres défauts globaux (nœud / stockage / pont Proxmox) étaient déjà réutilisés
|
||
comme valeurs par défaut, surchargeables par hôte.
|
||
|
||
### Modifié
|
||
- **Détail GUI : section « Groupes (dérivés) » retirée (redondante).** Depuis la fusion
|
||
en atelier maître-détail, elle ne répétait que le socle (universel), les rôles des
|
||
applications (déjà dans « Applications ici ») et les intégrations (déjà cochées). Son
|
||
seul signal unique — le prérequis bloquant — est désormais nommé dans le pied
|
||
(« Prérequis manquant : … ») ; le panneau Dépendances reste pour la vue d'ensemble.
|
||
Code mort retiré (`renduGroupe`, `detailGroupe`, `titreGroupe`, CSS `.groupe*`).
|
||
- **Nettoyage CSS/HTML du GUI** après la refonte : retrait des règles et éléments
|
||
morts (`.message`, `.chips-filtre`/`.chip-f`, `.onglet*`, `.base-ligne`,
|
||
`.bases-liste`, `.ch-grp*`/`.ch-fleche`/`.ch-roles`, `.champ-val`, divs `#message`
|
||
et `#chips`).
|
||
- **GUI refondu en atelier maître-détail unifié.** Toutes les vues suivent le même
|
||
motif : tuiles à gauche, **détail + saisie à droite**, le panneau droit reflétant
|
||
la sélection de la vue courante (fin du panneau « figé » au changement de vue).
|
||
Les vues **Applications** et **Bases** passent de tableaux pleine largeur à ce
|
||
motif ; la saisie se fait dans le panneau droit, avec **liens cliquables** entre
|
||
objets (serveur → application → base). Le panneau droit est élargi (~38 %).
|
||
- **Fusion Serveur/Hôte.** Les vues « Inventaire » (hôtes, lecture seule) et
|
||
« Serveurs » (plan) faisaient doublon : elles sont fusionnées en **une seule vue
|
||
Serveurs**. Sa tuile porte le statut de réconciliation (réconcilié / divergent /
|
||
non instancié) ; son détail réunit l'**identité éditable** (plan), les **dérivés**
|
||
(VMID/IP/VLAN), les **groupes**, les **applications et bases hébergées**, et
|
||
**Vérifier/Déployer**. La navigation clavier (`1-3`, `j/k`, `v/d`, `/`) et le
|
||
filtre opèrent désormais sur les serveurs. Un bandeau « Comment lire ce parc »
|
||
explicite le modèle serveur·hôte·groupe·application·base. Code mort retiré
|
||
(`carte`, `renduChaine`, `ONGLETS`, `champLecture`, sélection d'hôte, etc.).
|
||
|
||
### Ajouté
|
||
- **Fluidité d'exploitation du GUI** (sans dépendance, stdlib pure) :
|
||
- **Notifications empilées (toasts)** auto-effaçables au lieu d'une bannière unique
|
||
écrasée ; les erreurs restent plus longtemps, les opérations longues mettent à
|
||
jour leur propre toast.
|
||
- **Durée d'exécution** affichée en direct dans la console (Vérifier / Déployer) et
|
||
suivi vivant de « Appliquer le plan ».
|
||
- **Navigation clavier** : `1-5` changent de vue, `j/k` parcourent les hôtes,
|
||
`v`/`d` vérifient/déploient l'hôte sélectionné, `/` cible le filtre, `Ctrl+S`
|
||
sauvegarde la vue éditable courante (ignorés pendant la saisie).
|
||
- **Validation inline** des champs du plan (vue Serveurs) : nom `fonction-NN`,
|
||
mémoire, cœurs, disque — liseré rouge et blocage avant l'envoi.
|
||
- **Garde-fou anti-perte** : confirmation `beforeunload` si des éditions de plan ne
|
||
sont pas sauvegardées.
|
||
- Le bouton **Sauvegarder** de l'en-tête devient contextuel (sauve la vue éditable,
|
||
désactivé en lecture seule) — fin de l'impasse 409 ; suppression du code mort.
|
||
|
||
### Ajouté
|
||
- **Vue Serveurs : champs alimentés par les paramètres globaux.** À `+ Serveur`,
|
||
**Nœud** et **Stockage** deviennent des listes déroulantes (catalogues
|
||
`proxmox_noeuds` / `proxmox_stockages`, vide = défaut global) et **Intégrations**
|
||
une rangée de cases à cocher des rôles `client_*` disponibles (au lieu d'une saisie
|
||
texte). Les catalogues Nœuds / Stockages / Ponts sont de nouveaux intrants
|
||
(classe « Catalogue ») éditables dans le panneau « Intrants de base » et stockés
|
||
dans `group_vars/proxmox.yml` ; l'API expose `integrations_disponibles` (scan de
|
||
`roles/client_*`). Le type `liste` (chaîne virgulée → liste YAML dédoublonnée) est
|
||
ajouté au schéma des intrants.
|
||
|
||
### Supprimé
|
||
- **Vue « Chaîne » du bandeau (redondante).** Son contenu (`renduChaine`) était déjà
|
||
rendu, par hôte, dans l'onglet « Chaîne » du détail de la vue Inventaire ; la vue ne
|
||
faisait que le répéter pour tous les hôtes à la fois, sans réseau/proxmox ni boutons
|
||
d'opération. Retirée (bouton, rendu `dessinerArbre`, CSS `.arbre-*`) ; la chaîne
|
||
reste consultable hôte par hôte. Raccourcis clavier ramenés à 1-4.
|
||
|
||
### Modifié
|
||
- **Vue Inventaire (cartes) : détail converti en inspection lecture seule.** La vue
|
||
affichait « généré depuis le plan (lecture seule) » tout en exposant une surface
|
||
d'édition (+ Hôte, ✕, bascule actif/planifié, ✨ Proposer, champs et cases de
|
||
groupes éditables) qui ne pouvait pas être persistée (l'inventaire est généré ;
|
||
`/api/inventaire` renvoie 409). Ces contrôles orphelins sont retirés : nom, champs
|
||
Réseau/Proxmox et groupes en lecture seule, état affiché en badge. **Vérifier /
|
||
Déployer** et les onglets d'inspection sont conservés. L'édition reste dans les vues
|
||
**Serveurs / Applications / Bases**. Code mort supprimé (`ajouterHote`,
|
||
`supprimerHote`, `definir`, `definirNom`, `definirEtat`, `basculerGroupe`,
|
||
`proposer`, `autoProposer`, `prochainSeqLibre`, `marquerModifie`, état `modifie`).
|
||
- **Référence des paramètres de `make config`** (`docs/config-proxmox.md`).
|
||
Explique un par un les 16 paramètres Proxmox non sensibles + les secrets API
|
||
demandés par l'assistant : sens, valeur par défaut, quoi saisir, et quels champs
|
||
sont des constantes vs des défauts surchargeables par hôte. Renvois ajoutés depuis
|
||
`make help` et `QUICKSTART.md`. Comble un trou : ces invites n'étaient expliquées
|
||
nulle part de façon pérenne (impératif « exploitable sans IA »).
|
||
- **Panneau « Intrants de base » dans le GUI.** Un bouton ⚙ Intrants ouvre une fenêtre
|
||
unique pour saisir les valeurs communes à tout l'écosystème, avec une distinction
|
||
visible entre **constantes** (valeur unique, non surchargeable) et **défauts**
|
||
(valeurs proposées, surchargeables dans les instances). Les secrets ne sont **jamais**
|
||
saisis ni affichés ici (garde-fou `INTRANTS_CLES_INTERDITES` + filtrage par schéma) :
|
||
ils restent dans le Vault Ansible, édités en CLI ; le panneau les liste seulement à
|
||
titre informatif. Un changement de `domaine_interne` (clé de voûte) demande une
|
||
confirmation explicite ; un `domaine_interne` vide est refusé côté serveur. La
|
||
nomenclature reste en lecture seule (modifiable dans le plan). Voir
|
||
`docs/intrants-communs.md` et `docs/intrants-base-gui-conception.md`.
|
||
- **Source unique d'identité partagée.** `domaine_interne` et `chezlepro_timezone`
|
||
vivent désormais dans `inventories/partage/intrants-identite.yml`, référencé par
|
||
symlink depuis chaque environnement (`group_vars/all/10-intrants.yml`) — fin de la
|
||
duplication lab/production.
|
||
- **Info-bulles d'aide sur les champs du GUI.** Survoler la description d'un champ à
|
||
saisir affiche une bulle avec des instructions sommaires.
|
||
|
||
## 2026-06-28
|
||
|
||
### Ajouté
|
||
- **Document de présentation de l'écosystème Chezlepro** (`docs/ecosysteme-chezlepro.md`).
|
||
Description vulgarisée à destination client/partenaire : les piliers de l'écosystème
|
||
souverain, le modèle reproductible (plan → génération → clonage → conformité), et un
|
||
inventaire des **mesures de renforcement** réellement en place (SSH durci, fail2ban,
|
||
sysctl, nftables, AppArmor, auditd, mises à jour automatiques, gestion des secrets,
|
||
garde-fous destructifs, sécurité par l'architecture). Honnête sur le statut
|
||
« défini/validé vs déployé ».
|
||
|
||
### Ajouté
|
||
- **Dimensionnement dérivé des ressources VM.** Les cœurs/RAM/disque d'une VM sont
|
||
désormais **estimés depuis les logiciels hébergés + le socle SE**, au lieu d'hériter
|
||
des specs du golden template (CPU/RAM identiques pour toutes les VM auparavant).
|
||
Chaque rôle déclare son empreinte (`roles/<rôle>/meta/empreinte.yml`) ; le
|
||
générateur somme par hôte (marge + arrondis) et écrit `proxmox_coeurs`/
|
||
`proxmox_memoire`/`proxmox_disque_taille` ; le clonage passe `cores`/`memory` à
|
||
Proxmox (`omit` si absent → aucune régression). Override par hôte possible dans le
|
||
plan (`serveurs.yml`). Voir `docs/dimensionnement-ressources.md`.
|
||
|
||
### Corrigé
|
||
- **`make instancier-appliquer FORCE=1` n'honorait pas `--force`.** La recette Makefile lançait `instancier.py appliquer` sans relayer `FORCE` ; le message « Utilise FORCE=1 » était donc trompeur (un changement de plan intentionnel restait bloqué). La recette passe désormais `$(if $(FORCE),--force)`. Trouvé en dogfooding.
|
||
|
||
## 2026-06-24 — Première publication publique
|
||
|
||
Première mise à disposition publique de **Set-OPS**, moteur Ansible d'écosystèmes
|
||
numériques souverains sur Proxmox — offert à la communauté québécoise par
|
||
l'**Alliance Boréale**, à la Saint-Jean-Baptiste 2026.
|
||
|
||
- **Moteur générique, piloté par un plan déclaratif** : on édite le plan
|
||
(`instance/plan/*.yml`), l'inventaire Ansible se génère, les VM se clonent depuis
|
||
un golden template Debian 13, les rôles s'appliquent par groupes. Tout passe par
|
||
`make`, le GUI local et la documentation.
|
||
- **Piliers d'un écosystème souverain** : socle Debian durci, AC/PKI interne, DNS
|
||
interne, identité (LDAP + SSO), relais courriel, bases de données, observabilité,
|
||
forge.
|
||
- **Catalogue de modèles prêts à déployer** (`exemples/modeles/`) : un hébergeur
|
||
copie un modèle, le renseigne à ses couleurs, et instancie.
|
||
- **Souveraineté jusqu'au bout** : Set-OPS s'exploite entièrement à la main, sans
|
||
aucune IA.
|
||
|
||
Pour démarrer : **`QUICKSTART.md`**.
|