2026-06-24 20:17:46 -04:00
# CHANGELOG — Set-OPS
2026-07-03 08:33:30 -04:00
## 2026-07-03
### Ajouté
2026-07-04 15:47:39 -04:00
- **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.
2026-07-04 11:56:51 -04:00
- **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).
2026-07-03 23:42:57 -04:00
- **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` .
2026-07-03 23:47:44 -04:00
**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).
2026-07-03 23:54:19 -04:00
**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).
2026-07-03 23:17:36 -04:00
- **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).
2026-07-03 22:49:26 -04:00
- **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
2026-07-03 23:00:51 -04:00
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).
2026-07-03 22:23:22 -04:00
- **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
2026-07-03 23:24:38 -04:00
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`).
2026-07-03 19:05:04 -04:00
- **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.
2026-07-03 16:57:04 -04:00
- **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`,
2026-07-03 17:04:34 -04:00
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.
2026-07-03 16:39:23 -04:00
- **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).
2026-07-03 15:52:38 -04:00
- **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.
2026-07-03 15:57:06 -04:00
- **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` ).
2026-07-03 13:47:29 -04:00
- **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.
2026-07-03 14:14:34 -04:00
- **`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.
2026-07-03 14:35:00 -04:00
- **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).
2026-07-03 08:33:30 -04:00
- **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).
2026-07-03 08:49:04 -04:00
- **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 d'orientation (index + mécanismes) + rafraîchir la maturité
Après audit du dépôt : docs/carte-set-ops.md = point d'entrée « à lire
d'abord » (index du corpus) + catalogue des mécanismes transverses (2
directions de binding, pont de cert, résolution BD par registre,
socle-first, check-mode, voûte) 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) — référencée, pas dupliquée.
catalogue-services « É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.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:16:14 -04:00
- **`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` .
2026-07-03 10:29:37 -04:00
- **`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).
2026-07-03 11:29:23 -04:00
- **`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.
2026-07-03 11:48:59 -04:00
- **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`).
2026-07-03 12:01:19 -04:00
- **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).
2026-07-03 13:15:52 -04:00
- **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).
2026-07-03 13:37:33 -04:00
- **`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-03 08:33:30 -04:00
2026-07-02 06:50:46 -04:00
## 2026-07-02
2026-07-02 12:52:49 -04:00
### Décidé
2026-07-03 05:29:24 -04:00
- **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).
2026-07-02 18:25:02 -04:00
- **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** .
2026-07-02 13:27:04 -04:00
- **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).
2026-07-02 12:52:49 -04:00
- **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.
2026-07-02 06:50:46 -04:00
### Ajouté
2026-07-02 18:09:40 -04:00
- **`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é ».
2026-07-02 18:04:44 -04:00
- **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.
2026-07-02 17:47:12 -04:00
- **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).
2026-07-02 15:34:24 -04:00
- **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.
2026-07-02 15:09:37 -04:00
- **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` .
2026-07-02 07:27:43 -04:00
- **`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.*
2026-07-02 06:50:46 -04:00
- **`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 15:58:32 -04:00
## 2026-07-01
### Ajouté
2026-07-01 17:39:44 -04:00
- **É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
2026-07-01 17:52:39 -04:00
**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é
2026-07-01 23:26:43 -04:00
- **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.
2026-07-01 22:17:45 -04:00
- **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).
2026-07-01 21:29:18 -04:00
- **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.
2026-07-01 21:01:53 -04:00
- **« 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.
2026-07-01 17:52:39 -04:00
- **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).
2026-07-01 17:39:44 -04:00
2026-07-01 15:58:32 -04:00
- **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 10:07:17 -04:00
## 2026-06-30
2026-06-30 15:59:37 -04:00
### Ajouté
2026-06-30 18:05:42 -04:00
- **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** .
2026-06-30 17:36:01 -04:00
- **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`
2026-06-30 17:47:00 -04:00
(`{index}{catégorie}{service}{séq}`). Convention : `supernet = 10.(10+index).0.0/16` ,
2026-06-30 17:36:01 -04:00
`VMID = index·CSNN` . Permet à N écosystèmes de **coexister/s'interconnecter** sans
2026-06-30 17:47:00 -04:00
collision (Chezlepro=1 → `10.11` /`1xxxx`, Technolibre=2 → `10.12` /`2xxxx`). Sans
2026-06-30 17:36:01 -04:00
index → `9CSNN` (rétro-compatible ; bacs à sable, plages ad-hoc `172.19.x` ).
Code mort retiré (`deriveServeur` JS). Voir `docs/multi-instances.md` .
2026-06-30 17:13:45 -04:00
- **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.
2026-06-30 16:31:42 -04:00
- **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.
2026-06-30 16:08:25 -04:00
- **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` ).
2026-06-30 16:20:18 -04:00
- **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é.
2026-06-30 15:59:37 -04:00
2026-06-30 15:42:12 -04:00
### 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.
2026-06-30 15:36:08 -04:00
### Corrigé
2026-07-01 15:38:42 -04:00
- **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` ).
2026-06-30 15:36:08 -04:00
- **`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.
GUI : atelier maître-détail unifié + fusion Serveur/Hôte
Toutes les vues adoptent le même motif que l'Inventaire : tuiles à
gauche, détail + saisie à droite, panneau droit contextuel (fin du
panneau figé au changement de vue). Applications et Bases passent en
maître-détail ; liens cliquables entre objets.
Les vues Inventaire (hôtes) et Serveurs (plan) faisaient doublon : elles
sont fusionnées en une vue Serveurs unique — tuile avec statut de
réconciliation, détail réunissant identité éditable, dérivés, groupes,
applications/bases hébergées et Vérifier/Déployer. Navigation clavier et
filtre rebranchés sur les serveurs ; bandeau « Comment lire ce parc ».
Code mort retiré (carte, renduChaine, ONGLETS, champLecture, sélection
d'hôte, chips de filtre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:22:11 -04:00
### Modifié
2026-06-30 19:01:39 -04:00
- **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*` ).
2026-06-30 15:36:08 -04:00
- **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 : atelier maître-détail unifié + fusion Serveur/Hôte
Toutes les vues adoptent le même motif que l'Inventaire : tuiles à
gauche, détail + saisie à droite, panneau droit contextuel (fin du
panneau figé au changement de vue). Applications et Bases passent en
maître-détail ; liens cliquables entre objets.
Les vues Inventaire (hôtes) et Serveurs (plan) faisaient doublon : elles
sont fusionnées en une vue Serveurs unique — tuile avec statut de
réconciliation, détail réunissant identité éditable, dérivés, groupes,
applications/bases hébergées et Vérifier/Déployer. Navigation clavier et
filtre rebranchés sur les serveurs ; bandeau « Comment lire ce parc ».
Code mort retiré (carte, renduChaine, ONGLETS, champLecture, sélection
d'hôte, chips de filtre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 15:22:11 -04:00
- **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.).
2026-06-30 10:07:17 -04:00
### Ajouté
2026-06-30 12:19:26 -04:00
- **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.
2026-06-30 12:36:51 -04:00
### 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.
2026-06-30 13:28:04 -04:00
### 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.
2026-06-30 12:19:26 -04:00
### 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 »).
2026-06-30 10:07:17 -04:00
- **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` .
2026-06-25 09:29:08 -04:00
### 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 20:17:46 -04:00
## 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` **.