Les interfaces VLAN du Binardat n'offrent aucun `access-group` : impossible
de lier une ACL à un SVI. Plutôt que d'émettre des règles jamais liées — qui
auraient l'air d'isoler sans jamais filtrer — la capacité se déclare :
`underlay.acl_inter_tenant`, `true` par défaut.
Ce n'est pas lié au dialecte de CLI mais au matériel : un autre commutateur
parlant la même CLI pourrait savoir lier des ACL.
À `false`, la section 3 ne contient plus de règles mais la raison, et surtout
ce qu'on perd : une VM émettant vers l'underlay est routée localement vers le
mgmt des switches, celui de Proxmox et l'OOB/IPMI. Les nftables des VM n'y
peuvent rien (politique `output` permissive), et l'IPMI n'est pas un hôte
géré.
Des VRF auraient donné cette isolation sans ACL — critère à retenir au
prochain renouvellement. Parade d'ici là : sortir le management de la fabric
routée des tenants, comme l'est déjà le stockage.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`ip ?` sur une interface VLAN du Binardat n'offre aucun `access-group`, et la
liste complète des commandes de ce mode n'en contient pas davantage. La ligne
`ip access-group <NOM> in` posée sur les douze SVI n'existe pas sur cette
plateforme.
C'est la ligne qui rend l'isolation effective. Sans elle, les ACL de la
section 3 sont parfaitement définies et jamais liées : `show access-lists`
afficherait « used 0 time(s) », et rien d'autre ne signalerait que
l'isolation inter-tenant ne filtre rien. Même signature que le défaut du
trunk — une configuration qui a l'air juste et n'agit pas.
Le `firewall disable` aperçu dans un `show running-config` prend
rétrospectivement du sens : le filtrage semble conditionné globalement.
Aucune forme de remplacement n'est devinée. Le devis porte un avertissement à
cet endroit, en dialecte `binardat` uniquement — après trois syntaxes
supposées dont deux fausses, marquer l'incertitude vaut mieux qu'un quatrième
pari.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
J'ai annoncé « les six familles sont closes » sur la foi d'un
`spanning-tree ?` en mode configuration GLOBALE. Trois lignes du devis
vivent ailleurs et n'y figuraient donc pas :
- `spanning-tree portfast trunk` (interface) — `trunk` est un mot-clé
Cisco ; l'équivalent s'écrit souvent `portfast` seul, voire `edged-port` ;
- `ip access-group <NOM> in` (interface) — c'est ce qui LIE l'ACL au SVI ;
sans elle l'ACL existe et ne filtre rien ;
- `ip default-gateway <ip>` (global, absent de l'aide consultée).
`(config-if)#spanning-tree ?` et `(config-if)#ip ?` les donneraient.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`spanning-tree ?` en mode configuration tranche la dernière inconnue, en
faveur de la forme émise : `mode` et `priority` s'acceptent au niveau global.
La priorité n'a pas besoin d'être portée par une instance, même en MSTP — la
réserve inverse, notée la veille, était infondée et le devis ne la porte
plus. Une mise en garde fausse nuit autant qu'une syntaxe fausse.
Ajouté : `spanning-tree` seul, qui garantit l'état actif. Sans lui, `mode` et
`priority` sur un boîtier où le protocole aurait été désactivé
configureraient un arbre qui ne tourne pas. Sans effet s'il est déjà actif —
même logique déclarative que pour les trunks.
Six familles vérifiées contre le matériel : VLAN, SVI, trunks, routes, ACL,
spanning-tree. Deux ont révélé un défaut réel plutôt que de confirmer
l'existant — les routes (CIDR) et les trunks, dont `add` ne retranchait rien.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Une ACL générée s'applique telle quelle, ses règles dans l'ordre émis —
`ip access-list extended <NOM>`, masques normaux, `any`.
Détail de lecture consigné : le boîtier affiche `any-destination` là où l'on
saisit `any`. Comparer un `show access-lists` au devis ferait apparaître une
différence qui n'en est pas une.
Cinq familles de syntaxe sur six sont maintenant vérifiées contre le
matériel : VLAN, SVI, trunks, routes, ACL. Ne reste que la forme d'entrée des
commandes de spanning-tree, que `show` ne révèle pas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`underlay.stp.mode` passe à `mstp` — le mode d'usine du commutateur, vérifié
par `show spanning-tree`. Sur une étoile sans lien redondant, l'instance 0 de
MSTP se comporte comme un RSTP : changer de mode aurait donné le même
résultat, au risque près de toucher à un protocole qui fonctionne déjà.
Le devis énonce son incertitude là où elle est : en MSTP la priorité se règle
souvent par instance, alors que la forme émise est globale. Il le dit plutôt
que de laisser croire à une syntaxe vérifiée — `show spanning-tree` donne
l'état du protocole, jamais la forme d'entrée des commandes.
Corrigé : le commentaire de la section 6 disait « RSTP » en dur alors que le
mode est déclaré. Il le dérive.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Correction d'une déduction fausse : j'avais conclu de son absence du
`show running-config` que le spanning-tree était désactivé. Il est actif —
il n'y figurait pas parce qu'il est aux valeurs d'usine. Une absence dans une
configuration ne veut pas dire une absence de fonction.
La plateforme est en MSTP (802.1s, Force Version 3) alors que
`underlay.stp.mode` déclare rstp. Sur une étoile sans lien redondant les
deux se comportent identiquement ; reste à décider si l'on aligne la
déclaration sur le matériel ou l'inverse.
La priorité de pont est déjà 32768 : la ligne émise pour les switches
d'accès est un non-opérant.
Reste non vérifiée la forme d'ENTRÉE des commandes — `show` donne l'état,
pas la syntaxe. En MSTP la priorité se règle en général par instance, ce que
la forme émise ne fait pas.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`switchport trunk allowed vlan ?` sur le matériel confirme la syntaxe et
révèle un défaut : `add` ajoute à la liste courante, la forme sans mot-clé
la définit.
Le devis émettait `add`. Or un port trunk neuf autorise tous les VLAN — dans
une config réelle, les ports n'ont aucune ligne `allowed vlan`, ce qui
signifie exactement cela. Y ajouter la liste voulue n'en retranchait aucun :
le trunk continuait de tout transporter, et le devis donnait l'illusion de
restreindre. Le pire genre de défaut — ça a l'air juste, ça s'applique sans
erreur, et ça ne fait pas ce que ça annonce.
La forme sans mot-clé est aussi atomique : `none` puis `add` couperait le
trunk entre les deux commandes, ce qui suffit à perdre la session si on
l'applique sur le port de gestion.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un `show running-config` du commutateur tranche la question restée ouverte :
la plateforme écrit ses routes en notation CIDR — `ip route 0.0.0.0/0
192.168.10.254` — et non en masque séparé comme Cisco. Le générateur
produisait du Cisco quel que soit le dialecte.
`route_statique()` suit maintenant le dialecte, comme les masques d'ACL.
Vérifié dans les deux formes.
Restent non vérifiés faute d'apparaître dans la config réelle : la syntaxe
des ACL, celle de `switchport trunk allowed vlan add`, et le spanning-tree —
totalement absent du `show running-config`, ce qui suggère qu'il est
désactivé par défaut sur cette plateforme.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Renvoi ambigu corrigé : « en cas de retour arrière (§6) » figurait dans
l'étape 6 — deux « 6 » pour deux choses dans une seule phrase. La section est
nommée plutôt que numérotée.
Le responsable désigné devient le §3 : il vivait sous « le transfert de nom
de domaine » alors que ce n'est pas un emprunt aux registraires mais une
décision de modèle, valable migration ou pas.
Et le §8 énumérait ce que la migration ne déplace PAS sans dire ce qu'elle
déplace. Le responsable, lui, suit le tenant — c'est l'inverse, et le dire
renforce la ligne de partage : ce qui est à l'hébergeur reste, ce qui est au
tenant part avec lui. Si quelque chose appartenant à l'organisation ne peut
pas partir, elle n'est pas vraiment souveraine ; si quelque chose
appartenant à l'hébergeur devait partir, la frontière est mal tracée.
Sections renumérotées (9 au lieu de 8), six renvois internes vérifiés.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Elle se perd, se compromet, ou la personne quitte l'organisation. Sans
procédure, un tenant devient inmigrable : captif non par contrat mais par
accident — exactement ce que la recette existe pour empêcher.
Deux écueils symétriques consignés. Trop lourde, la procédure n'aboutit
jamais et le tenant reste bloqué. Trop légère, elle devient le chemin de
moindre résistance pour contourner la signature : inutile de forger un
mandat si l'on peut se faire attribuer la clé. Le recouvrement doit être au
moins aussi difficile que ce qu'il protège.
Vraisemblablement le même mécanisme que le changement de responsable — dans
les deux cas quelqu'un d'extérieur à la clé atteste de l'autorité. Piste la
plus transposable des registraires : un contact de secours nommé en même
temps que le responsable, tant que personne n'est en situation d'urgence.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Chaque tenant a un responsable désigné — la personne qui engage
l'organisation, et dont la signature seule vaut mandat de migration. Sans
responsable nommé d'avance, la question « qui peut décider de déménager
cette organisation ? » se pose au pire moment, quand les deux hébergeurs ont
un intérêt dans la réponse.
La table des états annonçait une réversibilité « gratuite » jusqu'à
`libéré`, alors que le §6 établit qu'elle change de nature à la bascule. Pas
contradictoire, mais un lecteur pressé s'arrêtant au tableau en retirait une
fausse impression. Ce qui reste gratuit est l'adressage, pas le retour.
Deux questions de gouvernance ajoutées aux points à trancher : où le
responsable est déclaré et comment on en change — acte au moins aussi
sensible que la migration, puisqu'il décide qui pourra la mandater ensuite ;
et la rétention, convenue avec qui, consignée où, attestée par qui.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La recette affirmait la réversibilité sans décrire le retour. Or il change
de nature à la bascule, et le geste évident — repointer le DNS — devient
faux à cet instant : les utilisateurs ont écrit chez l'entrant, et ces
données n'existent nulle part ailleurs. Les perdre serait silencieux.
Trois régimes écrits : avant le gel (sans conséquence), pendant le gel
(dégeler), après la bascule (migration inverse, même outillage).
Rendu explicite : le sortant reste gelé après la bascule, jusqu'à
confirmation. Le dégeler « au cas où » créerait deux copies vivantes et plus
aucune vérité ; en contrepartie il n'a pas divergé, donc le delta d'un
retour reste à sens unique.
Deux points de non-retour distingués : la bascule fait perdre le retour
gratuit, la purge fait tout perdre. D'où l'exigence ajoutée : les critères
de confirmation se fixent par écrit AVANT la première bascule.
Corrigés : le rattrapage est à l'étape 5 (non 4) ; le chemin de vérification
hors DNS public est un prérequis de l'étape 3 elle-même.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La séquence, les états et les gardes — écrite avant tout code, délibérément :
figer un enchaînement qu'on n'a jamais joué serait prématuré.
Le modèle est le transfert de nom de domaine : le mandat appartient au
client, verrou par défaut, deux actes délibérés et traçables. Là où
l'analogie casse elle est remplacée, pas étirée — sans registre central pour
arbitrer, le mandat est signé par le tenant et vérifié contre une clé
publique de son plan ; un secret partagé ne prouverait rien à l'entrant.
L'ordre est commandé par une règle unique : le receveur doit être prouvé
prêt avant que quoi que ce soit ne gèle. L'entrant se construit pendant que
le sortant sert ; l'interruption se réduit au delta plus la propagation DNS.
Garde de transition, pas conseil : `gelé` est inaccessible tant que
`préparé` n'est pas prouvé.
Deux pièges consignés : le TTL s'abaisse à l'étape 1 et non à la bascule ;
l'entrant doit être vérifiable sans être public, sinon le tenant sert des
deux côtés et l'identité se dédouble.
La libération est une révocation, pas une transmission : re-clétage de la
voûte chez l'entrant, sans quoi l'ancien hébergeur garde à vie l'accès aux
secrets d'un client parti.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un hébergeur sert plusieurs tenants et n'a qu'une frontière. Ses intrants
étaient lus chez le tenant actif : basculer sur un invité — Technolibre, qui
n'a pas de frontière à lui — faisait perdre au devis l'URL de gestion,
l'adresse publique et les deux interfaces. Il repartait en marqueurs, comme
si le boîtier n'existait pas. Vérifié en simulant la bascule.
Même famille que le défaut de l'underlay corrigé plus tôt ; c'est la
distinction hébergeur/tenant qui le fait apparaître.
Le devis et le panneau lisent maintenant la frontière chez l'hébergeur, qui
n'est pas déclaré pour autant : le symlink `underlay.yml` le désigne déjà.
Repli sur l'instance active sans underlay monté.
Vérifié : devis identique avec l'hébergeur actif, intrants conservés avec un
invité actif.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Un modèle décrivait un tenant — ses services, ses zones, ses bases. Tous les
hébergeurs n'ont pas le même matériel : l'infrastructure physique mérite le
même traitement.
Le modèle public gagne un underlay volontairement minimal (un commutateur,
pas de fabric de stockage séparée), point de départ honnête d'un petit
hébergeur. Les montages plus riches sont d'autres modèles, conformément à la
doctrine : un générique public, les étoffés en privé.
Le modèle contient désormais deux moitiés qui ne vont pas au même endroit :
`plan/` et `inventories/` chez le tenant, `underlay.yml` chez l'hébergeur.
`modeles.py verifier` le valide (P17), facultativement et sur sa cohérence
INTERNE seulement — pas contre les tenants fédérés réels, un modèle étant un
gabarit et non un site déployé. Il a fallu rendre paramétrables deux
hypothèses du validateur, qui lisait la nomenclature de l'instance active et
globait les dépôts frères ; comportement par défaut inchangé.
Cinq cas de rejet exercés : VLAN empiétant sur la plage tenant, passerelle au
mauvais dernier octet (lue dans la nomenclature du modèle), routeur inconnu,
sortie hors du lien, port déclaré deux fois.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Il vivait gitignoré à la racine du moteur : consommé par deux générateurs,
validé par P23, versionné nulle part. La dizaine de modifications de la
journée n'était récupérable d'aucune façon, et un clone frais repartait du
gabarit.
Il appartient à l'hébergeur — ses switches, ses câbles — pas au moteur, qui
est générique, ni à un tenant, qui n'en possède pas. Chezlepro est ici
hébergeur ET tenant, d'où la confusion initiale.
Le moteur le monte par symlink comme il monte le plan par `instance/`. Ce
lien NE SUIT PAS `make instance-utiliser` : la fabric reste celle de
l'hébergeur quel que soit le tenant actif.
Vérifié : les deux devis sortent identiques octet pour octet avant et après,
P23 verte, 24 preuves. Symlink brisé (clone sans dépôt frère) : dégrade
proprement, sections omises au lieu d'échouer. Cas exercé.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis frontière annonçait trois routes de retour « déjà émises par
devis-reseau ». Le devis switch n'en émettait qu'une : il lisait
`nftables_admin_ssh` de la seule instance active, alors que la frontière
était passée multi-tenant. Les réseaux d'administration de Technolibre
n'étaient routés nulle part — et une affirmation fausse est pire qu'un
silence, elle désamorce la vérification.
`admin_tous_tenants()` vit dans devis_reseau et devis_opnsense l'importe au
lieu d'en refaire une copie : routes de retour et règles lisent les mêmes
tenants par construction. Vérifié identiques.
Ajouté : l'avertissement « Block private networks ». Le SSH d'administration
a une source RFC1918 arrivant sur une interface WAN, où ce filtre est actif
par défaut et s'applique AVANT les règles — coché, il jette le paquet sans
qu'aucune règle ne soit consultée. Un réglage d'interface est invisible dans
les règles, il fallait l'écrire à part.
Prédicat exactement RFC1918, périmètre de cette case ; `is_private` aurait
été trop large (documentation, CGNAT) et l'avertissement se serait déclenché
à tort. Trois cas exercés : RFC1918 averti, 8.8.8.8 muet, 203.0.113.7 muet.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Dans OPNsense une règle est toujours `in` sur l'interface d'arrivée : posée
ailleurs elle ne s'applique jamais, et le trafic est bloqué sans que la
configuration paraisse anormale. Les règles n'en portaient aucune, alors que
le champ est obligatoire dans l'API.
L'attribution se dérive du sens du flux : `ingress`/`externe` arrive par le
WAN, `egress`/`externe` par le lien de transit. Le SSH d'administration suit
la première ligne — le VPN est hébergé sur le pfSense voisin et revient par
l'adresse publique. Le montage parallèle décrit ce jour lève la dernière
inconnue.
Le rendu abandonne `pass out` pour `pass in on <interface>`, l'idiome réel
d'OPNsense et ce que le client d'API devra envoyer.
Ajouté aussi :
- `opnsense_wan_ip`, la face publique, en section Frontière du panneau ;
- invariant du dernier octet (P23) : un point de routage porte le même
dernier octet sur tous ses sous-réseaux. Le chiffre vient de
`reservations.passerelle`, pas d'une constante. Exemption des liens plus
étroits qu'un /24 — sur le /29 de transit l'adressage est dicté par les
participants. Vérifié que l'invariant tenait déjà sur les 13 sous-réseaux
routés avant d'écrire la garde.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis était multi-tenant pour ses routes, mono-tenant pour ses règles :
il routait 10.21.0.0/16 et 10.27.0.0/16 mais ne filtrait que l'instance
active. Technolibre aurait été routé jusqu'à la bordure puis bloqué dans les
deux sens, SSH d'administration compris, sans qu'une ligne dise pourquoi.
La résolution est paramétrée par tenant : `inventaire_de()` lit le hosts.yml
de chaque instance fédérée, `cibles_par_role()` prend l'inventaire en
argument, les alias d'hôtes sont préfixés. 11 règles par tenant, 22 au total.
Cloisonnement : la première version faisait de SETOPS_ADMIN l'union des
réseaux d'administration — le plan de gestion d'un tenant serait entré chez
le voisin, la bordure rouvrant ce que les ACL de switch ferment. Corrigé
avant livraison : un alias par tenant, n'ouvrant que son propre supernet.
L'union reste pour les routes de retour et P24 : router n'est pas autoriser.
Deux omissions annoncées : tenant sans inventaire (aucune règle), tenant
sans `nftables_admin_ssh` (règle SSH omise plutôt qu'ouverte à `any`, ce qui
exposerait le SSH à Internet). Cas dégradé exercé.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis se terminait par `block out log all` avec une seule règle sortante
(le relais SMTP). Appliqué tel quel, il coupait la flotte d'Internet : plus
d'apt, plus de NTP, plus de récursion DNS. Rien ne le signalait — la ligne
la plus lourde de conséquences du devis, posée à la suite des autres.
La sortie est déclarée dans le registre, donc dérivée :
- `serveur_debian` (socle, 14 hôtes) : 443 et 80 pour les dépôts apt, 123/udp
pour l'horloge — une dérive fait échouer step-ca et le SSO des semaines
après la cause ;
- `client_unbound` : 53 udp et tcp, la récursion depuis la racine qu'implique
`client_unbound_transitaires: []`. Le TCP est le repli obligatoire dès
qu'une réponse DNSSEC dépasse la taille UDP.
Le devis passe de 6 à 11 règles, et sa section 5 énonce le default-deny
sortant, le nombre de règles qui l'accompagnent, et où déclarer un besoin
oublié — jamais à la main dans le pare-feu.
Vérifié : les nftables d'hôte sont inchangés, octet pour octet. Le pair
`externe` reste sauté par resoudre_flux.
Non déclaré volontairement : le rôle `chrony` n'est référencé par aucun
groupe ni playbook — un flux pour lui aurait été une règle morte.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`opt1`, `igb1` et `TENANTS` désignent le même port — identifiant interne,
périphérique FreeBSD, libellé affiché. L'API REST ne parle que du premier.
Les libellés des deux intrants ne le disaient pas, et la question s'est
posée en pratique.
Ils le disent maintenant, et docs/frontiere-opnsense.md explique les trois
couches et pourquoi `opt1` est le plus stable : il survit à un changement de
carte comme à un renommage.
La note « prochain_saut dérive de l'underlay » est repliée dans l'en-tête
régénéré par le panneau : une sauvegarde l'effaçait, le fichier étant
réécrit depuis le YAML analysé. Vérifié qu'une sauvegarde préserve valeurs
et références de voûte.
Corrigé au passage : après le renumérotage du /29, deux passages de la doc
annonçaient encore 10.0.4.2 comme prochain saut et contredisaient le devis.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le rôle dérive l'empreinte à chaud depuis l'autorité, parce qu'un from-zero
régénère l'AC avec une empreinte neuve. Figée en voûte, elle serait périmée
dès la première reconstruction — et une empreinte périmée fait échouer le
bootstrap de chaque hôte.
Or `defaults/main.yml` portait encore un défaut lisant
`vault_step_ca_fingerprint`. Ce défaut était mort : la tâche suivante écrase
le fait sans condition, donc la valeur de la voûte n'avait aucun effet.
Le recensement de voute.py s'y laissait prendre — il cherche la chaîne
`vault_*` sans pouvoir savoir qu'un défaut n'est jamais lu. La « source
unique » avait hérité de l'erreur, et le panneau réclamait un secret
impossible à fournir avant que l'AC n'existe.
Défaut retiré : la clé disparaît du recensement (25 -> 24 exigés), du
gabarit et du panneau. `client_pki_ca_fingerprint_override` reste le moyen
d'épingler une empreinte.
`docs/intrants-communs.md` §H était une troisième copie manuelle de la liste
des secrets, avec les deux mêmes erreurs ; elle renvoie maintenant à
`voute.py lister` et à la preuve P18.
Preuves : 24 OK, 0 échec ; voute.py verifier --strict passe.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`SECRETS_ATTENDUS` était une liste tenue à la main, en parallèle du
recensement que voute.py fait déjà depuis le plan, les rôles et les
group_vars. Deux sources pour la même vérité, et la manuelle avait divergé :
elle annonçait `vault_ldap_sssd`, que rien ne consomme — aucun rôle sssd
n'existe — et omettait `vault_step_ca_fingerprint`, réellement lu par
client_pki pour vérifier l'empreinte de l'AC racine.
Un opérateur qui suivait le panneau créait un secret inutile et en oubliait
un nécessaire.
Le rappel dérive maintenant de `voute.secrets_exiges()` — la même source que
la preuve P18 — augmentée de SECRETS_HORS_MOTIF pour les jetons Proxmox, qui
n'ont pas le préfixe `vault_`. Vérifié : 27 noms, écart nul avec le gabarit.
Dépôt public nu : liste vide plutôt qu'une erreur d'affichage.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Tout le modèle d'underlay bâti aujourd'hui s'éditait à la main dans un YAML,
pendant que la doctrine dit qu'un sysadmin doit exploiter l'outil sans IA.
La frontière avait eu sa section ; l'underlay, non.
La section couvre les valeurs plates : switch routeur, dialecte de CLI, mode
et topologie de spanning-tree. Les listes de tables (`reseaux`, `hotes`, donc
les ports) restent hors de portée du panneau — elles demandent une vue
dédiée, comme celle des serveurs.
Le dialecte devient un intrant déclaré : il ne vivait que dans
SETOPS_DIALECTE. C'est une propriété du matériel, donc de la fabric.
Précédence : `--dialecte` > environnement > intrant déclaré > cisco.
Écriture chirurgicale plutôt que safe_dump : underlay.yml porte 23 lignes de
commentaires qui expliquent des décisions d'architecture, et un dump les
aurait effacées comme c'est arrivé à plan/applications.yml. Vérifié : trois
valeurs modifiées, 73 lignes et 23 commentaires avant comme après. Une clef
absente du fichier est refusée plutôt qu'inventée à un endroit arbitraire.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les noms de ports n'étaient modélisés nulle part — des marqueurs littéraux
dans le générateur, remplacés à la main dans la sortie et perdus à chaque
régénération. Seul endroit du devis où le travail était refait à répétition.
Ils se déclarent par équipement sous quatre clefs correspondant aux quatre
natures de lien : `hyperviseurs` et `frontiere` (terminaux, portfast),
`rayons` (côté routeur), `montante` (côté switch d'accès).
Le devis émet les vrais ports, y compris PLUSIEURS vers les hyperviseurs —
il n'en supposait qu'un, alors que le cluster en compte trois. Non déclarés,
les marqueurs reviennent, par équipement : un switch renseigné et un autre
non cohabitent.
La partie B devient par switch : gestion, montante et ports terminaux
diffèrent d'une machine à l'autre, un bloc commun n'avait plus de sens.
Gardes exercées : port déclaré deux fois, rayon vers un switch inconnu,
`rayons` sur autre chose que le routeur, `montante` sur le routeur.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La partie A déclarait ses ports de bord deux fois — l'interface en section
4, son portfast en section 6 — alors que la partie B posait tout dans le
même bloc. Deux conventions pour la même chose dans un seul document.
Le portfast est posé avec son interface (sections 4 et 4b). La section 6 se
réduit au global — mode, priorité du pont racine — plus les deux
avertissements : les rayons de la 4c n'en sont volontairement pas, et
pourquoi BPDU guard n'est pas émis.
Vérifié : aucune interface déclarée deux fois dans une même partie,
3 portfast avec `stp`, 0 sans lui, 0 sur un rayon.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'ajout du spanning-tree venait de rendre dangereuse une imprécision de
titre. La section 4 s'appelait « Trunk vers Proxmox + inter-switch » et
n'émettait qu'un port, que la section 6 déclarait en bord de réseau.
Réutiliser ce placeholder pour les rayons revenait à mettre portfast sur les
liens qui portent les BPDU — désactiver la protection anti-boucle là où elle
sert.
Les rayons sont dérivés et émis à part (4c côté routeur, B3a côté accès), un
par switch d'accès, marqués comme non-bord. La section 4 ne désigne plus que
les hyperviseurs ; la partie B distingue sa montante de son trunk terminal.
`underlay.switches_acces()` devient la source unique du « qui est un switch
d'accès » — pour leur devis ET pour les rayons côté routeur.
Vérifié : 3 commandes portfast avec `stp` déclaré, 0 sans lui, 0 sur un
rayon.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le devis ne disait rien du spanning-tree. Sur une fabric convergée à trois
switches, une boucle par brassage accidentel est une tempête de diffusion.
La topologie se déclare (`stp: {mode: rstp, topologie: etoile}`) et le devis
en tire la configuration. Le routeur est désigné pont racine : il est le
centre de l'étoile, tous les chemins passent déjà par lui, donc l'arbre
logique suit le câblage physique plutôt qu'une élection arbitraire. Les
switches d'accès reçoivent une priorité haute — jamais racine.
Ports terminaux déclarés en bord de réseau. BPDU guard délibérément NON
émis : un pont Linux dont le STP serait activé enverrait des BPDU et ferait
tomber le port côté hyperviseur. Le devis dit pourquoi et à quelle condition
l'ajouter.
En étoile aucun lien n'est redondant : RSTP est un filet, pas une nécessité.
Le devis le dit au lieu de laisser croire à une protection indispensable.
Validation : `mode` et `topologie` contrôlés, `stp` sans `routeur` refusé
(aucun pont racine désignable). Sans `stp`, la section signale l'absence de
protection au lieu de disparaître.
Réserve : la forme `binardat` du spanning-tree n'est pas vérifiée sur le
matériel, comme les `ip route`.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les VLAN et les trunks étaient déjà filtrés par fabric ; le roster de la
section 0, non. Un équipement d'une autre fabric — les switches de stockage,
le jour où ils seront déclarés — serait apparu en commentaire dans un devis
qui ne le concerne pas. Un devis est une configuration qu'on applique, pas
un inventaire.
Vérifié en déclarant un hôte de la fabric stockage : il reste absent, et la
sortie du jour est inchangée.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Le stockage jumbo (iSCSI, Ceph) est porté par un réseau indépendant de deux
switches 10G, sans câble commun avec la fabric convergée des sleipnir. Le
modèle l'ignorait : le devis déclarait les VLAN 20/30/31 sur les switches
convergés et les mettait dans leurs trunks. C'était faux.
Chaque réseau porte une `fabric` (`principal` par défaut). Le devis ne
configure que celle du routeur, et énonce explicitement ce qu'il ne couvre
pas au lieu de le taire — VLAN, trunks et spanning-tree d'une autre fabric
ne le regardent pas.
Les `deny` de l'ACL couvrent en revanche toutes les fabrics : la règle porte
sur l'adresse de destination, pas sur le câblage. Si un chemin s'ouvre un
jour vers le stockage, il est déjà fermé.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Les frontières occupent le bas du /29, le SVI du switch le haut. `.3` reste
libre pour une future IP virtuelle CARP si les deux OPNsense passent en
haute disponibilité — ce jour-là, `passerelle_sortie` pointera sur la VIP
plutôt que sur un boîtier nommé, et ce sera le seul changement.
Plan du lien :
10.0.4.1 bifrost-1 frontière active, sortie par défaut de la flotte
10.0.4.2 bifrost-2 seconde frontière
10.0.4.3 libre réservée VIP CARP
10.0.4.6 sleipnir-01 SVI du switch routeur
Les deux devis suivent sans intervention : routes du switch vers 10.0.4.1,
routes tenants de la frontière via 10.0.4.6.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`bifrost-0` et `bifrost-1` sont réservés aux deux frontières OPNsense —
Bifröst est le pont vers l'extérieur. Les switches internes deviennent
`sleipnir-01..03` : le cheval qui traverse les mondes, pas le pont qui en
sort. La division du nom suit celle de l'architecture.
Les deux boîtiers sont déclarés comme hôtes du lien de transit (10.0.4.2,
10.0.4.3) : hors flotte Ansible, la déclaration documente le lien et réserve
les noms. Le /29 choisi plus tôt les loge tous les deux.
Conséquence traitée : la partie B du devis aurait listé les deux pare-feux
parmi les « switches d'accès ». Elle ne retient plus que les hôtes du réseau
de management — un équipement déclaré ailleurs ne reçoit aucune ligne de
configuration de switch.
Le devis frontière nomme le boîtier quand il est déclaré.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux incohérences visibles à la relecture du devis complet.
`bifrost-01` portait le SVI Vlan10 -> 10.0.0.1 et était déclaré dans
`underlay.hotes` à 10.0.0.2. Une interface VLAN n'a qu'une adresse primaire :
les deux ne pouvaient pas être vraies. L'entrée datait d'avant la désignation
du routeur, quand 10.0.0.1 était une passerelle abstraite. Le routeur est
déclaré à l'adresse du SVI qu'il porte, et `make underlay` refuse désormais
la divergence.
La partie B créait `vlan 40` alors que son trunk ne le transporte pas — le
transit ne relie que le routeur à la frontière. VLAN exclu de la partie B,
comme il l'est déjà des trunks généraux.
Preuves : 24 OK, 0 échec ; garde de cohérence exercée.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Sans MLAG, le routage est porté par un unique switch (`underlay.routeur`).
Le devis émettait un jeu unique de SVI sans dire à quel switch il
s'adressait : appliqué sur les trois, il aurait créé autant de conflits
d'adresses qu'il y a de zones.
Il se scinde désormais en deux :
- partie A (switch routeur) : VLANs, SVI, ACL, trunks, routes ;
- partie B (switches d'accès, L2 pur) : mêmes VLANs pour commuter les trames
étiquetées, une adresse de gestion par switch tirée de `underlay.hotes`
avec `ip default-gateway` vers le routeur, et les trunks. Aucun SVI de
zone, aucune ACL, aucune route.
Gardes : `make underlay` refuse un `routeur` inconnu des hôtes déclarés ; la
partie B avertit qu'un seul de ses blocs de gestion va sur chaque machine.
Sans routeur désigné, l'en-tête signale le risque de duplication au lieu de
laisser croire que le devis est applicable partout.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
L'ACL d'isolation bloquait l'autre tenant, puis se terminait par
`permit ip <tenant> any`. Ce `any` autorisait 10.27.x -> 10.0.0.0/24 : le
management des switches, celui de Proxmox et l'OOB/IPMI, plus iSCSI et Ceph.
Une VM compromise atteignait la console physique des hyperviseurs.
Le commentaire du générateur disait « Reste -> passerelle OPNsense », ce qui
est faux pour l'underlay : ce trafic est routé LOCALEMENT par le switch et ne
passe jamais par la frontière, donc elle ne le filtre jamais.
Un `deny` par sous-réseau underlay est désormais émis avant le `permit`
final, dérivé de underlay.yml, dialecte respecté (masque normal ou wildcard).
Vérifié qu'aucun flux du registre ne vise l'underlay : rien de déclaré ne
casse. Sans underlay déclaré, l'ACL retrouve sa forme d'avant.
Consigné en §6 : le registre n'a pas de mot-clé `underlay` (un besoin
légitime, superviser l'hyperviseur, ne pourrait pas être déclaré), et le
devis émet un jeu unique de SVI pour trois switches sans MLAG.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Deux omissions que le devis faisait porter à l'opérateur.
Le VLAN de transit était étiqueté sur le trunk `<PORT-VERS-PROXMOX>`, et
aucune interface vers le pare-feu n'était émise. Les hyperviseurs n'ont pas
d'interface sur le transit, et le pare-feu n'a rien à faire des VLAN
tenants — il route vers eux, il ne les étiquette pas. Le transit sort du
trunk Proxmox et prend son propre port (section 4b), dérivé de l'underlay.
Les routes de la section 5 déplacent la sortie du switch, y compris celle de
ses propres réponses. Tant que la frontière ne répond pas, elles coupent
l'accès d'administration AU SWITCH LUI-MÊME — le mécanisme qui a rendu une
VM muette le 2026-07-29, appliqué à l'équipement depuis lequel on travaille.
Le devis les crachait à la suite des autres comme si elles étaient
équivalentes ; il énonce maintenant les préalables et l'ordre.
Sans transit déclaré, les deux sections s'affichent en clair comme
manquantes plutôt que de disparaître.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`opnsense_prochain_saut` quitte le panneau Intrants. Il dérive du réseau de
transit de l'underlay — la `passerelle` du réseau portant `passerelle_sortie`.
Un seul bloc alimente les deux devis : 10.0.4.1 est le SVI côté switch ET le
prochain saut des routes tenants côté frontière ; 10.0.4.2 est la route par
défaut du switch. Le saisir en doublon rouvrait la possibilité de deux
valeurs contradictoires pour un seul lien — le mode de panne qu'on venait de
fermer pour les réseaux d'administration.
Le devis frontière expose le bloc `transit` (JSON compris) et cesse de
réclamer en section 0 les routes que `devis-reseau` émet désormais : il dit
lesquelles sont déjà émises, ou signale l'absence de transit déclaré. Sans
underlay, le marqueur revient — le repli reste explicite.
Preuves : 24 OK, 0 échec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La bordure devient un artefact dérivé, comme le devis switch — et le chemin
qui y mène est enfin déclaré.
`make devis-opnsense` (+ preuve P24) dérive la politique de bordure du
registre des flux : les flux `pair: externe`, que `resoudre_flux.py` saute
volontairement parce qu'ils relèvent de la frontière et non du pare-feu
d'hôte. Aucun port, aucune adresse, aucun nom d'hôte dans le générateur.
Le lien manquait dans tous les fichiers : le devis switch ne contenait pas
une seule `ip route`. Un réseau underlay portant `passerelle_sortie` le
déclare — il vit dans l'underlay et non dans un tenant parce que la
frontière route vers TOUS les supernets tenants par le même saut, donc il
ne peut dériver d'aucun `index`. `devis-reseau` en tire deux routes :
l'aller (sortie générale) et le retour vers l'administration, dont l'absence
a coûté la passe de déploiement du 2026-07-29 — la réponse revient au
pare-feu par une autre interface que celle où l'état a été créé, et se fait
jeter en silence.
Les réseaux d'administration viennent de l'intrant `nftables_admin_ssh` :
même source unique que la garde anti-lockout des nftables et l'alias
SETOPS_ADMIN. Les trois pare-feux et les routes ne peuvent plus diverger.
La frontière est réglable depuis la console (section « Frontière » du
panneau Intrants) ; les identifiants d'API restent interdits d'écriture par
le GUI et vivent dans la voûte.
Correctifs de la même passe :
- le panneau refusait d'enregistrer les intrants de la frontière : le
garde-fou confondait une référence de voûte `{{ vault_* }}` préservée
avec un secret soumis. Il regarde désormais la valeur, pas le nom.
- `supprimer_vm_debian.yml` ne chargeait que `proxmox.vault.yml` pour ses
secrets ; retirer ce reliquat aurait cassé `make detruire`. Aligné sur le
playbook de clonage, voûte unique en dernier.
- documentation : la voûte est unique, `proxmox.vault.yml` n'est qu'un
reliquat de compatibilité.
Preuves : 24 OK, 0 échec. Cas de rejet du validateur d'underlay exercés un
par un ; résolution du jeton Proxmox vérifiée en exécution réelle.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Solde la dette documentaire repérée à l'audit du 2026-07-03. Tous les rôles ont
désormais un README, au format maison (intention → rôle → variables →
notes/limites → prérequis). Ils documentent ce qui ne se lit PAS dans les tâches :
- serveur_debian : rôle-catégorie sans tâches — il ne porte que le flux SSH du
plan de gestion, sans quoi les nftables générés couperaient l'accès Ansible à
toute la flotte ; le vrai travail est dans le playbook de groupe.
- hosts_statiques : le plancher /etc/hosts comme CONDITION de l'ordre de
reconstruction (résolution par nom avant que PowerDNS existe) ; fichier
entièrement géré, alias d'exposition best-effort.
- resoudre_base / resoudre_annuaire : entrées, sorties (facts), et pourquoi le
FQDN plutôt que le nom court (fédération + nom canonique pour verify-full).
- serveur_dovecot : les 3 réglages Dovecot 2.4 qui conditionnent la remise
(static_allow_all_users, mail_inbox_path vide, mail_path par utilisateur) et
le local-part seul comme chemin commun LMTP/IMAP ; limite mono-domaine.
- serveur_postfix : liens mailstore/milter, recopie de /etc/hosts dans le chroot.
- serveur_rspamd : clé DKIM idempotente ; le domaine signé doit être PUBLIC en
prod, sinon la signature ne vaut rien pour les MX distants.
- client_backup / serveur_backup : jobs déclaratifs, chiffrement côté client,
deux pièges (jobs vides = silencieux ; dépôt neuf vide après un from-zero) ;
hors-nœud n'est pas hors-site.
- serveur_oauth2_proxy : le patron réutilisable Keycloak-devant-n'importe-quoi,
et pourquoi allow_unverified_email est nécessaire avec un annuaire LDAP.
- serveur_icingaweb2 : modes ldap vs external, écoute à restreindre en SSO.
- client_unbound : le garde-fou de bascule (apply ET confirm, validation réelle
avant de toucher /etc/resolv.conf).
docs/carte-set-ops.md ne décrivait plus l'état du code :
- expose EST consommé au déploiement (annoncé comme « Phase 3 à venir ») :
expositions_des_applications + expositions.conf.j2, alias /etc/hosts, SANs
d'edge dérivés par instancier.py ;
- 3 mécanismes transverses ajoutés (résolution d'annuaire, plancher de
résolution, resoudre_base nommé dans les bindings app→base) ;
- 5 entrées d'index ajoutées : réseau/pare-feu, ordre de déploiement,
preuve/recette, exploitation courante, wiki — pans du corpus non indexés ;
- ce qui reste ouvert est marqué : meta/liens.yml sur le seul serveur_postfix,
requiert non consommé au déploiement (l'ordre vient des couches + du graphe).
make verifier vert : ansible-lint 514 fichiers, tests, syntax-check,
23 preuves CONFORME / 0 échec / 0 sauté.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
La série d'illustrations (commit précédent) rejoint sa page. En fin de section
② de l'unité, une galerie « Les vues, annotées » présente les 8 figures — vues
éditables (Serveurs, Applications, Bases × 2, Domaines) puis dérivées (Flux,
Couches, Réseau) — chacune un SVG auto-contenu avec ses repères + légende.
Mécanique, sans duplication dans l'arbre source :
- symlink wiki/img -> ../docs/img : foyer unique dans docs/img/, refs relatives
(img/*.svg) qui résolvent en local et sur le navigateur de dépôt ;
- make wiki-publier embarque désormais les docs/img/*-annote.svg (déréférencés)
dans le wiki Forgejo publié — jusqu'ici seules les .md voyageaient, donc aucune
image n'aurait rendu une fois publiée.
Preuves : 22 OK / 0 échec / 1 sauté (dont P22 « plan de recette à jour »).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Complète la série d'illustrations commencée avec la vue Serveurs : les 7
autres vues de la console (Applications, Bases × 2 — serveur BD et fiche de
base —, Domaines, Flux, Couches, Réseau), chacune en capture PNG + version
annotée en SVG AUTO-CONTENU (PNG intégré en base64, un seul fichier portable
qui rend dans les .md, le wiki Forgejo, un navigateur).
Même moule que Set-OPS-Serveurs-annote.svg : 8 repères aux couleurs Alliance
Boréale + légende deux colonnes. Points saillants par vue :
- Applications : catalogue des applis, éditeur (rôle/hôte/port/FQDN exposé),
relations déclaratives (requiert/liens/bases), dépendances causales.
- Bases : serveurs SGBD + bases applicatives « nom @ serveur », bases hébergées.
- Bases (fiche) : portée/consommateur/serveur, secret Vault (jamais en clair),
DSN dérivé masqué.
- Domaines : zones DNS publique vs interne, autorité/edge/DNSSEC, expositions.
- Flux : matrice d'audit (source de nftables ET justification), sens/chiffrement.
- Couches : ordre de déploiement en 6 couches (socle → agents), lecture seule.
- Réseau : flotte multi-instances, adressage dérivé du seed, bascule, devis switches.
La série des 8 vues est désormais complète — prête à intégrer aux .md / au wiki.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le modele derive l'adressage par tenant (VLAN 1000+index*10+zone), mais la
fabric physique qui porte la flotte (mgmt switches/Proxmox/OOB, iSCSI, Ceph
public+cluster) n'appartient a aucun tenant. Elle est desormais codifiee.
- scripts/underlay.py + make underlay : charge/affiche/valide underlay.yml
(VLAN < 1000, sous-reseaux hors des supernets tenant 10.(10+index).0.0/16).
- underlay.yml gitignore (comme le vault) ; gabarit public underlay.yml.example ;
surchargeable par SETOPS_UNDERLAY.
- devis_reseau : section 0. Underlay + VLAN underlay sur le trunk, selon dialecte.
- P23 : underlay.py --verifier ; sautee si underlay.yml absent (comme P16 sans vault).
23/23 preuves. Doc : docs/audit/README.md, wiki page reseau, CHANGELOG.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le switch Binardat differe de Cisco sur deux points que le devis ignorait :
- masque d'ACL normal (255.255.0.0) au lieu du wildcard inverse (0.0.255.255) ;
- pas de commande 'remark' (commentaires d'ACL).
devis_reseau.py gagne un dialecte (defaut cisco, option binardat) : masque_acl()
et remarque() adaptent la sortie. Reglable par --dialecte, SETOPS_DIALECTE, ou
make devis-reseau DIALECTE=binardat. Le GUI suit l'env. Public reste generique.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Les 78 exercices « À toi de jouer » du wiki forment un plan de tests d'acceptation.
Formalisé sans dupliquer :
- scripts/plan_recette.py + make plan-recette : GÉNÈRE docs/audit/plan-de-recette.md
depuis les exercices du wiki. Grille auto-contenue par unité, colonnes : ce qu'on
éprouve · le geste · type (observe/casse-répare) · Preuve auto (le Pxx extrait du
texte -> quels gestes manuels sont AUSSI gardés par la machine). Générée -> ne peut
pas dériver du wiki.
- Preuve P22 : plan_recette.py --verifier échoue si le fichier committé est périmé.
Le plan de recette devient auto-gardé.
- Honnêteté de couverture assumée : « — » = manuel seul ; pas d'exhaustivité au-delà
des exercices du wiki.
Pendant humain de make prouver (le harnais prouve le moteur P01-P21, la recette valide
l'exploitation) ; checklist du protocole-operateur-independant (« exploitable sans IA »).
Validé : 78 gestes / 19 unités, 5 doublés d'un Pxx ; P22 détecte une dérive (testé) ;
make verifier -> CONFORME 22/22 (instance cohérente).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Cinquième page de la vague méthode. Au moule à 4 temps : éditer la SOURCE (le
plan) pas l'artefact, dry-run avant apply, et l'invalide rendu impossible à
saisir (le <select> « Hôte » ne propose que des hôtes réels -> pas d'hôte
fantôme, garde P19). Exercices ancrés dans le réel (dont réparer un hôte
fantôme via le select). Raccordée dans _Sidebar (Socle & méthode).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le wiki enseignait les fondamentaux SERVICES mais pas la MÉTHODE. Quatre pages
au moule à 4 temps (concept -> Set-OPS -> transférable -> à toi de jouer), avec
exercices concrets :
- Le plan & l'adressage dérivé (un seed, tout en découle).
- Multi-instance & fédération (un moteur, N écosystèmes ; découverte par
convention, garde-fou P21).
- La preuve (ne jamais affirmer plus que ce qu'on prouve ; make prouver P01-P21).
- Glossaire (24 concepts ; était « à venir »).
Raccordées dans Home.md et _Sidebar.md (section « Flotte & preuve »). Le wiki
pointe vers docs/, ne recopie pas. 855 -> 1573 lignes, 22 pages, aucun lien mort.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Symétrique d'instance-creer, produit un modèle dans le dépôt PRIVÉ
(Set-OPS-Modeles/, jamais exemples/modeles/). scripts/model_creer.py, 2 modes :
- MODE=base : copie un modèle générique.
- MODE=instance : PROMEUT une instance en modèle (généralise identité ->
exemple.*, index -> 1, production -> false, nftables_admin_ssh -> [], cle de
sauvegarde videe, proxmox.yml generique [D1], inventaire principal [D2]).
Surete : aucun secret ne sort (vault.yml/proxmox.vault.yml jamais copies ; refus
si l'un subsiste ; .example conserve). Copie CIBLEE (plan/ + inventories/ seulement,
symlinks resolus) -> robuste aux depots imbriques et boucles de symlinks. Le modele
produit doit valider (modeles.py verifier), sinon annule.
Teste en isolement (depot prive jamais touche) : promotion du labo reussie +
validee (aucun chezlepro residuel, aucun secret, proxmox generique) ; copie socle
OK ; refus d'une instance INVALIDE (garde-fou : hote fantome detecte).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Le moteur reste independant des instances ET des modeles assembles (prives) :
il ne contient que le modele public socle ; les modeles assembles sont
DECOUVERTS au runtime via SETOPS_MODELES (depot prive de l'operateur), jamais
embarques dans le code public.
- scripts/instance_creer.py : copie un modele (exemples/modeles/* + SETOPS_MODELES)
vers un depot frere ../<nom>, y fixe l'index, et refuse un nom existant, un
modele inconnu, ou un index en collision avec une instance federee (verifie
AVANT toute copie). .git et hosts.genere.yml non copies.
- make instance-creer NOM=.. MODELE=.. [INDEX=N] ; make instance-modeles.
- GUI (vue Reseau) : formulaire « Creer une instance depuis un modele » sous la
flotte. POST /api/instance-creer ; /api/instances renvoie aussi modeles +
index_pris. Ne bascule pas l'active.
Corrige : gabarit de voute du labo complete (P18 a echoue en passant l'active
sur le labo, dont le vault.yml.example etait reste a 17 cles ; aligne sur 23).
Valide : garde-fous de creation testes en isolement (collision refusee avant
copie, ecrasement refuse, modele inconnu refuse, aucune pollution) ; node --check ;
make verifier rc=0 CONFORME 21/21 (active = labo).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>