Set-OPS-Public/CHANGELOG.md
Daniel Allaire 02bbba61b3 Nomenclature : VLAN dérivé (index × 10 + zone) pour réseau convergé
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>
2026-07-05 20:38:44 -04:00

58 KiB
Raw Blame History

CHANGELOG — Set-OPS

2026-07-05

Modifié

  • VLAN dérivé du tenant (réseau convergé). deriver_nomenclature (schéma ip-miroir) dérive désormais le VLAN = index × 10 + zoneunique globalement sur un trunk convergé (chaque tenant son bloc de 10 ; 1-9 réservés à l'infra partagée). Le VLAN contenant déjà le tenant (1er chiffre = index), le VMID mène avec le VLAN (VLAN·octet·seq, ≤ 9 chiffres Proxmox). Ex. Technolibre (index 2) → VLANs 21-26, VMID 2101101. Le schéma compact (lab, sandbox) reste inchangé. Champs vlan: codés en dur retirés des catégories ip-miroir (désormais dérivés).

Ajouté

  • Zéro-confiance est-ouest — flux Métriques, Logs et Courriel chiffrés. Suite du chantier (après PostgreSQL) : métriques (node_exporter sert en HTTPS via cert step-ca + --web.config.file + cert-sync owned prometheus ; Prometheus scrape scheme: https + tls_config), logs (Loki http_tls_config + cert-sync owned loki ; Alloy push https + tls_config), courriel (LMTP edge-mta→infra-mail:24 en lmtp_tls_security_level=verify + lmtp_tls_CAfile ; client_smtp en STARTTLS vérifié). Chacun prouvé de bout en bout (200 HTTPS, cibles UP, livraison status=sent, HTTP rejeté). Motif cert-sync .path industrialisé. Reste : edge→backends + DNS (DoT).
  • VMID 9 chiffres mnémotechnique (schéma ip-miroir, opt-in). vmid_schema: ip-miroir dans la nomenclature → VMID I·VVV·HHH·NN (index·VLAN·octet-hôte·séquence) : le VMID contient l'IP (10.(10+index).VLAN.hôte) + le tenant, lisible d'un coup d'œil. Défaut compact rétro-compatible (instances déployées inchangées).
  • Instance partenaire Technolibre. Écosystème complet (12 VM, etat: planifie) dans 10.12.16.0/20, 6 zones de sécurité (Frontière/Identité/Données/Services-infra/Observabilité/ Applications, un /24 + VLAN chacune), index de fédération 2, VMID ip-miroir. Preuve de portabilité d'un tenant.

Modifié

  • Références par FQDN partout (fin des IP codées en dur). Décision d'archi : FQDN pour toute référence inter-services (non ambigu en fédération, canonique pour TLS ; nom court = hostname OS). resoudre_base renvoie le FQDN (→ keycloak/forgejo/icinga) ; client_journal_loki_url dérivé du groupe serveur_loki ; defaults db_host IP morts nettoyés. Aucune IP littérale dans les defaults.
  • Découplage du tenant d'origine. Realm SSO centralisé sur l'intrant identite_realm (défaut chezlepro ; les 4 rôles keycloak/forgejo/grafana/oauth2_proxy en dérivent ; exposé dans la GUI). Vars brandées renommées génériques : chezlepro_timezone→fuseau_horaire, chezlepro_organisation→organisation. Le moteur ne porte plus le nom d'un tenant.
  • Modèles d'instance rafraîchis. integral régénéré depuis le cas prouvé (6 zones, fonctions éprouvées data-sql/id-ldap/id-sso/sup, ip-miroir, nouveaux intrants) ; socle/identite/ observabilite/forge réalignés sur le même moule (prouvés : dérivation + valider_serveurs). presence-web marqué aspirationnel (rôles web-frontal/dorsal absents) plutôt que faussement prêt.

2026-07-04

Ajouté

  • Zéro-confiance : flux PostgreSQL entièrement chiffré et vérifié. PG sert désormais son cert step-ca (vérifiable contre root_ca) au lieu du snakeoil, et refuse toute connexion non-TLS du réseau (hostssl dans pg_hba). Les 3 clients passent en verify-full : keycloak (db-url-properties sslmode=verify-full), forgejo (SSL_MODE=verify-full + PGSSLROOTCERT), IcingaDB (tls: true + ca). Prérequis posés : client_pki sur data-sql-01 (cert), et root_ca.crt en 0644 (cert public, requis par les clients TLS non-root). cert-sync PG (motif .path, owned postgres) + reload de l'instance postgresql@NN-main. Vars : serveur_postgresql_tls_actif/_tls_force, serveur_*_db_sslmode/_ca. Prouvé de bout en bout (cert Set-OPS CA servi, apps 200, non-TLS rejeté « aucun chiffrement », TLS accepté).

Corrigé

  • PG : détection de version robuste (collision avec un répertoire non-numérique). Placer le tls_dir sous /etc/postgresql/ faisait choisir tls comme « version » de cluster (find | sort | last) → configs déployées au mauvais endroit (verrou hostssl inopérant). Corrigé : détection filtrée aux dossiers numériques (^[0-9]+$) + tls_dir déplacé sous /var/lib/postgresql/tls.
  • Renouvellement de cert : recharger le VRAI consommateur (bug latent de flotte). Le cert-renewer@.service (client_pki) renouvelait le cert sur disque mais son ExecStartPost rechargeait un service nommé d'après le cert (%i = FQDN), inexistant → nginx (et postfix, dovecot, slapd) n'étaient jamais rechargés et servaient l'ancien cert jusqu'à expiration. Symptôme vécu : cert edge expiré en mémoire (renouvelé sur disque), échec TLS de l'échange code→jeton OIDC → login Grafana/SSO cassé (tous les services derrière l'edge). Correctif : client_pki_reload_services (liste des vrais consommateurs), câblée par groupe (edge→nginx, mail→postfix/dovecot, annuaire→slapd). Appliqué + vérifié sur les 4 hôtes. Fix immédiat de l'incident : systemctl reload nginx sur l'edge.

