Servie par le runner d un SITE, la console montrait zero serveur sans une erreur : serveur_ops retire le lien instance sur un hebergeur, charger_yaml rend un inventaire vide sur un fichier absent, et la page dessinait ce vide comme un plan vide. La portee se DERIVE des deux symlinks (instance = je configure, underlay = je materialise), jamais d un reglage declare. Une console de site sert desormais son inventaire dynamique ; POUVOIR_REQUIS exige un pouvoir pour chaque route POST et le refus dit pourquoi ; la page nomme la console. P81 refuse une portee sans source d inventaire, un site sans machines, et toute route qui echapperait a la table. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
30 KiB
AGENTS.md — Set-OPS
Rôle du dépôt
Ce dépôt contient les playbooks, rôles, inventaires et templates Ansible servant à construire, configurer, maintenir et documenter les serveurs d’un écosystème numérique souverain.
Set-OPS est le moteur global d’exploitation d’écosystèmes numériques souverains.
Le template Debian 13 Proxmox est seulement un sous-ensemble du dépôt. Le dépôt ne doit jamais être restructuré autour d’un seul besoin ponctuel.
Mission et identité
Au-delà de l’exploitation, Set-OPS définit et construit un écosystème numérique souverain : une infrastructure interne auto-suffisante, sans dépendance SaaS, dont tous les piliers sont décrits en code et reliés entre eux.
Piliers de l’écosystème :
- identité — machines (PKI / certificats) et utilisateurs (annuaire + SSO) ;
- confiance — autorité de certification interne (ACME) ;
- nommage et adressage — DNS interne et nomenclature dérivable ;
- données — bases relationnelles et cache, avec registre des connexions ;
- communication — service de courriel souverain (boîtes LDAP, SMTP, IMAP, antispam, DKIM) ; le relais des notifications système en est distinct (
client_smtp) ; - observabilité et supervision — métriques, journaux, tableaux de bord, supervision active ;
- applicatif — services internes (forge, etc.) et couche web.
Propriétés visées, avec leurs nuances honnêtes :
- Souverain / auto-suffisant : c’est l’objectif. Aucune dépendance à un service externe pour la confiance, l’identité, le nom ou la communication. Cela justifie le choix de construire plutôt qu’assembler des SaaS.
- Déclaratif et convergent : l’état voulu est décrit dans des registres machine-lisibles (
instance/plan/nomenclature.yml,docs/dependances-groupes.yml,instance/plan/bases-donnees.yml) et appliqué par les groupes Ansible. Ces registres sont la source unique de vérité. - Pas (encore) auto-réparé : la convergence est pilotée par l’opérateur (GUI / CLI /
make), pas une boucle fermée d’auto-remédiation. - Éprouvé sur VM réelles — mais la nuance tient toujours. Cette ligne a dit, jusqu’au 2026-09-06, que « une grande partie est planifiée et validée mais pas encore exécutée contre des VM réelles ». C’est faux depuis longtemps : la flotte a été rasée et remontée depuis zéro le 2026-08-13, puis deux fois le 2026-09-02 (15/15 puis 14/14 hôtes, 0 échec,
make validerà 0 échec sur 13 hôtes). Ce qui reste vrai, et qu’il faut garder : un--syntax-checkvert ne prouve rien de l’exécution, et le tableau de maturité dedocs/catalogue-services.md— pas ce fichier — dit ce qui est éprouvé et ce qui ne l’est pas. Ne jamais présenter comme « en production » un rôle que ce tableau ne donne pas pour tel.
Conséquence pour le travail : préserver la discipline qui tient l’ensemble — registres comme source unique, dépendances explicites, validation de chaque pièce, secrets hors dépôt. C’est ce qui empêche l’écosystème de devenir un objet ingérable. Le template Debian 13 reste la fondation (le moule des VM), pas la finalité.
Le plan et la génération de l’inventaire (méta-classe)
Set-OPS se pilote par un plan, pas par l’édition directe de l’inventaire.
L’inventaire Ansible est généré depuis le plan.
RÈGLE D’OR : instance/inventories/<inventaire>/hosts.yml est un artefact GÉNÉRÉ. Ne jamais l’éditer à la main. On édite le plan, puis on régénère.
<inventaire> est une place, pas un nom : le moteur le résout (principal, sinon production — scripts/inventory_rules.py, ORDRE_INVENTAIRE). La flotte dit principal, le modèle public dit production. Ne coder ni l’un ni l’autre en dur dans un document.
- L’application est l’entité pivot ; le groupe Ansible n’est qu’une capacité (le rôle appliqué), plus une cible de liaison.
- Le plan vit dans des registres machine-lisibles :
instance/plan/serveurs.yml(les VM),instance/plan/applications.yml(les services et leurs liensrequiert/utilise/expose),instance/plan/bases-donnees.yml,instance/plan/domaines.yml,instance/plan/nomenclature.yml. - Génération :
make instancier(génère + diff sémantique),make instancier-appliquer(régénèrehosts.yml, refuse si le diff n’est pas vide —FORCE=1pour un changement intentionnel). - Flux : éditer le plan →
make instancier(revoir le diff) →make instancier-appliquer→ déployer. Via le GUI : vues Serveurs et Applications, puis « Appliquer le plan » (la vue Inventaire est en lecture seule). - VMID / IP / VLAN / passerelle sont dérivés de la
fonctionvia la nomenclature ; les groupes d’une VM sont dérivés (socle + services des applications + intégrations + état). - Garde-fous : validateurs de registres, diff-vide,
node --checkdu JS du GUI (scripts/verifier_gui.py, dansmake inventaire-verifier), git comme filet.
Référence complète : docs/plan-et-generation.md.
Plan de contrôle gelé en périmètre : le GUI, le générateur et la modélisation sont volontairement maison et souverains, mais leur périmètre est gelé. Ne pas y ajouter de fonctionnalités de type NetBox/AWX — historique d'audit applicatif, API riche, source de vérité partagée entre organisations : le besoin réel d'une de ces fonctions est le signal d'adopter l'outil mûr correspondant (NetBox pour la source de vérité, AWX pour l'exécution), pas de le réimplémenter.
Avant d'invoquer un seuil, vérifier qu'il n'est pas déjà couvert autrement (D-84). Deux exemples cités ici jusqu'au 2026-09-08 ne tenaient plus : le RBAC est assuré par la séparation cryptographique des voûtes et des runners — l'adopter d'AWX serait régresser ; la détection de conflits IPAM est sans objet, rien ne s'alloue et cinq preuves (P20, P21, P23, P28, P33) tiennent déjà ce qu'un IPAM vérifierait. Une carte des seuils fausse ne fait pas perdre du temps : elle fait franchir un seuil qui ne l'est pas.
Le gel porte sur les FONCTIONS, jamais sur les VUES. Montrer à l'écran ce que le moteur sait déjà — l'écart d'un devis, l'état du diff, le périmètre sur lequel un ✅ a porté — ne franchit aucun seuil. Décision et seuils : docs/positionnement.md.
Règle d’or IA
Un seul agent IA travaille dans ce dépôt à la fois.
- Soit Codex.
- Soit Claude Code.
- Jamais les deux simultanément.
Tout changement significatif doit être consigné dans CHANGELOG.md.
Principes
- Toujours lire l’existant avant de modifier.
- Ne jamais introduire de secret en clair.
- Ne jamais casser l’idempotence Ansible.
- Ne jamais exécuter d’action destructive sans confirmation explicite.
- Préférer la simplicité à l’ingénierie excessive.
- Documenter ce qui est utile à l’exploitation réelle.
- Préférer les correctifs ciblés aux régénérations massives.
- Ne jamais supposer qu’un rôle est complet sans l’avoir inspecté.
- Ne jamais déclarer un playbook prêt si la validation minimale échoue.
- Impératif : Set-OPS doit rester pleinement exploitable par un humain SANS IA. La doc,
makeet le GUI sont l’interface primaire et complète. Ne jamais introduire de fonctionnalité qui exige une IA pour s’en servir. L’IA n’assiste que le mainteneur pour faire évoluer l’outil — jamais l’utilisateur final. (Le bon étalon : un sysadmin humain réussit depuis la doc.AGENTS.md/CLAUDE.mdne font pas partie de l’outil livré.)
Sécurité
Ne jamais commiter :
- mots de passe ;
- clés privées SSH ;
- tokens API ;
- secrets non chiffrés ;
- fichiers
.envsensibles ; - certificats privés ;
- backups réels ;
- exports de production non anonymisés.
Les secrets doivent être gérés hors dépôt ou avec un mécanisme explicitement prévu, par exemple :
- Ansible Vault ;
- fichier local non versionné ;
- secret injecté hors dépôt ;
- gestionnaire de secrets approuvé.
Actions destructives
Toute action destructive doit exiger une variable explicite, par exemple :
confirm_destructive_action: true
Sont considérées comme destructives ou risquées :
- modification bloquante de SSH ;
- activation ou modification d’un pare-feu ;
- suppression d’utilisateurs ;
- suppression de paquets critiques ;
- formatage disque ;
- modification de partitions ;
- redémarrage massif ;
- purge de données ;
- changement réseau pouvant couper l’accès ;
- modification d’un hyperviseur Proxmox ;
- opération sur stockage, iSCSI, ZFS ou Ceph.
Sans confirmation explicite, refuser l’exécution.
Règle de modification du dépôt
Avant toute modification, exécuter ou demander l’équivalent de :
git status --short
find . -maxdepth 3 -type f | sort
Ne pas remplacer massivement l’arborescence sans demande explicite.
Avant de modifier un fichier, lire son contenu actuel.
Après modification, indiquer clairement :
- les fichiers créés ;
- les fichiers modifiés ;
- les commandes de validation exécutées ;
- les tests non exécutés ;
- les limites connues.
Validation Ansible obligatoire
Avant de proposer un changement comme terminé, vérifier au minimum la syntaxe du playbook touché.
Exemple pour le template Debian 13 Proxmox :
ansible-playbook -i "$SETOPS_INVENTAIRE" playbooks/modeles_vm/debian13_proxmox_preparer.yml --syntax-check
SETOPS_INVENTAIRE est exporté par le Makefile, qui résout le nom de l’inventaire au lieu de le coder en dur (cf. la règle d’or ci-dessus) — la variable est donc déjà là dans toute recette make. Pour couvrir tous les playbooks d’un coup : make syntaxe. Depuis un shell nu, la variable n’existe pas : passer le chemin réel de l’inventaire de l’instance montée.
Pour un autre playbook, remplacer le chemin par le playbook concerné.
Ne jamais déclarer un playbook prêt si --syntax-check échoue.
Si ansible-lint est disponible, l’utiliser :
ansible-lint
Si ansible-lint n’est pas disponible, le signaler clairement. Ne pas inventer un résultat.
Écrire, puis relire (D-68)
--syntax-check et ansible-lint prouvent que le dépôt est cohérent avec lui-même.
C'est aussi ce que font les 81 preuves de make prouver : elles lisent le dépôt, sans le
moindre appel réseau. Aucune ne demande au système déployé s'il ressemble à ce que le
dépôt annonce.
C'est dans cet angle que vivaient les défauts du 2026-08-08 : une politique de mot de passe déclarée des deux côtés et appliquée d'aucun, une entrée LDAP figée à sa création, le certificat de l'autorité expiré depuis huit heures, la livraison de courriel interne différée en silence. Tous découverts en relisant après avoir écrit, aucun signalé par un test.
La règle : écrire, puis relire et comparer — quelle que soit l'interface. Choisir
celle dont le chemin de lecture parle le même langage que le chemin d'écriture. Ce
n'est pas « toujours préférer l'API » : la plupart de la flotte n'en a pas, et sur six
familles de défauts ce jour-là, deux seulement venaient d'un CLI — un module Ansible
(ldap_entry, qui crée sans jamais modifier) a commis la même faute.
Cas connu à ne pas réapprendre : kcadm -s sur une map (smtpServer, attributes,
config) accepte la commande, sort en succès et n'écrit rien. Passer par l'API
d'administration pour ces objets, et relire (D-69).
Les devis de service industrialisent cette relecture — voir docs/devis-services.md :
make identite-plan make certificats-plan make expositions-plan
make postgresql-plan make courriel-plan
Le même patron existe sous les services, pour le monde physique — make frontiere-plan,
proxmox-fw-plan, sdn-plan, underlay-plan, placement-plan.
Ils ne modifient rien et sortent en code 1 s'il y a un écart. Après un changement qui touche l'identité, les certificats, une exposition, la base ou le courriel, lancer le devis correspondant : une tâche verte ne prouve pas que le service rend son service.
Règle pour les handlers Ansible
Chaque rôle qui utilise notify doit contenir son handler dans le rôle lui-même.
Exemple :
roles/ssh_baseline/tasks/main.yml
roles/ssh_baseline/handlers/main.yml
Ne pas dépendre d’un handler défini dans un autre rôle, sauf justification explicite.
Tout notify doit pointer vers un handler existant.
Commandes de vérification utiles :
find roles -path '*/tasks/*.yml' -exec grep -H "notify:" {} \;
find roles -path '*/handlers/main.yml' -print
Avant de livrer un rôle, vérifier que chaque handler référencé existe réellement.
Style Ansible attendu
Les playbooks doivent être :
- idempotents ;
- lisibles ;
- sobres ;
- compatibles Debian 13 sauf exception documentée ;
- testables avec
--checkautant que possible ; - sécuritaires par défaut ;
- sans dépendances SaaS ou cloud inutiles.
Préférer les modules Ansible standards :
ansible.builtin.aptansible.builtin.templateansible.builtin.copyansible.builtin.serviceansible.builtin.systemdansible.builtin.lineinfileansible.builtin.fileansible.builtin.useransible.builtin.group
Éviter shell et command sauf nécessité réelle.
Lorsqu’une commande shell est nécessaire, elle doit être encadrée avec les paramètres appropriés selon le cas :
changed_whenfailed_whencreatesremoves
Structure générale du dépôt
Ce que la racine porte réellement (mesuré le 2026-09-06) :
roles/ les rôles Ansible
playbooks/ les playbooks, classés par domaine
scripts/ le moteur de plan, la GUI, les devis, les preuves
filter_plugins/ les filtres Jinja du dépôt
docs/ wiki/ la documentation
exemples/ les modèles d'instance prêts à copier
instance/ SYMLINK vers le dépôt de l'instance active — jamais un vrai dossier ici
Il n'y a ni inventories/, ni templates/, ni files/ à la racine, et il ne doit pas y en avoir : les inventaires appartiennent à l'instance (derrière le symlink), les gabarits et fichiers appartiennent à leur rôle.
Les playbooks sont classés par domaine :
playbooks/
├── groupes/ un playbook par groupe opérationnel — la conformité passe par là
├── maintenance/ les devis et les manœuvres ponctuelles
├── modeles_vm/ la fabrication du gabarit doré
├── proxmox/ le clonage et le cycle de vie des VM
└── applications/ backup/ database/ monitoring/ web/ — vides, un README d'espace réservé
Seuls les quatre premiers sont peuplés. Les cinq autres ne contiennent qu'un README : ce sont des espaces réservés, et leur existence contredit à demi la règle « ne pas créer de structure inutile » ci-dessous — les laisser vides est un choix assumé, en créer d'autres ne l'est pas.
Les rôles peuvent être ajoutés progressivement selon les besoins.
Ne pas créer de structure inutile uniquement pour donner une impression de complétude.
La conformité normale des VM déployées doit passer par playbooks/groupes/.
Ne pas maintenir en parallèle des playbooks de couches génériques comme playbooks/socle/ ou playbooks/durcissement/ lorsqu'un groupe opérationnel exprime déjà cet état voulu.
Interface opérateur Makefile
Le Makefile est l'interface opérateur privilégiée pour les gestes courants.
Les commandes make doivent simplifier l'exploitation Ansible sans masquer les playbooks réellement exécutés.
Préférer quelques cibles claires et utiles :
- validation du dépôt ;
- inspection des inventaires ;
- ajout ou mise à jour d'un hôte dans un inventaire ;
- association d'un hôte à ses groupes ;
- déploiement ou remise en conformité d'un hôte ;
- déploiement ou remise en conformité d'un groupe ;
- préparation, vérification et nettoyage protégé d'un modèle de VM.
Ne pas multiplier les cibles make secondaires si elles ne correspondent pas à un geste réel d'exploitation.
Les cibles d'exploitation des VM doivent privilégier les groupes :
make deployer HOTE=web-frontal-01
make deployer-groupe GROUPE=serveur_debian
make hote-planifier, hote-ajouter et hote-groupes sont DÉPRÉCIÉES — elles refusent et sortent en 2. Elles éditaient l'inventaire à la main, ce que la RÈGLE D'OR interdit. Pour ajouter un hôte : le déclarer dans instance/plan/serveurs.yml (ou la vue Serveurs du GUI), puis make instancier-appliquer. Le VMID n'est plus saisi : il est dérivé (neuf chiffres, miroir de l'IP — 117602101, et non le format à cinq chiffres d'avant).
Éviter les cibles parallèles qui réappliquent les mêmes rôles par couche, par exemple make socle, make durcissement, make converger ou make deployer-vm.
Toute cible make qui lance une action destructive ou risquée doit exiger une confirmation explicite.
Convention groupes et playbooks
Chaque groupe opérationnel Ansible doit avoir un playbook homonyme dans playbooks/groupes/.
La convention attendue est :
groupe Ansible : serveur_debian
playbook : playbooks/groupes/serveur_debian.yml
L'appartenance aux groupes détermine les services, intégrations et politiques appliqués à une VM.
playbooks/groupes/ est la source officielle de conformité pour les VM déployées.
Un playbook de groupe doit cibler son groupe homonyme, pas all, sauf justification explicite.
Les concepts comme socle Debian ou durcissement commun doivent être représentés par des groupes explicites :
serveur_debian -> socle commun Debian
serveur_durci -> durcissement commun
client_pki -> intégration cliente PKI / ACME
Ne pas dupliquer ces mêmes rôles dans des playbooks de couches séparés.
Quand un nouveau groupe opérationnel est ajouté :
- créer le playbook homonyme dans
playbooks/groupes/; - documenter son intention opérationnelle ;
- déclarer ses dépendances causales dans
docs/dependances-groupes.ymlsi son exécution requiert un autre service actif ; - prévoir les rôles nécessaires ;
- valider au minimum sa syntaxe ;
- l'ajouter aux facilités d'exploitation si l'opérateur doit l'utiliser directement.
Cycle de vie et conformité des VM
Set-OPS doit gérer le cycle de vie complet des VM de l’instance :
VM Debian minimale
→ goldenisation du template
→ clonage
→ identité initiale cloud-init
→ conformité par groupes Ansible
→ intégrations transversales
→ conformité continue
Le template est seulement la fondation. Les VM existantes et futures doivent converger vers l'état voulu par les playbooks de groupes.
Les rôles et playbooks doivent donc être conçus pour être relancés régulièrement, sans effet secondaire inutile.
Le groupe d'inventaire attendu pour les VM Debian gérées est :
serveur_debian
Les groupes spécialisés doivent s'ajouter selon les besoins réels, par exemple :
client_pki
client_metrique
serveur_postgresql
serveur_prometheus
serveur_keycloak
Ne pas cibler all par défaut pour un playbook qui ne s'applique pas réellement à tous les hôtes.
Services centraux et intégrations clientes
Pour chaque service d'infrastructure, distinguer deux responsabilités :
service serveur : installe et configure le service central
intégration cliente : raccorde les VM au service central
Exemples :
PowerDNS serveur → hosts_statiques (plancher) + client_resolveur (opt-in)
step-ca serveur → client_pki (certificat + renouvellement + rechargement du service)
LDAP serveur → resoudre_annuaire, et les applications qui s'y lient (Postfix, Dovecot, Keycloak)
Keycloak serveur → intégrations OIDC applicatives, ou serveur_oauth2_proxy si l'app n'a pas d'OIDC
Prometheus → client_metrique (node_exporter) sur les VM
Icinga2 → SANS AGENT : contrôles actifs depuis le cœur, résultats passifs poussés par l'API
Grafana → datasources et dashboards côté plateforme
Deux précisions qui ont manqué ici longtemps, et qui changent la conception d'un nouveau service :
- Pas de login LDAP au niveau du système.
SSSD/NSS/PAMne sont pas la couche cliente de l'annuaire : le rôleclient_ldapa été retiré (2026-07-04), hors conception. L'annuaire sert les applications, pas l'ouverture de session Unix. - Pas d'agent de supervision. Icinga ne pose rien sur les hôtes. Un nœud qui doit rapporter le fait lui-même, en poussant son résultat à l'API — c'est ainsi que
client_backuprapporte l'état de son propre dépôt. Ne pas prévoir de rôle « agent ».
Quand un nouveau service central est ajouté, prévoir aussi le ou les rôles clients nécessaires pour intégrer les VM existantes et futures.
Les dépendances entre groupes doivent être déclarées dans :
docs/dependances-groupes.yml
Ces dépendances servent à refuser un déploiement lorsque les prérequis actifs sont absents et à préparer les futurs checks de supervision.
Les intégrations clientes doivent être :
- idempotentes ;
- activables par inventaire ou variables ;
- désactivées par défaut si leur dépendance centrale n'existe pas ;
- documentées avec leurs prérequis ;
- testables avec
--syntax-checket, lorsque possible,--check.
Ne pas mettre les intégrations applicatives ou les agents spécialisés dans le golden template, sauf justification opérationnelle explicite.
Variables et inventaires
Les variables de rôle doivent utiliser un préfixe correspondant au rôle.
Exemples :
ssh_hardening_port: 22 # rôle ssh_hardening
nftables_baseline_enabled: false # rôle nftables_baseline
fail2ban_ssh_enabled: true # rôle fail2ban_ssh
Le préfixe est le nom du rôle, tel qu'il est, pas sa traduction : ces trois lignes sont copiées des defaults/main.yml réels. (Elles disaient ssh_durcissement_port et nftables_socle_enabled jusqu'au 2026-09-06 — deux préfixes qui ne correspondaient à aucun rôle, dans l'exemple censé illustrer la règle.)
Séparer clairement :
variables de template : construction du golden template
variables de conformité : état voulu des VM déployées
variables applicatives : services et rôles spécialisés
Les variables de conformité ne doivent pas couper l'accès SSH, DNS ou réseau sans validation explicite.
Toute variable susceptible de provoquer une action destructive, bloquante ou irréversible doit exiger une confirmation explicite.
Langue et nommage
La surface destinée à l'opérateur doit être en français.
À franciser :
- noms de groupes d'inventaire ;
- cibles
make; - variables de commande maison ;
- libellés et messages des scripts maison ;
- noms des playbooks maison quand ils sont exposés à l'opérateur ;
- documentation d'exploitation.
À ne pas franciser automatiquement :
- mots-clés Ansible (
hosts,roles,tasks,handlers,vars,become) ; - noms de modules Ansible (
ansible.builtin.apt,ansible.builtin.template, etc.) ; - conventions techniques imposées par un outil ;
- noms de rôles existants, sauf demande explicite ou migration ciblée justifiée.
Les variables de rôle peuvent rester alignées sur le nom technique du rôle, mais elles doivent conserver un préfixe clair et stable.
Ne pas faire de renommage massif uniquement pour franciser si cela augmente le risque sans bénéfice opérationnel immédiat.
Template Debian 13 Proxmox
Le template Debian 13 Proxmox doit rester un socle commun.
Il peut contenir :
- Debian minimal ;
- SSH ;
- sudo ;
- compte technique
ansible; - sudo NOPASSWD pour
ansiblelorsque requis ; qemu-guest-agent;cloud-init— au gabarit seulement : retiré parserveur_durciune fois la VM née (D-85) ;cloud-guest-utils(growpart) — conservé : ni service, ni source de données ;- chrony ;
- outils de diagnostic de base ;
- durcissement raisonnable ;
- AppArmor ;
- auditd ;
- fail2ban SSH ;
- unattended-upgrades ;
- configuration journald ;
- sysctl de sécurité ;
- nftables installé et préparé, mais pas forcément activé.
Il ne doit pas contenir par défaut :
- NGINX ;
- PostgreSQL ;
- MariaDB ;
- Docker ;
- Podman ;
- Redis ;
- GitLab ;
- Nextcloud ;
- monitoring complet ;
- agents applicatifs spécialisés ;
- données propres à un clone ;
- secrets ;
- clés privées.
Les services spécialisés doivent être installés ensuite par des playbooks dédiés sur les clones.
SSH
Le template Debian 13 Proxmox doit être construit avec un accès SSH par clé dès le départ.
Cloud-init doit injecter le ciuser et sa clé publique avant l'exécution d'Ansible.
État attendu :
PasswordAuthentication no
PermitRootLogin no
PubkeyAuthentication yes
AuthenticationMethods publickey
Ne pas lancer le playbook de préparation tant que l'accès SSH par clé au compte technique n'est pas confirmé.
Toute modification SSH doit valider la configuration avant rechargement :
sshd -t
Pare-feu
Ne pas activer un pare-feu générique dans un template sans confirmation explicite.
Pour le template Debian 13, nftables peut être installé et préparé, mais rester désactivé par défaut.
L’activation du pare-feu doit être faite sur un clone ou sur un serveur final. Ses règles ne s’écrivent pas à la main : elles sont dérivées du registre des flux — chaque rôle déclare ce qu’il reçoit dans son meta/flux.yml, et scripts/resoudre_flux.py en produit le jeu de règles. Le même registre alimente le pare-feu de l’hyperviseur et la frontière OPNsense, ce qui leur interdit de se contredire. Voir docs/flux-conception.md et docs/registre-flux.md.
Cloud-init
Cloud-init sert à donner l’identité initiale d’un clone :
- hostname ;
- utilisateur initial ;
- clé SSH ;
- adresse IP ;
- passerelle ;
- DNS ;
- agrandissement de la partition racine.
Cloud-init ne doit pas remplacer Ansible pour la configuration applicative.
Séparation attendue :
Proxmox + cloud-init : identité initiale de la VM
Set-OPS + Ansible : configuration réelle du serveur
Et cloud-init ne survit pas à cette première seconde (D-85, 2026-09-09). Il se réveille
à chaque démarrage et relit le lecteur attaché par l'hyperviseur — lequel peut redéfinir
comptes, clés SSH, mots de passe et réseau. Sur une machine que le plan possède, c'est un
second maître, que le plan ne décrit pas. Le groupe serveur_durci le retire donc
(cloud_init_retrait), et le socle ne l'installe plus : le garder aux deux endroits
produisait un va-et-vient à chaque déploiement. Le gabarit, lui, le garde — sans lui un
clone n'a ni adresse ni nom. P63 garde les trois moitiés.
Ce que ce retrait ne ferme pas. Il n'ôte aucun pouvoir à l'hébergeur :
qemu-guest-agent est au gabarit (il doit y être — P56), et l'API Proxmox expose sur son
dos exec, file-write, set-user-password, shutdown — strictement plus que le lecteur
cloud-init. Ce qui est fermé est étroit et réel : une réapplication automatique, à chaque
démarrage, depuis un support que le plan ne possède pas, et un interpréteur Python
complet exécuté en root au boot. La mainmise de l'hyperviseur sur ses invités est une
propriété de la virtualisation, pas de cloud-init — elle appelle sa propre décision, non
prise.
Nettoyage avant template
Le nettoyage final avant conversion en template doit être protégé par une confirmation explicite.
Exemple :
ansible-playbook -i "$SETOPS_INVENTAIRE" playbooks/modeles_vm/debian13_proxmox_nettoyer.yml -e template_cleanup_confirm=true
Le nettoyage peut inclure :
cloud-init clean --logs;- nettoyage du cache APT ;
- rotation ou purge contrôlée des journaux ;
- vidage de
/etc/machine-id; - remise en place du lien
/var/lib/dbus/machine-id; - suppression des historiques shell.
Ne jamais lancer ce nettoyage sur un serveur de production sans confirmation explicite.
CHANGELOG
Chaque modification significative doit être inscrite dans CHANGELOG.md.
Le format a changé, et ce fichier disait encore l'ancien. Les entrées d'avant le
2026-08-03 suivaient trois sections fixes (### Ajouté / ### Modifié / ### Corrigé).
Depuis, chaque entrée est un récit : ce qui était cassé, pourquoi personne ne le voyait,
ce que ça coûte de le réapprendre. Suivre la pratique en vigueur :
## AAAA-MM-JJ — un titre qui dit CE QUI A ÉTÉ APPRIS, pas ce qui a été touché
**N preuves.** Une ou deux phrases : l'état du harnais, et l'enjeu.
### Un sous-titre par piège payé
Ce que le dépôt croyait, ce que la machine faisait, et la mesure qui a tranché.
Deux entrées le même jour se distinguent par un rang : ## 2026-09-02 (6) — ….
Les corrections de rôles, handlers, playbooks et templates doivent être consignées.
Stabilisation des changements
Après une série cohérente de changements validés, recommander un commit propre avant de poursuivre vers un nouveau chantier.
Avant de recommander un commit :
- vérifier l'état Git ;
- résumer les fichiers créés, modifiés, déplacés ou supprimés ;
- indiquer les validations exécutées ;
- signaler les validations non exécutées ;
- mentionner les risques ou limites connus.
Ne pas créer de commit sans demande explicite.
Comportement attendu des agents IA
Ce comportement s'applique à tout agent IA travaillant dans ce dépôt (Codex, Claude Code, ou autre), conformément à la règle d'or « un seul agent IA à la fois ».
Avant de modifier :
- Lire
AGENTS.md. - Lire
README.md,CHANGELOG.mdetansible.cfgs’ils existent. - Vérifier l’état Git.
- Inspecter les fichiers concernés.
- Identifier la portée exacte de la demande.
- Proposer le plus petit changement utile.
- Préserver les conventions existantes.
Après modification :
- Résumer les fichiers créés ou modifiés.
- Indiquer les commandes de validation exécutées.
- Signaler clairement ce qui n’a pas été testé.
- Mettre à jour
CHANGELOG.mdsi pertinent. - Ne pas affirmer que c’est prêt si la validation a échoué.
Philosophie
Set-OPS est un outil d’exploitation réelle, pas une démonstration technique.
Priorités :
- reproductibilité ;
- sobriété ;
- clarté ;
- sécurité ;
- maintenance ;
- autonomie ;
- résilience.
La complexité doit toujours être justifiée par un bénéfice opérationnel clair.