Après audit du dépôt : docs/carte-set-ops.md = point d'entrée « à lire
d'abord » (index du corpus) + catalogue des mécanismes transverses (2
directions de binding, pont de cert, résolution BD par registre,
socle-first, check-mode, voûte) avec où ils vivent. But : ne plus
re-découvrir l'existant.
Constat : la cruft était déjà inventoriée dans catalogue-services.md
(rôles-catégories inertes, échafaudages) — référencée, pas dupliquée.
catalogue-services « État d'implémentation » rafraîchi (rôles éprouvés
sur VM réelles : socle, PKI, LDAP, DNS, nginx, pile courriel).
Pointeur ajouté depuis architecture-set-ops.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Constat : 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 +
lookup('vars', secret)), sur 4 rôles. Délibérément conservé (le secret ne
quitte jamais le rôle) — ne pas dupliquer en app-side/instancier.
Note corrigée : §3.1 raffinée (app→app côté app, app→base côté base — deux
directions assumées) ; §5 réécrite ; §9 mise à jour. Reste (session Keycloak) :
factoriser le bloc de résolution copié-collé en include partagé (DRY).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une application déclare ses liens ([{vers, role}]) dans applications.yml ;
chaque rôle décrit les liens acceptés dans meta/liens.yml. instancier
résout la cible (FQDN interne dérivé de la nomenclature) et injecte les
variables en host_vars du consommateur.
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 vars générées).
Cf. docs/bindings-conception.md.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Note d'architecture. 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 acceptés dans meta/liens.yml ; FQDN cible
dérivé de la nomenclature. Domaines = lien exposition (écrit sur l'edge).
Preuve de migration ciblée : les 3 liens mail. Implémentation phasée à suivre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
CC n'est pas faite pour du logiciel et la clause NonCommercial
contredisait « tout est libre » (et bloquait artisans/coopératives).
LICENSE = texte officiel intégral de l'AGPLv3 (non modifié). README :
attribution + notice AGPL standard + modèle libre + services/certification.
L'AGPLv3 protège la souveraineté sans interdire l'usage commercial.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Inventaire structuré des capacités du moteur : plan déclaratif,
plan de contrôle GUI, socle durci, piliers d'infrastructure prouvés
(PKI, identité, DNS, web, courriel), services outillés, patrons
d'ingénierie. Distingue « prouvé sur cluster réel » de « outillé ».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Trouvés au premier flux réel :
- lmtp_host_lookup = native (Postfix faisait du DNS-only, ignorant le
plancher /etc/hosts → « Host not found » pour le mail-store) ;
- copie de /etc/hosts, resolv.conf, nsswitch.conf, services dans le
chroot Postfix (/var/spool/postfix/etc) pour la résolution en chroot.
Résultat : Postfix atteint bien le mail-store en LMTP. Reste un nœud de
config Dovecot 2.4 (userdb LMTP renvoie NOTFOUND via auth-master) à
résoudre pour clore le flux interne.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Réception :25, validation des boîtes par carte LDAP (attribut mail),
remise LMTP réseau vers le mail-store Dovecot (virtual_transport lmtp),
TLS via step_ca (pont de cert), aucune boîte locale. main.cf +
ldap-mailboxes.cf, validé par postfix check. Bind LDAP: vault_openldap_admin.
Validé statiquement (ansible-lint, syntax, rendu main.cf) ; déploiement à suivre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute un inet_listener LMTP (port 24, variable) pour qu'un Postfix
distant (nœud edge-mta) livre par LMTP réseau. À restreindre au nœud MTA
par pare-feu en prod. Prérequis de la topologie MTA-dédié.
Validé : :24 écouté, doveconf OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Décision : séparer le MTA (Postfix+rspamd, exposé Internet) du stockage
des boîtes (Dovecot, interne). Lien Postfix→Dovecot (LMTP + SASL) en
réseau chiffré step_ca (réutilise le mTLS). Défense en profondeur : la
surface la plus exposée est isolée des données sensibles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Rôle mail : IMAP :993/:143 + LMTP, auth/annuaire LDAP (serveur_openldap),
stockage Maildir (user vmail), TLS via step_ca (pont de cert + resync au
renouvellement), auth système par défaut neutralisée. Config drop-in en
syntaxe Dovecot 2.4 (mail_driver, ssl_server_cert_file, passdb ldap),
validée par doveconf au déploiement. Sockets Postfix conditionnels à la
présence de l'utilisateur postfix (co-localisation).
Déployé et prouvé sur infra-mail-01 : doveadm auth test — bon mot de passe
accepté, mauvais refusé, cert IMAPS émis par step_ca. Format 2.4 validé
empiriquement avant l'écriture (leçon « éprouver l'outil »).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Modèle A arrêté : OpenLDAP = source de vérité des identités ; Keycloak =
SSO web OIDC fédéré à LDAP (MFA, self-service) ; mail (Dovecot/Postfix) =
bind LDAP direct. Une identité, un mot de passe, deux chemins d'auth,
même OpenLDAP. Sert la portabilité multi-tenant.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stalwart trop jeune/volatil pour un pilier mail critique (config cassée
0.15→0.16, `config apply` annoncé non livré, API REST supprimée pour
JMAP, gros backlog). Pivot vers la stack mature Postfix/Dovecot/rspamd,
100 % configurable par fichiers (alignée au modèle déclaratif Set-OPS).
- Rôle serveur_stalwart retiré (Phase 1 prototypée ; git en garde la trace).
- docs/courriel-conception.md mis à jour ; vault_stalwart_admin retiré.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Stalwart Mail Server v0.16.11, binaire unique natif. Mécaniques réelles
validées empiriquement sur le binaire :
- config.json = objet typé {"@type":"RocksDb","path":…} (DataStore seul) ;
- démarrage IaC en mode récupération (STALWART_RECOVERY_MODE +
STALWART_RECOVERY_ADMIN), admin depuis la voûte (vault_stalwart_admin).
Rôle : install version-épinglée, user système, unité systemd durcie
(CAP_NET_BIND_SERVICE, ProtectSystem), EnvironmentFile pour le secret.
Phase 2 (écouteurs/TLS step_ca/annuaire LDAP/DKIM via API d'admin) à venir.
Validé statiquement (ansible-lint, syntax, rendu config.json).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le module ansible debconf exige debconf-get-selections (paquet
debconf-utils), non installé -> échec au preconfigure du mot de passe
admin slapd. Ajout de l'install en tête de rôle.
Déploiement réel validé sur un nœud identité frais : slapd en LDAPS
(:636), certificat émis par step_ca (Verify OK), OU people/groups
créées, organisation depuis l'intrant. Avec le fix d'ordre (socle
d'abord), la chaîne socle → client_pki → serveur_openldap tient.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les groupes etaient deployes en ordre alphabetique -> client_pki (c)
passait AVANT serveur_debian (s), donc avant que hosts_statiques ne pose
/etc/hosts. Sur un noeud frais, client_pki ne pouvait pas resoudre l'AC
interne (« no such host ») et echouait au bootstrap.
L'ordre force desormais le socle en tete (serveur_debian, serveur_durci)
puis le reste : socle -> client_* -> services. Trouve en deployant un
noeud identite frais (client_pki avant socle).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- IP/PTR VÉRIFIÉ : mx.chezlepro.ca=69.70.26.53, FCrDNS OK, bloc Videotron
contrôlé → make-or-break levé pour le primaire.
- DNS public chez Namespro (pas PowerDNS) ; MX/SPF/DMARC existants à
reprendre ; DKIM à (re)poser ; MX secours .55 à re-PTR s'il émet.
- Phasage restructuré en deux étapes : (A) interne d'abord, (B) externe.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- TLS LDAPS/STARTTLS : pont du cert step_ca (client_pki, root:root 600)
vers /etc/ldap/tls lisible par openldap ; script + unité path systemd
qui re-synchronise et recharge slapd au renouvellement ; olcTLS* dans
cn=config ; SLAPD_SERVICES expose ldaps://. Dégrade proprement sans cert.
- Organisation : intrant chezlepro_organisation (remplace « Exemple Inc »).
Validé statiquement (ansible-lint, syntax) ; déploiement réel à suivre.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Décisions : vrai service (boîtes/IMAP), full self-host, suite Stalwart
enveloppée par un rôle mince, identité via LDAP, PKI Let's Encrypt
(public) + step_ca (interne). Topologie MX primaire + MX secours,
enregistrements DNS publics, prérequis bloquant IP/PTR, décisions
ouvertes et phasage. Conception seulement.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
step ca certificate ne recevait aucun --san explicite. Ajout d'une liste
client_pki_sans (FQDN + nom court + IP) et d'une boucle --san dans la
commande. Le cert porte désormais DNS:fqdn, DNS:court, IP:adresse — avec
EKU Server+Client Auth, prêt pour un mTLS bidirectionnel. Vérifié sur
infra-dns-01 (ré-émission).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Debian 13 livre « server_tokens off; » actif dans nginx.conf ; notre
drop-in 99-setops.conf le redéclarait → nginx -t « directive is
duplicate » → déploiement planté au handler de validation. Le rôle
neutralise maintenant la ligne distro (replace idempotent) ; le drop-in
reste l'unique source. Trouvé en déployant nginx pour de vrai.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Une voûte chiffrée group_vars/all/vault.yml fait échouer
ansible-inventory --list (comparaison du plan) sans mot de passe →
« Appliquer le plan » cassé dès qu'une voûte existe. instancier utilise
maintenant ~/.config/setops-vault-pass si ANSIBLE_VAULT_PASSWORD_FILE
n'est pas déjà fourni. Validé : plus d'exit 4.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le rôle ajoute bien le dépôt Smallstep et installe step-cli/step-ca —
il n'était PAS cassé. Mais en --check, le dépôt/binaire ne sont pas
réellement présents → apt « No package step-cli » puis « step ca init »
échouaient (faux). Ajout de when: not ansible_check_mode sur l'install
et l'initialisation de l'AC (le service l'avait déjà).
Dry-run step_ca : failed=0 (prouvé avec vault_* de test). Config dépôt
vérifiée (clé officielle, stable/debian suite debs) — vrai déploiement
non exécuté (nécessite la voûte + action GUI).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les rôles à secrets déclaraient serveur_X_password: "" avec la variable
de voûte seulement en commentaire — aucun mapping réel. Remplir la voûte
ne branchait donc rien (secret vide → assertion échoue).
Les 12 secrets des 8 rôles pointent maintenant vers leur source :
serveur_X_password: "{{ vault_X | default('') }}"
Comportement inchangé si la voûte est vide ; branché dès qu'elle a le
secret. Prouvé : assertion step_ca passe avec vault_step_ca_password.
Chaque instance remplit ses vault_* ; aucun mapping par instance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sans lui, le dry-run ne peut pas déchiffrer les secrets → les rôles à
secrets (step_ca, etc.) échouaient au Vérifier même avec des secrets
présents, et une voûte chiffrée aurait bloqué TOUT Vérifier. La modale
autorise un mot de passe vide (hôtes sans secrets = comportement actuel).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
« Vérifier » échouait faussement sur un hôte frais : les tâches
« démarrer service » et les handlers restart/reload/validate touchent
un paquet que --check n'installe pas vraiment → service/fichier absent
→ faux fatal, qui bloquait le déploiement (le dry-run doit passer pour
débloquer « Déployer »).
Ajout de « when: not ansible_check_mode » sur ces tâches + handlers des
13 rôles serveur_* (29 gardes). Sautées en dry-run, inchangées en réel.
Validé : powerdns dry-run failed=0 ; ansible-lint 0 échec ; 19 playbooks
syntax-OK.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug racine : /api/instancier appelait « instancier appliquer » SANS
--force → refus silencieux dès que le plan divergeait de l'inventaire.
Après une édition (disque, état, auto-actif), l'inventaire n'était jamais
régénéré → compteurs figés (« 0 actifs » alors qu'un hôte était actif au
plan). Le clic « Appliquer » = intention explicite → on force.
Aussi :
- après matérialisation (creer/deployer/pousser), le frontend régénère
l'inventaire (appliquerPlan) au lieu d'un simple rechargement ;
- compteur « vivantes » dans l'en-tête (depuis la sonde).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le badge planifié/actif = intention du plan, pas existence de la VM.
- Sonde de vie : /api/sondes teste la joignabilité SSH de chaque hôte
(parallèle, non bloquant) → point ● vivante/injoignable sur les tuiles
et dans le détail (« Plan : … · Réel : … »).
- Auto-actif : un hôte cloné (creer) ou déployé passe actif dans le plan
automatiquement (matérialisé = actif).
Répond au constat « serveur planifié alors qu'il est vivant ».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Chaque objet du détail se matérialise sur son hôte, en streaming console
(vault + confirmation prod) :
- Serveur → 🖥 Pousser clone la VM (make creer-vm), même sur un planifié —
comble le trou : le GUI ne clonait pas.
- Application → Pousser déploie l'hôte porteur.
- Base → Pousser déploie l'hôte du serveur de BD (crée la base).
Nouveaux modes creer/pousser dans executer_flux + routes /api/creer et
/api/pousser. Flux complet : éditer → Appliquer → Pousser → Vérifier →
Déployer, sans quitter le navigateur.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- clonage/Makefile : chemin proxmox codé en dur `inventories/lab` →
détection lab>principal>production (débloque les instances principal)
- resize disque grow-only : tolère le cas disque dérivé < template
(shrinking not supported), la VM garde le disque du template
- PowerDNS : ne plus redéclarer launch+=bind (déjà posé par le paquet
pdns-backend-bind) → « multiple backends 'bind' » résolu
Trouvés en déployant réellement infra-dns-01 (clone + socle durci +
PowerDNS qui résout) sur le cluster Proxmox.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Depuis la fusion maître-détail, elle ne répétait que le socle (universel),
les rôles des applications (déjà listées) et les intégrations (déjà
cochées). Le prérequis bloquant — son seul signal unique — est désormais
nommé dans le pied. Code mort retiré (renduGroupe, detailGroupe,
titreGroupe, CSS .groupe*).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nouveau rôle de socle hosts_statiques (dans serveur_debian) : génère
/etc/hosts sur chaque VM depuis l'inventaire. L'écosystème se résout par
nom même DNS éteint, et le bootstrap ne dépend plus du DNS. client_dns
rendu tolérant (inerte sans DNS interne) ; dépendance client_dns→powerdns
passée molle. PowerDNS devient une commodité (zone/externe).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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. Convention : supernet =
10.(index*10).0.0/16, VMID = index·CSNN. Permet à N écosystèmes de
coexister sans collision (Chezlepro=1, Technolibre=2). Sans index →
9CSNN (rétro-compatible, bacs à sable en 172.19.x). deriveServeur JS
mort retiré. Doc multi-instances.md mise à jour.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
make instance-utiliser NOM=… repointe le symlink 'instance' (prod ↔ bac à
sable) ; make instance-courante affiche le montage. Le panneau Intrants lit
l'identité dans group_vars/all/10-intrants.yml de l'inventaire monté (réel
ou symlink partagé), donc compatible avec la séparation par instance comme
avec l'ancien partage lab/production.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le déploiement réel est permis sur TOUTE instance (un bac à sable déploie
sur son infra lab : isolé ET fonctionnel). Le drapeau ne bloque plus ;
il marque la PROD pour exiger une confirmation renforcée (badge/bouton
rouge, re-saisie du nom d'hôte). executer_flux ne refuse plus selon le
drapeau. Badge « PROD » / « bac à sable » dans l'en-tête.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le moteur ne code plus en dur inventories/lab|production : un inventaire
par instance, détecté de façon rétro-compatible (principal > production >
lab) et surchargeable par SETOPS_INVENTAIRE. Makefile (définitions seules,
pas les 47 usages), inventory_gui, instancier, config_proxmox, serveurs,
applications. Les instances lab/production existantes marchent à
l'identique ; les nouvelles adoptent inventories/principal/.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le droit de déployer ne dépend plus du nom de l'inventaire mais d'un
drapeau explicite setops_production dans group_vars/all/ de l'instance
(true = prod, false = bac à sable). Le GUI (champ "production" de l'API) et
executer_flux le lisent ; rétro-compatible (à défaut, ancien repère
"inventaire production"). Première pierre de la séparation par instance.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Fini les voûtes éparpillées : tous les secrets (token Proxmox + vault_*)
vivent dans group_vars/all/vault.yml, un seul fichier chiffré, un seul mot
de passe. Gabarit committé exemples/vault.exemple.yml (17 clés). make config
écrit/édite cette voûte (semée depuis le gabarit si absente). .gitignore
durci (**/vault.yml). Docs : config-proxmox.md (+ migration), intrants-
communs.md §H, QUICKSTART. Le GUI ne stocke aucun secret.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
L'intrant de base global chezlepro_timezone était défini mais jamais
appliqué : le rôle chrony règle désormais le fuseau horaire (chrony_timezone,
vide = ne pas toucher). Nettoyage du CSS/HTML devenu mort après la refonte
maître-détail (.message, .chip-f, .onglet*, .base-ligne, .ch-grp*, etc.).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Toutes les vues adoptent le même motif que l'Inventaire : tuiles à
gauche, détail + saisie à droite, panneau droit contextuel (fin du
panneau figé au changement de vue). Applications et Bases passent en
maître-détail ; liens cliquables entre objets.
Les vues Inventaire (hôtes) et Serveurs (plan) faisaient doublon : elles
sont fusionnées en une vue Serveurs unique — tuile avec statut de
réconciliation, détail réunissant identité éditable, dérivés, groupes,
applications/bases hébergées et Vérifier/Déployer. Navigation clavier et
filtre rebranchés sur les serveurs ; bandeau « Comment lire ce parc ».
Code mort retiré (carte, renduChaine, ONGLETS, champLecture, sélection
d'hôte, chips de filtre).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Son contenu (renduChaine) est déjà accessible, 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. Retire le bouton, le rendu dessinerArbre et
le CSS .arbre-* ; raccourcis clavier ramenés à 1-4. La chaîne reste
consultable hôte par hôte.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les champs libres deviennent des sélections alimentées par les paramètres
globaux : Nœud et Stockage puisent dans de nouveaux catalogues
proxmox_noeuds / proxmox_stockages (vide = défaut global), Intégrations
liste les rôles client_* disponibles (scan roles/client_*). Les catalogues
(Nœuds/Stockages/Ponts) sont des intrants de classe « Catalogue » éditables
dans le panneau Intrants ; ajout du type `liste` (chaîne virgulée → liste
YAML dédoublonnée) et de integrations_disponibles dans l'API.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Notifications empilées (toasts) au lieu d'une bannière écrasée, durée
d'exécution en direct sur les opérations longues, navigation clavier
(1-5 / j-k / v / d / Ctrl+S contextuel), validation inline des champs du
plan (vue Serveurs), garde-fou beforeunload, bouton Sauvegarder contextuel
(fin de l'impasse 409).
Convertit le détail de la vue Inventaire (cartes) en inspection lecture
seule : la vue annonçait « généré depuis le plan » tout en exposant une
surface d'édition non persistable. Retire les contrôles orphelins
(+ Hôte, ✕, bascule d'état, ✨ Proposer, champs et groupes éditables) et
le code mort associé ; conserve Vérifier / Déployer et les onglets. CSS
inutilisé nettoyé. L'édition reste dans Serveurs / Applications / Bases.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ajoute docs/config-proxmox.md : référence champ par champ des 16 paramètres
Proxmox non sensibles + les secrets API demandés par l'assistant (sens,
défaut, quoi saisir, constante vs défaut surchargeable), plus l'annexe de
création du token API. Renvois ajoutés depuis make help et QUICKSTART.md.
Ces invites n'étaient expliquées nulle part de façon pérenne.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Nouveau panneau « ⚙ Intrants » pour saisir d'un endroit unique les
valeurs communes à l'écosystème, avec distinction constantes (non
surchargeables) vs défauts (surchargeables dans les instances). Les
secrets ne sont jamais saisis ni affichés (garde INTRANTS_CLES_INTERDITES
+ filtrage par schéma) ; un domaine_interne vide est refusé et son
changement demande confirmation. Ajoute aussi les info-bulles d'aide au
survol des champs. domaine_interne/chezlepro_timezone consolidés en une
source partagée (inventories/partage/intrants-identite.yml).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>