Ajouté

  • Doc à jour : unité wiki « Autorisation & RBAC », leçon renouvellement, runbooks. Fermeture des dettes de doc : nouvelle unité wiki authZ/RBAC (pendant d'Identité & SSO, avec l'exemple Grafana), section « le renouvellement est un système » versée dans l'unité PKI (comparer cert servi vs fichier ; recharger le consommateur), et docs/runbooks-exploitation.md (cert expiré, RBAC Grafana, branding Forgejo). 15 unités wiki désormais.
  • UI des logs Loki : dashboard Grafana provisionné. Loki n'a pas d'UI ; son UI est Grafana. Ajout d'un dashboard « Journaux de la flotte » (dossier Set-OPS) : sélecteur d'hôte multi + filtre regex insensible à la casse + panneau logs + débit par hôte. Référence Loki par une variable de datasource (ds_loki), pas un UID codé en dur (leçon : ajouter un uid explicite à une datasource déjà provisionnée casse le démarrage de Grafana). Visible par les Viewers (dont testmail) sans accès Explore. Prouvé sur obs-01 (dashboard chargé, Grafana actif).
  • RBAC via SSO : rôle de realm → niveau Grafana. Machinerie additive et idempotente dans serveur_keycloak (rbac-oidc.yml) : rôles de realm (serveur_keycloak_realm_roles), mapper roles sur les clients choisis (_role_mapper_clients, rôles de realm → claim roles dans ID token + userinfo), assignations rôle→utilisateur (_role_assignments). Côté Grafana, role_attribute_path (grafana-admin→Admin, grafana-editor→Editor, sinon Viewer). kcadm à chaud, zéro coupure SSO. Prouvé (idempotence changed=0) sur id-sso-01 : rôles créés, mapper présent, testmail = grafana-editor (→ Explore). Illustre l'authZ (vs authN du SSO).

2026-07-03

