Fin des 2 loose ends : icingaweb2 (LDAP dormant 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é. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
587 lines
48 KiB
Markdown
587 lines
48 KiB
Markdown
# CHANGELOG — Set-OPS
|
|
|
|
## 2026-07-03
|
|
|
|
### Ajouté
|
|
- **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`**.
|