Sur un trunk convergé (tenants partagent l'infra), les VLAN doivent être uniques GLOBALEMENT. deriver_nomenclature (ip-miroir) dérive VLAN = index×10 + zone : chaque tenant son bloc de 10, 1-9 réservés à l'infra partagée. Le VLAN contenant le tenant, le VMID mène avec le VLAN (VLAN·octet·seq, ≤ 9 chiffres Proxmox). compact (lab) inchangé. GUI (miroir) + validation 1-4094 : compatibles. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
58 KiB
CHANGELOG — Set-OPS
2026-07-05
Modifié
- VLAN dérivé du tenant (réseau convergé).
deriver_nomenclature(schémaip-miroir) dérive désormais le VLAN =index × 10 + zone— unique globalement sur un trunk convergé (chaque tenant son bloc de 10 ; 1-9 réservés à l'infra partagée). Le VLAN contenant déjà le tenant (1er chiffre = index), le VMID mène avec le VLAN (VLAN·octet·seq, ≤ 9 chiffres Proxmox). Ex. Technolibre (index 2) → VLANs 21-26, VMID 2101101. Le schémacompact(lab, sandbox) reste inchangé. Champsvlan:codés en dur retirés des catégories ip-miroir (désormais dérivés).
Ajouté
- Zéro-confiance est-ouest — flux Métriques, Logs et Courriel chiffrés. Suite du chantier
(après PostgreSQL) : métriques (node_exporter sert en HTTPS via cert step-ca +
--web.config.file+ cert-sync owned prometheus ; Prometheus scrapescheme: https+tls_config), logs (Lokihttp_tls_config+ cert-sync owned loki ; Alloy pushhttps+tls_config), courriel (LMTPedge-mta→infra-mail:24enlmtp_tls_security_level=verify+lmtp_tls_CAfile;client_smtpen STARTTLS vérifié). Chacun prouvé de bout en bout (200 HTTPS, cibles UP, livraisonstatus=sent, HTTP rejeté). Motif cert-sync.pathindustrialisé. Reste : edge→backends + DNS (DoT). - VMID 9 chiffres mnémotechnique (schéma
ip-miroir, opt-in).vmid_schema: ip-miroirdans la nomenclature → VMIDI·VVV·HHH·NN(index·VLAN·octet-hôte·séquence) : le VMID contient l'IP (10.(10+index).VLAN.hôte) + le tenant, lisible d'un coup d'œil. Défautcompactrétro-compatible (instances déployées inchangées). - Instance partenaire Technolibre. Écosystème complet (12 VM,
etat: planifie) dans10.12.16.0/20, 6 zones de sécurité (Frontière/Identité/Données/Services-infra/Observabilité/ Applications, un /24 + VLAN chacune), index de fédération 2, VMID ip-miroir. Preuve de portabilité d'un tenant.
Modifié
- Références par FQDN partout (fin des IP codées en dur). Décision d'archi : FQDN pour toute
référence inter-services (non ambigu en fédération, canonique pour TLS ; nom court = hostname OS).
resoudre_baserenvoie le FQDN (→ keycloak/forgejo/icinga) ;client_journal_loki_urldérivé du groupeserveur_loki; defaults db_host IP morts nettoyés. Aucune IP littérale dans les defaults. - Découplage du tenant d'origine. Realm SSO centralisé sur l'intrant
identite_realm(défautchezlepro; les 4 rôles keycloak/forgejo/grafana/oauth2_proxy en dérivent ; exposé dans la GUI). Vars brandées renommées génériques :chezlepro_timezone→fuseau_horaire,chezlepro_organisation→organisation. Le moteur ne porte plus le nom d'un tenant. - Modèles d'instance rafraîchis.
integralrégénéré depuis le cas prouvé (6 zones, fonctions éprouvéesdata-sql/id-ldap/id-sso/sup, ip-miroir, nouveaux intrants) ;socle/identite/observabilite/forgeréalignés sur le même moule (prouvés : dérivation +valider_serveurs).presence-webmarqué aspirationnel (rôles web-frontal/dorsal absents) plutôt que faussement prêt.
2026-07-04
Ajouté
- Zéro-confiance : flux PostgreSQL entièrement chiffré et vérifié. PG sert désormais son cert
step-ca (vérifiable contre root_ca) au lieu du snakeoil, et refuse toute connexion non-TLS
du réseau (
hostssldans pg_hba). Les 3 clients passent en verify-full : keycloak (db-url-properties sslmode=verify-full), forgejo (SSL_MODE=verify-full+PGSSLROOTCERT), IcingaDB (tls: true+ca). Prérequis posés :client_pkisur data-sql-01 (cert), etroot_ca.crten 0644 (cert public, requis par les clients TLS non-root). cert-sync PG (motif.path, owned postgres) + reload de l'instancepostgresql@NN-main. Vars :serveur_postgresql_tls_actif/_tls_force,serveur_*_db_sslmode/_ca. Prouvé de bout en bout (cert Set-OPS CA servi, apps 200, non-TLS rejeté « aucun chiffrement », TLS accepté).
Corrigé
- PG : détection de version robuste (collision avec un répertoire non-numérique). Placer le
tls_dirsous/etc/postgresql/faisait choisirtlscomme « version » de cluster (find | sort | last) → configs déployées au mauvais endroit (verrou hostssl inopérant). Corrigé : détection filtrée aux dossiers numériques (^[0-9]+$) +tls_dirdéplacé sous/var/lib/postgresql/tls. - Renouvellement de cert : recharger le VRAI consommateur (bug latent de flotte). Le
cert-renewer@.service(client_pki) renouvelait le cert sur disque mais sonExecStartPostrechargeait un service nommé d'après le cert (%i= FQDN), inexistant → nginx (et postfix, dovecot, slapd) n'étaient jamais rechargés et servaient l'ancien cert jusqu'à expiration. Symptôme vécu : cert edge expiré en mémoire (renouvelé sur disque), échec TLS de l'échange code→jeton OIDC → login Grafana/SSO cassé (tous les services derrière l'edge). Correctif :client_pki_reload_services(liste des vrais consommateurs), câblée par groupe (edge→nginx, mail→postfix/dovecot, annuaire→slapd). Appliqué + vérifié sur les 4 hôtes. Fix immédiat de l'incident :systemctl reload nginxsur l'edge.
Ajouté
- Doc à jour : unité wiki « Autorisation & RBAC », leçon renouvellement, runbooks. Fermeture
des dettes de doc : nouvelle unité wiki authZ/RBAC (pendant d'Identité & SSO, avec l'exemple
Grafana), section « le renouvellement est un système » versée dans l'unité PKI (comparer cert
servi vs fichier ; recharger le consommateur), et
docs/runbooks-exploitation.md(cert expiré, RBAC Grafana, branding Forgejo). 15 unités wiki désormais. - UI des logs Loki : dashboard Grafana provisionné. Loki n'a pas d'UI ; son UI est Grafana.
Ajout d'un dashboard « Journaux de la flotte » (dossier Set-OPS) : sélecteur d'hôte multi +
filtre regex insensible à la casse + panneau logs + débit par hôte. Référence Loki par une
variable de datasource (
ds_loki), pas un UID codé en dur (leçon : ajouter un uid explicite à une datasource déjà provisionnée casse le démarrage de Grafana). Visible par les Viewers (dont testmail) sans accès Explore. Prouvé sur obs-01 (dashboard chargé, Grafana actif). - RBAC via SSO : rôle de realm → niveau Grafana. Machinerie additive et idempotente dans
serveur_keycloak(rbac-oidc.yml) : rôles de realm (serveur_keycloak_realm_roles), mapperrolessur les clients choisis (_role_mapper_clients, rôles de realm → claimrolesdans ID token + userinfo), assignations rôle→utilisateur (_role_assignments). Côté Grafana,role_attribute_path(grafana-admin→Admin,grafana-editor→Editor, sinon Viewer). kcadm à chaud, zéro coupure SSO. Prouvé (idempotencechanged=0) sur id-sso-01 : rôles créés, mapper présent,testmail=grafana-editor(→ Explore). Illustre l'authZ (vs authN du SSO).
2026-07-03
Ajouté
- Identité visuelle Alliance Boréale sur Forgejo (léger, officiel). Branding via le dossier
custom/de Forgejo (mécanisme officiel — pas de fork, résistant aux MAJ) : accent aurore par variables CSS (--color-primary…, aucune classe interne touchée), logo/favicon étoile (réutilisés du thème Keycloak), page d'accueil brandée (home.tmpl: hero aurore + accroche), thème sombre par défaut, nom + méta. Codifié dansserveur_forgejo(serveur_forgejo_branding,_app_name,_theme), déployé dans{{ data }}/custom/. Prouvé sur forge-01 : accueil rend (200, « Forge Chezlepro »),alliance.cssservi (cyan aurore), lint OK. - Wiki pédagogique Forgejo — 14 unités d'apprentissage. Set-OPS comme compagnon pédagogique :
chaque service = une lentille sur un fondamental TIC, méthodes génériques (on apprend OIDC, pas
Keycloak). Source versionnée dans
wiki/, publiée dans le wiki Forgejo (eregion). Moule à 4 temps (concept → Set-OPS → transférable → à toi de jouer, avec casse-répare). - GUI : boutons cohérents. « Pousser » (surchargé : clonait et déployait) → « 🖥 Créer la VM » pour le clone, « Déployer » partout pour le déploiement, dry-run obligatoire partout. GUI 100 % française.
- Agents d'observabilité/ops éprouvés — observabilité flotte-complète. Les 3 intégrations
« agent » (liaisons nœud × optionnelles) déployées sur 4 nœuds (obs-01, data-sql-01, id-ldap-01,
forge-01) et prouvées :
client_metrique(node_exporter → Prometheus scrape les 4 cibles, toutes UP) ;client_journal(journald → Loki reçoit les logs des 4 nœuds) ;client_smtp(msmtp → courriel système d'un nœud relayé par l'edge-MTA et livré). Comble le trou : les serveurs d'observabilité (Grafana/Prometheus/Loki) étaient prouvés, mais pas la collecte fleet-wide — désormais Grafana voit toute la flotte. Cibles corrigées (bac à sable) : Loki→obs-01, relais→edge-mta-01 (pas infra-mail-01, qui est le store Dovecot sans SMTP :25). - Thème de connexion Keycloak à l'identité Alliance Boréale. Thème de login
alliance-boreale(roles/serveur_keycloak/files/themes/,parent=keycloak+ overlay CSS) reprenant l'identité du site de l'Alliance (extraite desite-alliance-boreale) : ciel nocturne aurore (#05060f/#0a0d24+ dégradés), carte glassmorphism, logo étoile aurore (lefavicon.svgdu 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 viakcadm ... -s loginTheme(varserveur_keycloak_login_theme, idempotent), Keycloak rechargé (flush_handlersavant la config realm). Prouvé : la page de login chargealliance.css(HTTP 200) + lelogo.svg(200),loginTheme=alliance-borealeactif surchezlepro. 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 ; respecteprefers-reduced-motion. Prouvé :constellation.jsréférencé + servi (200). Console de compte thémée aussi (thèmeaccount,parent=keycloak.v3) : overlay CSS surchargeant les variables PatternFly 5 (fond aurore, cartes en verre, accent aurore) + la même constellation animée. Varserveur_keycloak_account_themeviakcadm -s accountTheme. Prouvé : console charge (HTTP 200,keycloak.v3intact),account.cssservi (200). - Soumission courriel
:587interne (authentifiée) — la boucle souveraine est bouclée. Postfix (edge-mta) sert la soumission:587(blocmaster.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) :testmails'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 sectioninet_listener; ajouter un servicemaster.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œudbackup-01) = cible SFTP/SSH (utilisateurrestic, 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étentionforget --prune. Prouvé de bout en bout sur le Tier 0 (infra-pki-01→/etc/step-ca, l'ancre de confiance) : sauvegarde hors-nœud versbackup-01, puis restauration byte-identique des clés CA (root_ca_key,intermediate_ca_key,ca.json). Piège corrigé : le plancher/etc/hostsd'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(slapcatLDIF — 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é contienttestmail. Les 5 dépôts restic (pki, sql, ldap, mail, forge) sont hors-nœud surbackup-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-échafaudagesdebugsans rôle (nextcloud, collabora, client_supervision, web_frontal, web_dorsal) ; l'intention reste documentée dansdocs/catalogue-services.md. Binding annuaire : nouveau rôle utilitaire partagéresoudre_annuaire(commeresoudre_base) qui dérive la connexion OpenLDAP dudomaine_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 viaset_fact.
- 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
- Binding annuaire complété —
icingaweb2+client_ldapmigrés versresoudre_annuaire. Fin des 2 loose ends :serveur_icingaweb2(connexion LDAP dormante en mode SSO) résout viaresoudre_annuaire(redéploiementchanged=0, SSO intact) ;client_ldap(SSSD, dormant) ne pointe plus sur unidm-01pé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 authexternal. Rôle paramétrable (client, secret voûte, redirect, upstream, cookie voûte) — réutilisable pour toute app OIDC-less. Éprouvé sursup-01devant Icinga Web 2 : client Keycloakicingaweb2, oauth2-proxy:4180(exposé par l'edge) → upstream nginx local:8080→ icingaweb2backend = external(REMOTE_USER depuisX-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 pasemail_verified; IdP interne de confiance) ; en reverse-proxy oauth2-proxy passeX-Forwarded-*(pasX-Auth-Request-*) ; handler nginx enrestart(pasreload) 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 + moduleicingadb), pas d'assistant de setup. Base IcingaDB viaresoudre_base(registre). Auth LDAP direct vers OpenLDAP (LDAPS,client_pkisursup-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ôteicinga). Déploiementfailed=0(le rôle est bon ; les frictions étaient dans le simulateur de login curl : contrôle de cookie_checkCookie, champsuid/submit_login, valeur CSRF avantname). - Module BPM (Business Process) éprouvé + codifié — pile Icinga complète.
serveur_icingaweb2installe + activeicingaweb2-module-businessprocess(serveur_icingaweb2_modules), crée le répertoire des processus (éditable via l'UI, groupeicingaweb2, setgid) et sème des processus métier en IaC (serveur_icingaweb2_bpm_processes, nom → contenu.conf). Format des feuilleshost;service(éprouvé via les fixtures du module). Prouvé : un processus « Supervision Chezlepro » (agrège load/procs/swap/ping4/ssh du hosticingaen logique ET) rend un état dans l'UI (testmailconnecté), 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é sursup-01(🔧→⭐), baseicingadbPostgreSQL 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 DRYresoudre_basesur 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é surforge-01(🔧→⭐), adossé à PostgreSQL (baseforgejoauto-provisionnée depuis le registre), exposé par l'edge (vhost + cert SAN + A PowerDNS + alias plancher, tout auto-dérivé deexpose). Source OAuth2 vers Keycloak (forgejo admin auth add-oauth, idempotent, realmchezlepro), client OIDCforgejoenregistré viaserveur_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éeserveur_sendmail→serveur_postfix(le vrai MTA) ;app.inidoit appartenir au usergit(Forgejo persiste des secrets générés) ; ordre admin/migrations (flush_handlers+wait_foravantadmin user create) ;HTTP_ADDR127.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é dansserveur_keycloak,serveur_forgejoetserveur_icinga(charger le registre, filtrer par consommateur, déréférencer le secret vialookup('vars', ...), résoudre hôte/port) est extrait dansroles/resoudre_base(factsresoudre_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 + forgejofailed=0, idempotent,testmailtoken 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_powerdnsgénère désormais, dans la zone interne, un enregistrement A pour chaque FQDN d'exposition (champexposedes applications) vers l'edge qui le sert (domaines.edge) — viaexpositions_des_applications(même source que les vhosts nginx et les SANs du cert edge). Déclarerexposeproduit maintenant vhost + SAN de cert + enregistrement DNS, tout dérivé. Prouvé :dig @infra-dns-01 grafana.lab.chezlepro.internaletkeycloak.…→192.168.15.21(edge). Optionserveur_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ïvementclient_dnsvers 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 chaqueexpose(dérivé dedomaines.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 (statdelegate_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/hostsd'obs-01 régénéré aveckeycloak/grafana→ edge (ligne manuelle éliminée),getentOK, et le flux SSO Grafana fonctionne via la résolution du plancher (login: testmail). Boucle Phase 3 fermée : déclarerexpose→ vhost + SAN cert + A PowerDNS + alias plancher, tout dérivé. Bugs corrigés en chemin :serveur_loki(groupelokimanquant),statsur cible→contrôle,becomeinutile 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 viaclient_unbound_transitaires). Alternative dynamique au plancher/etc/hostsstatique, sans casser Internet. Bascule de/etc/resolv.confproté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,aptOK. 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 sesliens: [{vers, role}]dansplan/applications.yml; chaque rôle décrit les liens qu'il accepte dansmeta/liens.yml(setops_liens.accepte, commemeta/empreinte.yml).instancierré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 instancierdonne 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/porteedansbases-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 danscatalogue-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é depuisarchitecture-set-ops.md.serveur_postgresqletserveur_keycloaképrouvés sur VM réelles (🔧→⭐). PostgreSQL déployé (data-sql-01), écoute réseau + pg_hba VLAN, et provisionne la basekeycloakdepuis 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éescatalogue-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, viakcadm(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 parenvironment+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_expositionsexistait déjà (litexposedes applications +edgededomaines.yml, dériveamont = http://<IP hôte>:<port>, génère le vhost avecX-Forwarded-*). Le bac à sable déclarekeycloak.expose: [keycloak.lab.chezlepro.internal]+ le domaine internelab.chezlepro.internal(edgeserveur_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/..."(lesX-Forwardedpassent, 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 auxclient_pki_sansde l'edge (client_pki demande + renouvelle déjà le cert d'hôte), puis pointerserveur_nginx_certificatsur le cert client_pki (/etc/step/certs/<edge>.crt). - Cert de l'edge : snakeoil → step_ca (HTTPS valide). Appliqué le raffinement ci-dessus :
client_pkiajouté à l'edge, sesclient_pki_sansincluent le FQDN d'exposition (keycloak.lab.chezlepro.internal), etserveur_nginx_certificat/_clepointent sur le cert client_pki. Prouvé : HTTPSHTTP 200avecssl_verify_result=0(chaîne validée contre la racine step_ca, nom correct), émetteurSet-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 viaGF_AUTH_GENERIC_OAUTH_*(client confidentielgrafana, realmchezlepro, secretvault_grafana_oidc).serveur_loki+serveur_prometheus+serveur_grafanadéployés surobs-01(🔧→⭐). Bug de rôle corrigé :serveur_lokicréait le répertoire engroup: lokialors que le paquet crée l'utilisateur ennogroupsans groupeloki→ 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/userrenvoielogin: 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 viakcadmà la main (à codifier — rôle grafana ou liste de clients côté keycloak) ; (2) la résolutionkeycloak.…internal → edgesur obs-01 est un/etc/hostsmanuel (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éclarativeserveur_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) ; lesecretréférence la même variable de voûte que l'app. Prouvé : clientgrafanasupprimé → rôle → recréé →testmailse 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 parinstancier.pyen variables Ansible ; chaque rôle décrit les liens qu'il accepte dansmeta/liens.yml(commemeta/empreinte.yml) ; FQDN cible dérivé de la nomenclature (jamais codé en dur). Domaines publics traités comme lienexposition(écrit sur l'edge). Réconcilie l'existant (basesconsommateur,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.
LICENSEremplacé par le texte officiel intégral de l'AGPLv3 (verbatim, non modifié). Attribution + modèle libre + services/certification documentés dans leREADME(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 IaCstalwart config applyannoncé 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ôleserveur_stalwartest retiré (git en garde la trace) ;docs/courriel-conception.mdmis à 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é viasmtpd_milters(optionserveur_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/mailroot 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'attributmail), 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. Configmain.cf+ carteldap-mailboxes.cf, validée parpostfix check. Secret de bind :vault_openldap_admin. Nécessiteserveur_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 (versserveur_openldap, filtremail), stockage Maildir (user systèmevmail), 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 pardoveconfau déploiement. Sockets d'intégration Postfix conditionnels (rendus si l'utilisateurpostfixest co-localisé). Prouvé :doveadm auth test— bon mot de passe accepté, mauvais refusé, sur cert step_ca. Secret de bind :vault_openldap_admin. serveur_openldapdurci 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 paropenldap(/etc/ldap/tls) par un script + une unitépathsystemd qui re-synchronise et recharge slapd à chaque renouvellement ;olcTLS*configuré danscn=config,SLAPD_SERVICESexposeldaps://. Dégrade proprement (slapd en clair local) siclient_pkin'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.
- TLS (LDAPS + STARTTLS) : le certificat d'hôte step_ca (déposé par
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é/actifdé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
creerréussi) ou qu'on déploie passe automatiquementactifdans 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.
- Sonde de vie : le GUI teste la joignabilité SSH de chaque hôte en arrière-plan
(
Corrigé
-
nginx ne validait pas sur Debian 13 (
server_tokensen double). Debian 13 livreserver_tokens off;actif dans/etc/nginx/nginx.conf(avant : commenté). Le drop-inconf.d/99-setops.confdu rôle le redéclarait →nginx -téchouait (« directive is duplicate ») et le déploiement plantait au handler de validation. Le rôleserveur_nginxneutralise 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échiffrergroup_vars/all/vault.ymlet échoue sans mot de passe (exit 4) — alors que l'opération est structurelle, sans secret.instancierutilise désormais automatiquement le fichier conventionnel~/.config/setops-vault-pass(siANSIBLE_VAULT_PASSWORD_FILEn'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 selonvault.exemple.ymlne 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 sesvault_*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--checkn'installe pas réellement → le service (ou le fichier de zone/conf) n'existe pas encore → fauxfatal, qui bloquait le déploiement (le dry-run doit réussir pour débloquer « Déployer »). Ajout dewhen: not ansible_check_modesur ces tâches et handlers des 13 rôlesserveur_*(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 appliquersans--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 →
🖥 Pousserclone 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 →
Pousserdéploie l'hôte porteur (make deployer HOTE=<hôte>). - Base →
Pousserdéploie l'hôte du serveur de BD (crée la base). Nouveaux modescreer/pousserdansexecuter_flux+ routes/api/creeret/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 soclehosts_statiques(dansserveur_debian) : génère/etc/hostssur 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_dnsest rendu tolérant (inerte si aucun DNS interne) et sa dépendance àserveur_powerdnspasse molle. - Adressage fédéré : index d'instance. Le VMID n'est plus codé
9CSNNen dur : il prend le préfixe d'unindexdéclaré en tête deplan/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-hoc172.19.x). Code mort retiré (deriveServeurJS). Voirdocs/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 symlinkinstancevers un autre dépôt d'instance (prod ↔ bac à sable) ;make instance-couranteaffiche 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.ymlde 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 durinventories/lab/inventories/production: il vise un inventaire par instance, détecté de façon rétro-compatible (principal>production>lab) et surchargeable parSETOPS_INVENTAIRE. Les instances existantes (découpage lab/production) continuent de fonctionner à l'identique ; les nouvelles peuvent adopterinventories/principal/. Deuxième pierre de la séparation par instance (la 1re étant le drapeausetops_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 drapeausetops_productiondansgroup_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)..gitignoredurci (**/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/laben dur (config Proxmox + voûte) — vestige du modèle env qui cassait les instancesprincipal. 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éclaraitlaunch+=bindque le paquetpdns-backend-bindpose déjà. Le rôle ne déclare pluslaunch(seulementbind-config).
- Clonage/Makefile codaient
chezlepro_timezonen'était appliqué nulle part. Cet intrant de base global était défini mais aucun rôle ne s'en servait. Le rôlechrony(appliqué à tout hôte viaserveur_debian) règle désormais le fuseau horaire à partir dechezlepro_timezone(chrony_timezonepar 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#messageet#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-5changent de vue,j/kparcourent les hôtes,v/dvérifient/déploient l'hôte sélectionné,/cible le filtre,Ctrl+Ssauvegarde 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
beforeunloadsi 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 (cataloguesproxmox_noeuds/proxmox_stockages, vide = défaut global) et Intégrations une rangée de cases à cocher des rôlesclient_*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 dansgroup_vars/proxmox.yml; l'API exposeintegrations_disponibles(scan deroles/client_*). Le typeliste(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, rendudessinerArbre, 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/inventairerenvoie 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, étatmodifie). - 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 depuismake helpetQUICKSTART.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 dedomaine_interne(clé de voûte) demande une confirmation explicite ; undomaine_internevide est refusé côté serveur. La nomenclature reste en lecture seule (modifiable dans le plan). Voirdocs/intrants-communs.mdetdocs/intrants-base-gui-conception.md. - Source unique d'identité partagée.
domaine_interneetchezlepro_timezonevivent désormais dansinventories/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 écritproxmox_coeurs/proxmox_memoire/proxmox_disque_taille; le clonage passecores/memoryà Proxmox (omitsi absent → aucune régression). Override par hôte possible dans le plan (serveurs.yml). Voirdocs/dimensionnement-ressources.md.
Corrigé
make instancier-appliquer FORCE=1n'honorait pas--force. La recette Makefile lançaitinstancier.py appliquersans relayerFORCE; 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 parmake, 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.