Ajouté

  • Identité visuelle Alliance Boréale sur Forgejo (léger, officiel). Branding via le dossier custom/ de Forgejo (mécanisme officiel — pas de fork, résistant aux MAJ) : accent aurore par variables CSS (--color-primary…, aucune classe interne touchée), logo/favicon étoile (réutilisés du thème Keycloak), page d'accueil brandée (home.tmpl : hero aurore + accroche), thème sombre par défaut, nom + méta. Codifié dans serveur_forgejo (serveur_forgejo_branding, _app_name, _theme), déployé dans {{ data }}/custom/. Prouvé sur forge-01 : accueil rend (200, « Forge Chezlepro »), alliance.css servi (cyan aurore), lint OK.
  • Wiki pédagogique Forgejo — 14 unités d'apprentissage. Set-OPS comme compagnon pédagogique : chaque service = une lentille sur un fondamental TIC, méthodes génériques (on apprend OIDC, pas Keycloak). Source versionnée dans wiki/, publiée dans le wiki Forgejo (eregion). Moule à 4 temps (concept → Set-OPS → transférable → à toi de jouer, avec casse-répare).
  • GUI : boutons cohérents. « Pousser » (surchargé : clonait et déployait) → « 🖥 Créer la VM » pour le clone, « Déployer » partout pour le déploiement, dry-run obligatoire partout. GUI 100 % française.
  • Agents d'observabilité/ops éprouvés — observabilité flotte-complète. Les 3 intégrations « agent » (liaisons nœud × optionnelles) déployées sur 4 nœuds (obs-01, data-sql-01, id-ldap-01, forge-01) et prouvées : client_metrique (node_exporter → Prometheus scrape les 4 cibles, toutes UP) ; client_journal (journald → Loki reçoit les logs des 4 nœuds) ; client_smtp (msmtp → courriel système d'un nœud relayé par l'edge-MTA et livré). Comble le trou : les serveurs d'observabilité (Grafana/Prometheus/Loki) étaient prouvés, mais pas la collecte fleet-wide — désormais Grafana voit toute la flotte. Cibles corrigées (bac à sable) : Loki→obs-01, relais→edge-mta-01 (pas infra-mail-01, qui est le store Dovecot sans SMTP :25).
  • Thème de connexion Keycloak à l'identité Alliance Boréale. Thème de login alliance-boreale (roles/serveur_keycloak/files/themes/, parent=keycloak + overlay CSS) reprenant l'identité du site de l'Alliance (extraite de site-alliance-boreale) : ciel nocturne aurore (#05060f/ #0a0d24 + dégradés), carte glassmorphism, logo étoile aurore (le favicon.svg du site), bouton dégradé aurore (teal→cyan, pilule), liens cyan, police système (souveraineté, zéro dépendance externe). Déployé dans {{ keycloak_home }}/themes/, appliqué au realm via kcadm ... -s loginTheme (var serveur_keycloak_login_theme, idempotent), Keycloak rechargé (flush_handlers avant la config realm). Prouvé : la page de login charge alliance.css (HTTP 200) + le logo.svg (200), loginTheme=alliance-boreale actif sur chezlepro. Constellation animée en fond (scripts=js/constellation.js) : le JS crée son propre ciel (canvas + aurore, le template n'en ayant pas) — étoiles scintillantes qui dérivent, liens de constellation cyan, blob d'aurore ondulant ; respecte prefers-reduced-motion. Prouvé : constellation.js référencé + servi (200). Console de compte thémée aussi (thème account, parent=keycloak.v3) : overlay CSS surchargeant les variables PatternFly 5 (fond aurore, cartes en verre, accent aurore) + la même constellation animée. Var serveur_keycloak_account_theme via kcadm -s accountTheme. Prouvé : console charge (HTTP 200, keycloak.v3 intact), account.css servi (200).
  • Soumission courriel :587 interne (authentifiée) — la boucle souveraine est bouclée. Postfix (edge-mta) sert la soumission :587 (bloc master.cf : STARTTLS requis, SMTP AUTH, seuls les authentifiés relaient) ; l'auth SASL est déléguée à Dovecot (infra-mail, passdb LDAP prouvé) via un auth-listener réseau (service auth { inet_listener sasl }, port 12345). Aucune sortie internet : interne→interne uniquement (l'externe = déliverabilité, Étape B). Prouvé (swaks) : testmail s'authentifie (235 Authentication successful), Postfix accepte (250 queued), et le courriel est livré dans la boîte (LMTP→Dovecot). Le courriel souverain fait maintenant recevoir ET envoyer. Vars : serveur_dovecot_sasl_reseau, serveur_postfix_submission_actif. Pièges : Dovecot 2.4 exige un nom de section inet_listener ; ajouter un service master.cf (nouveau listener) → handler restart (pas reload) ; et une config cassée peut bloquer un redéploiement si la synchro cert/restart précède le template (corriger la config à la main pour débloquer).
  • Sauvegardes applicatives (logiques) — serveur_backup + client_backup (restic), Tier 0 prouvé. Choix : sauvegarder la donnée d'état (non régénérable) plutôt que les VM (reconstructibles par le code + le template). Outil restic (chiffrement côté client, déduplication, rétention). serveur_backup (nœud backup-01) = cible SFTP/SSH (utilisateur restic, clé autorisée, dépôts sous /srv/restic/<nœud>). client_backup (intégration par nœud) = restic + jobs déclaratifs (client_backup_jobs : {nom, commande?, chemins}), clé SSH + mot de passe restic en voûte, script + timer systemd (quotidien) + rétention forget --prune. Prouvé de bout en bout sur le Tier 0 (infra-pki-01/etc/step-ca, l'ancre de confiance) : sauvegarde hors-nœud vers backup-01, puis restauration byte-identique des clés CA (root_ca_key, intermediate_ca_key, ca.json). Piège corrigé : le plancher /etc/hosts d'un nœud existant ignore un nœud nouvellement ajouté → rafraîchir le socle.
  • Sauvegardes Tier 1 généralisées — 5 nœuds, restauration prouvée. client_backup étendu (jobs déclaratifs en host_vars) à : data-sql-01 (pg_dumpall — keycloak/forgejo/icingadb), id-ldap-01 (slapcat LDIF — les identités), infra-mail-01 (/var/vmail — les boîtes), forge-01 (/var/lib/forgejo + /etc/forgejo — dépôts Git ; la BD est déjà couverte par PG). Prouvé par restauration : dump PostgreSQL restauré contient bien les 3 bases (CREATE DATABASE forgejo/icingadb/keycloak) ; LDIF restauré contient testmail. Les 5 dépôts restic (pki, sql, ldap, mail, forge) sont hors-nœud sur backup-01, chiffrés. Ajouter un service à sauvegarder = déclarer un job. Reste : cible offsite (3-2-1, Étape B — le dépôt n'est qu'une URL swappable).
  • Consolidation — binding annuaire (resoudre_annuaire) + retrait de la cruft. Cruft : supprimés les 8 dossiers-catégories inertes (roles/{applications,backup,database, identity,monitoring,proxmox,storage,web}/, README seuls) et les 5 playbooks-échafaudages debug sans rôle (nextcloud, collabora, client_supervision, web_frontal, web_dorsal) ; l'intention reste documentée dans docs/catalogue-services.md. Binding annuaire : nouveau rôle utilitaire partagé resoudre_annuaire (comme resoudre_base) qui dérive la connexion OpenLDAP du domaine_interne
    • un hôte d'annuaire surchargeable — LE seul endroit où le nom d'hôte de l'annuaire est fixé, au lieu d'être répété. Facts resoudre_annuaire_{uri,port,base_dn,users_dn,bind_dn,bind_password} (secret déréférencé, no_log). Migrés + prouvés (config neutre, changed=0) : serveur_keycloak (fédération LDAP — testmail token HTTP 200), serveur_dovecot + serveur_postfix (flux courriel Postfix→LDAP→LMTP→Dovecot livré de bout en bout). Piège appris : les defaults d'un rôle inclus ne persistent pas hors de son exécution — publier via set_fact.
  • Binding annuaire complété — icingaweb2 + client_ldap migrés vers resoudre_annuaire. Fin des 2 loose ends : serveur_icingaweb2 (connexion LDAP dormante en mode SSO) résout via resoudre_annuaire (redéploiement changed=0, SSO intact) ; client_ldap (SSSD, dormant) ne pointe plus sur un idm-01 périmé. Plus AUCUN rôle ne code en dur l'hôte d'annuaire — un seul point de vérité (resoudre_annuaire).
  • Rôle serveur_oauth2_proxy — passerelle SSO OIDC générique (Keycloak) + Icinga Web 2 au SSO. oauth2-proxy (v7.15.3, binaire GitHub) place Keycloak devant n'importe quelle app sans OIDC natif : elle reçoit l'utilisateur authentifié via en-tête, en auth external. Rôle paramétrable (client, secret voûte, redirect, upstream, cookie voûte) — réutilisable pour toute app OIDC-less. Éprouvé sur sup-01 devant Icinga Web 2 : client Keycloak icingaweb2, oauth2-proxy :4180 (exposé par l'edge) → upstream nginx local :8080 → icingaweb2 backend = external (REMOTE_USER depuis X-Forwarded-Preferred-Username). Prouvé (flux authorization code headless) : testmail → oauth2-proxy → Keycloak → icingaweb2 /dashboard, connecté (« Se connecter avec Chezlepro », comme Grafana/Forgejo). Ferme le gap LDAP-direct d'icingaweb2. Réglages appris : insecure_oidc_allow_unverified_email (les users LDAP n'ont pas email_verified ; IdP interne de confiance) ; en reverse-proxy oauth2-proxy passe X-Forwarded-* (pas X-Auth-Request-*) ; handler nginx en restart (pas reload) car un changement d'adresse d'écoute n'est pas pris par un reload gracieux.
  • Rôle serveur_icingaweb2 — Icinga Web 2 (UI native) + module IcingaDB : éprouvé. App PHP (php8.4-fpm) servie par un nginx local, exposée par l'edge (icinga.lab.chezlepro.internal, auto-dérivé : vhost + cert SAN + A PowerDNS + alias plancher). Config par fichiers .ini (config/resources/authentication/roles + module icingadb), pas d'assistant de setup. Base IcingaDB via resoudre_base (registre). Auth LDAP direct vers OpenLDAP (LDAPS, client_pki sur sup-01) — icingaweb2 n'a pas d'OIDC natif ; SSO-par-proxy = raffinement futur. Prouvé : testmail (LDAP) se connecte (/dashboard), et le module IcingaDB affiche la supervision (hôte icinga). Déploiement failed=0 (le rôle est bon ; les frictions étaient dans le simulateur de login curl : contrôle de cookie _checkCookie, champs uid/submit_login, valeur CSRF avant name).
  • Module BPM (Business Process) éprouvé + codifié — pile Icinga complète. serveur_icingaweb2 installe + active icingaweb2-module-businessprocess (serveur_icingaweb2_modules), crée le répertoire des processus (éditable via l'UI, groupe icingaweb2, setgid) et sème des processus métier en IaC (serveur_icingaweb2_bpm_processes, nom → contenu .conf). Format des feuilles host;service (éprouvé via les fixtures du module). Prouvé : un processus « Supervision Chezlepro » (agrège load/procs/swap/ping4/ssh du host icinga en logique ET) rend un état dans l'UI (testmail connecté), avec le backend IcingaDB (pas d'IDO). Rôle re-prouvé (reset → recrée le processus, idempotent). BPM n'est pas remplaçable par Grafana (roll-up d'impact métier). Pile Icinga = moteur + Web 2 + BPM, complète.
  • Cœur Icinga éprouvé (supervision active). serveur_icinga (cœur : icinga2 + icingadb + icingadb-redis) déployé sur sup-01 (🔧), base icingadb PostgreSQL via le registre. Prouvé : 3 services actifs, et le moteur supervise — IcingaDB peuplée (1 hôte, 12 services, résultats de checks persistés en base). Zéro bug de déploiement (rôle bien bâti). Icinga Web 2 + module BPM restent différés (phases dédiées : UI native + vues d'impact métier ; le BPM n'est pas remplaçable par Grafana). Confirme aussi le DRY resoudre_base sur icinga (les 3 rôles consommateurs validés).
  • Forgejo branché au SSO OIDC (« Se connecter avec Chezlepro ») — 2e app SSO, prouvée. serveur_forgejo (10.0.0) éprouvé sur forge-01 (🔧), adossé à PostgreSQL (base forgejo auto-provisionnée depuis le registre), exposé par l'edge (vhost + cert SAN + A PowerDNS + alias plancher, tout auto-dérivé de expose). Source OAuth2 vers Keycloak (forgejo admin auth add-oauth, idempotent, realm chezlepro), client OIDC forgejo enregistré via serveur_keycloak_clients. Auto-enregistrement OIDC ([oauth2_client] ENABLE_AUTO_REGISTRATION + ALLOW_ONLY_EXTERNAL_REGISTRATION : identités depuis l'annuaire seulement). Prouvé (flux authorization code headless) : testmail (LDAP) se connecte, compte auto-créé (testmail@lab.chezlepro.internal), atterrit sur le tableau de bord. 5 bugs de 1er déploiement corrigés : dépendance périmée serveur_sendmailserveur_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.10.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 exposevhost + 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.
    • ApplicationPousser déploie l'hôte porteur (make deployer HOTE=<hôte>).
    • BasePousser 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.