Set-OPS-Public/scripts/serveurs.py

157 lines
6 KiB
Python
Raw Normal View History

#!/usr/bin/env python3
"""Registre des serveurs (VM) du plan Set-OPS.
documentation : la tournee des 74 documents, parce qu un balayage ne lit pas La revision a commence par un balayage par motifs — chemins morts, cibles make absentes, comptes derives. Il a trouve une trentaine d ecarts et rate presque tout le reste : un motif ne voit que ce qui s exprime en motif. make hote-planifier en est l exemple. La cible EXISTE, donc le controle passait au vert. C est une cible depreciee qui refuse et sort en 2, recommandee par AGENTS.md, et qui contredit la REGLE D OR du meme fichier trois ecrans plus haut. Il fallait lire pour la voir. 74 documents lus un par un. 66 corriges, 8 exacts. CE QUI ETAIT FRANCHEMENT FAUX AGENTS.md, la source d autorite, annoncait la flotte pas encore executee contre des VM reelles. Elle a ete rasee et remontee depuis zero trois fois. ecosysteme-chezlepro.md, le document montre a un client, portait la meme phrase : il se sous-vendait gravement. courriel-conception.md s ouvrait sur aucun role n est encore ecrit, au-dessus de son propre paragraphe 1 qui les nomme. autorisation.md se terminait sur rien n est construit alors qu il rapporte des mesures datees du role en fonctionnement. hebergeur-exploitation.md disait rien n est fait d un depot qui existe. filiation-emancipation.md se contredisait a deux ecrans de distance. DES MODELES DECRITS D APRES UN MONDE ANTERIEUR Le resolveur : cinq documents decrivaient un Unbound par VM en opt-in, trois le donnaient en exemple d integration FACULTATIVE — il est universel depuis le 2026-08-24. L adressage de nomenclature-vm.md : reseau unique, VLAN 11-15, VMID a cinq chiffres. Le nommage SDN de sdn-evpn.md contre le code : c est le wiki qui avait raison. CE QUI CASSE AU PREMIER ESSAI Le nom du gabarit dore etait faux a quatre endroits, dont la procedure qui le FABRIQUE et le critere R2 de l epreuve d operateur independant. preparer-un-site-hebergeur.md avertissait qu une VM faite a la main serait detruite : raser derive du plan, il ne la detruira jamais — le risque est l inverse. Un mot de passe d essai en clair dans un depot public. DEUX PREUVES ETENDUES, ET UNE QUI SE TROMPAIT ELLE-MEME P57 couvre les groupes : elle a signale aussitot 29 groupes annonces au-dessus d un tableau qui en cite 40. P29 confronte le tableau de authentification.md aux declarations reelles : 12 annonces, 21 reels. Et P57 imposait un chiffre faux — 56 preuves alors que le depot en porte 57, la conditionnelle vivant hors de tout comptage. Un garde-fou qui fait respecter une erreur ajoute l assurance a l erreur. CE QUI RESTE, ET QU AUCUNE PREUVE NE TIENT Deux comptes trouves a la main. Et une lacune reelle : rien ne garde les meta/acces.yml — ni qu un service web-sso en porte un, ni que le groupe qu il nomme existe. P29 tient les positions d authentification, personne ne tient les habilitations. make prouver : CONFORME, 56 OK, 0 echec, 1 saute. 0 lien mort. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-06 16:18:23 -04:00
LE PLAN EST L'AUTORITE, ET CE N'EST PLUS L'INVERSE (corrige le 2026-09-06). Ce module a
longtemps annonce « Phase 2 : l'inventaire reste AUTORITE [...] la generation depuis le
plan viendra en Phase 3 ». La Phase 3 est faite depuis longtemps : `make instancier`
genere `hosts.yml` DEPUIS ce registre, et la REGLE D'OR d'AGENTS.md interdit d'editer
l'inventaire a la main. Lire l'ancienne phrase aujourd'hui, c'est croire l'exact contraire
de la doctrine.
`bootstrap` reste utile, mais c'est une manoeuvre de REPRISE, pas le flux normal : il
(re)constitue `plan/serveurs.yml` depuis un inventaire existant — le geste qu'on fait une
fois, quand on herite d'un ecosysteme dont le plan n'existe pas encore.
"""
from __future__ import annotations
import os
import argparse
import sys
from pathlib import Path
import yaml
from inventory_gui import charger_yaml, liste_hotes
from inventory_rules import (
plan : l ecriture des registres devient atomique — tout, ou rien `path.open("w")` TRONQUE avant d ecrire : entre les deux, le fichier est vide. Une exception dans yaml.safe_dump, un disque plein, un Ctrl-C, et instance/plan/serveurs.yml reste mutile. L asymetrie fait la gravite : hosts.yml se regenere d un make instancier-appliquer, le PLAN ne se regenere de rien. C est la source unique de verite. Git est le filet, mais encore faut-il savoir qu on est tombe. TREIZE SITES, UNE SEULE FONCTION Le defaut n etait pas dans le GUI seul : douze sites dans sept fichiers, dont les miroirs CLI des MEMES registres. Corriger le GUI seul aurait recree la divergence que P41 garde depuis les neuf resolutions d instance. La fonction vit donc dans inventory_rules.py, que les sept importaient deja. Une source, pas douze. TROIS DETAILS QUI FONT LA DIFFERENCE ENTRE « CA MARCHE » ET « CA TIENT » temporaire dans le MEME dossier os.replace n est atomique qu au sein d un meme systeme de fichiers ; un /tmp sur une autre partition casserait la garantie sans rien dire fsync AVANT le rename sinon le renommage peut atteindre le disque avant le contenu : au retour d une coupure brutale, un fichier neuf et VIDE — le defaut qu on ferme, deplace d un cran report des droits mkstemp cree en 0600, le plan est en 0664 et doit rester lisible par le groupe sur les runners LE TEST PORTE SON PROPRE CONTROLE NEGATIF scripts/tests/test_ecriture_atomique.py rejoue D ABORD l ancienne forme et verifie qu elle DETRUIT. Sans ce controle, « le fichier est intact » ne prouverait rien — il pourrait l etre parce que rien n a ete ecrit du tout. Une garantie qu on n a jamais vue echouer n est pas une garantie, c est une habitude. Branche sur P02, dont le titre annoncait « inventory_host » alors qu il lance maintenant trois tests. Corrige au passage. LA VOUTE DU GUI : VERIFIEE, PAS DE DEFAUT Le soupcon etait qu executer_flux pose ANSIBLE_VAULT_PASSWORD_FILE (un seul mot de passe) alors que creer une VM ouvre DEUX voutes depuis « une voute, une cle ». Eprouve contre deux voutes JETABLES a mots de passe distincts — jamais les vraies. Les deux variables se CUMULENT : Ansible essaie tous les secrets, et un PASSWORD_FILE errone n empeche rien. Confirme en sondant l environnement qu une recette make recoit reellement : le mot de passe saisi ET les cinq cles calculees par voutes.py. Ce qui sauve ce chemin n est donc pas le mot de passe saisi, c est l IDENTITY_LIST que make pose par-dessus. Chacun couvre ce que l autre ne couvre pas — le PASSWORD_FILE sert le runner qui n a que sa cle, l IDENTITY_LIST le poste qui les a toutes. Ecrit au-dessus du code, pour que personne ne « simplifie » en retirant l un des deux. make prouver : CONFORME, 59 OK, 0 echec, 1 saute. make instancier : DIFF VIDE, quatre registres relus, droits 664 preserves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 14:57:57 -04:00
ecriture_atomique,
charger_nomenclature,
charger_serveurs,
fonction_seq,
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
integrations_universelles,
resolution d'instance : une seule, partagee — au lieu de neuf copies Cinq jours, cinq defauts, tous de la meme famille : « quelle instance, quel inventaire ? » Neuf modules portaient chacun leur reponse. - 18 aout : P03 comparait chaque instance a l'inventaire d'une AUTRE ; - 19 aout : verifier_ports codait `principal/` en dur ; verifier_intrants et _frontiere_absente lisaient le symlink au lieu de la variable ; - 20 aout : devis_placement rendait un verdict juste sur le mauvais tenant ; - 22 aout : P35, puis P36 — la dixieme, trouvee par la preuve elle-meme. Aucune n'etait une faute d'inattention : chacune avait ete ecrite de bonne foi, a un moment ou le besoin semblait local. C'est le mode de panne de la duplication — pas l'erreur, mais la DERIVE, invisible depuis l'interieur d'un fichier. LA RESOLUTION UNIQUE. `inventory_rules` porte instance_courante(), inventaire_de(), dossier_inventaire() et plan_de(). Trois niveaux de repli, dont le TROISIEME manquait a la moitie des copies : un hosts.yml existant, puis un REPERTOIRE existant (instance neuve — c'est ce qui faisait echouer `make instancier` sur le modele public), puis le defaut. Vingt-huit modules y sont branches. CE QUI REND CE REFACTOR SUR : avant de toucher quoi que ce soit, chaque module a ete interroge sur ce qu'il resolvait, pour les DEUX ecosystemes. Apres refactor, meme mesure : 17 modules x 2 instances, diff VIDE. Aucune resolution n'a change — prouve, pas suppose. P41 echoue des qu'un module reintroduit une copie. Eprouvee en negatif : une copie replacee dans genome.py est signalee avec son numero de ligne. Trois exemptions nommees : instances.py et inventory_gui.py manipulent le SYMLINK lui-meme (bascule d'instance), et devis_opnsense lit deliberement quelle instance est ACTIVE. Elles parlent du lien, pas de la resolution. make verifier 41 OK, 0 echec, 0 saute ; make ci idem ; lint vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:19:55 -04:00
instance_courante,
inventaire_de,
reconcilier_serveur,
valider_serveurs,
)
RACINE = Path(__file__).resolve().parents[1]
resolution d'instance : une seule, partagee — au lieu de neuf copies Cinq jours, cinq defauts, tous de la meme famille : « quelle instance, quel inventaire ? » Neuf modules portaient chacun leur reponse. - 18 aout : P03 comparait chaque instance a l'inventaire d'une AUTRE ; - 19 aout : verifier_ports codait `principal/` en dur ; verifier_intrants et _frontiere_absente lisaient le symlink au lieu de la variable ; - 20 aout : devis_placement rendait un verdict juste sur le mauvais tenant ; - 22 aout : P35, puis P36 — la dixieme, trouvee par la preuve elle-meme. Aucune n'etait une faute d'inattention : chacune avait ete ecrite de bonne foi, a un moment ou le besoin semblait local. C'est le mode de panne de la duplication — pas l'erreur, mais la DERIVE, invisible depuis l'interieur d'un fichier. LA RESOLUTION UNIQUE. `inventory_rules` porte instance_courante(), inventaire_de(), dossier_inventaire() et plan_de(). Trois niveaux de repli, dont le TROISIEME manquait a la moitie des copies : un hosts.yml existant, puis un REPERTOIRE existant (instance neuve — c'est ce qui faisait echouer `make instancier` sur le modele public), puis le defaut. Vingt-huit modules y sont branches. CE QUI REND CE REFACTOR SUR : avant de toucher quoi que ce soit, chaque module a ete interroge sur ce qu'il resolvait, pour les DEUX ecosystemes. Apres refactor, meme mesure : 17 modules x 2 instances, diff VIDE. Aucune resolution n'a change — prouve, pas suppose. P41 echoue des qu'un module reintroduit une copie. Eprouvee en negatif : une copie replacee dans genome.py est signalee avec son numero de ligne. Trois exemptions nommees : instances.py et inventory_gui.py manipulent le SYMLINK lui-meme (bascule d'instance), et devis_opnsense lit deliberement quelle instance est ACTIVE. Elles parlent du lien, pas de la resolution. make verifier 41 OK, 0 echec, 0 saute ; make ci idem ; lint vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:19:55 -04:00
INSTANCE = instance_courante()
FICHIER = INSTANCE / "plan/serveurs.yml"
FICHIER_NOMENCLATURE = INSTANCE / "plan/nomenclature.yml"
resolution d'instance : une seule, partagee — au lieu de neuf copies Cinq jours, cinq defauts, tous de la meme famille : « quelle instance, quel inventaire ? » Neuf modules portaient chacun leur reponse. - 18 aout : P03 comparait chaque instance a l'inventaire d'une AUTRE ; - 19 aout : verifier_ports codait `principal/` en dur ; verifier_intrants et _frontiere_absente lisaient le symlink au lieu de la variable ; - 20 aout : devis_placement rendait un verdict juste sur le mauvais tenant ; - 22 aout : P35, puis P36 — la dixieme, trouvee par la preuve elle-meme. Aucune n'etait une faute d'inattention : chacune avait ete ecrite de bonne foi, a un moment ou le besoin semblait local. C'est le mode de panne de la duplication — pas l'erreur, mais la DERIVE, invisible depuis l'interieur d'un fichier. LA RESOLUTION UNIQUE. `inventory_rules` porte instance_courante(), inventaire_de(), dossier_inventaire() et plan_de(). Trois niveaux de repli, dont le TROISIEME manquait a la moitie des copies : un hosts.yml existant, puis un REPERTOIRE existant (instance neuve — c'est ce qui faisait echouer `make instancier` sur le modele public), puis le defaut. Vingt-huit modules y sont branches. CE QUI REND CE REFACTOR SUR : avant de toucher quoi que ce soit, chaque module a ete interroge sur ce qu'il resolvait, pour les DEUX ecosystemes. Apres refactor, meme mesure : 17 modules x 2 instances, diff VIDE. Aucune resolution n'a change — prouve, pas suppose. P41 echoue des qu'un module reintroduit une copie. Eprouvee en negatif : une copie replacee dans genome.py est signalee avec son numero de ligne. Trois exemptions nommees : instances.py et inventory_gui.py manipulent le SYMLINK lui-meme (bascule d'instance), et devis_opnsense lit deliberement quelle instance est ACTIVE. Elles parlent du lien, pas de la resolution. make verifier 41 OK, 0 echec, 0 saute ; make ci idem ; lint vert. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-22 14:19:55 -04:00
INVENTAIRE = inventaire_de(INSTANCE)
# Champs de placement / dimensionnement NON derivables (proviennent du plan).
CHAMPS_PLAN = [("noeud", "noeud"), ("stockage", "stockage"),
("disque_taille", "disque"), ("memoire", "memoire"), ("coeurs", "coeurs")]
def ecrire(registre: dict) -> None:
entete = (
"# Registre des serveurs (VM) du plan Set-OPS.\n"
documentation : la tournee des 74 documents, parce qu un balayage ne lit pas La revision a commence par un balayage par motifs — chemins morts, cibles make absentes, comptes derives. Il a trouve une trentaine d ecarts et rate presque tout le reste : un motif ne voit que ce qui s exprime en motif. make hote-planifier en est l exemple. La cible EXISTE, donc le controle passait au vert. C est une cible depreciee qui refuse et sort en 2, recommandee par AGENTS.md, et qui contredit la REGLE D OR du meme fichier trois ecrans plus haut. Il fallait lire pour la voir. 74 documents lus un par un. 66 corriges, 8 exacts. CE QUI ETAIT FRANCHEMENT FAUX AGENTS.md, la source d autorite, annoncait la flotte pas encore executee contre des VM reelles. Elle a ete rasee et remontee depuis zero trois fois. ecosysteme-chezlepro.md, le document montre a un client, portait la meme phrase : il se sous-vendait gravement. courriel-conception.md s ouvrait sur aucun role n est encore ecrit, au-dessus de son propre paragraphe 1 qui les nomme. autorisation.md se terminait sur rien n est construit alors qu il rapporte des mesures datees du role en fonctionnement. hebergeur-exploitation.md disait rien n est fait d un depot qui existe. filiation-emancipation.md se contredisait a deux ecrans de distance. DES MODELES DECRITS D APRES UN MONDE ANTERIEUR Le resolveur : cinq documents decrivaient un Unbound par VM en opt-in, trois le donnaient en exemple d integration FACULTATIVE — il est universel depuis le 2026-08-24. L adressage de nomenclature-vm.md : reseau unique, VLAN 11-15, VMID a cinq chiffres. Le nommage SDN de sdn-evpn.md contre le code : c est le wiki qui avait raison. CE QUI CASSE AU PREMIER ESSAI Le nom du gabarit dore etait faux a quatre endroits, dont la procedure qui le FABRIQUE et le critere R2 de l epreuve d operateur independant. preparer-un-site-hebergeur.md avertissait qu une VM faite a la main serait detruite : raser derive du plan, il ne la detruira jamais — le risque est l inverse. Un mot de passe d essai en clair dans un depot public. DEUX PREUVES ETENDUES, ET UNE QUI SE TROMPAIT ELLE-MEME P57 couvre les groupes : elle a signale aussitot 29 groupes annonces au-dessus d un tableau qui en cite 40. P29 confronte le tableau de authentification.md aux declarations reelles : 12 annonces, 21 reels. Et P57 imposait un chiffre faux — 56 preuves alors que le depot en porte 57, la conditionnelle vivant hors de tout comptage. Un garde-fou qui fait respecter une erreur ajoute l assurance a l erreur. CE QUI RESTE, ET QU AUCUNE PREUVE NE TIENT Deux comptes trouves a la main. Et une lacune reelle : rien ne garde les meta/acces.yml — ni qu un service web-sso en porte un, ni que le groupe qu il nomme existe. P29 tient les positions d authentification, personne ne tient les habilitations. make prouver : CONFORME, 56 OK, 0 echec, 1 saute. 0 lien mort. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-06 16:18:23 -04:00
"# CE FICHIER EST L'AUTORITE : l'inventaire en est GENERE (make instancier).\n"
"# Ne jamais editer instance/inventories/*/hosts.yml a la main.\n"
"# Reconstitue ici depuis un inventaire existant par `make serveurs-bootstrap` —\n"
"# manoeuvre de reprise, pas le flux normal.\n"
"# VMID/IP/VLAN/passerelle sont DERIVES de la fonction via\n"
"# instance/plan/nomenclature.yml (jamais stockes ici).\n"
"---\n"
)
plan : l ecriture des registres devient atomique — tout, ou rien `path.open("w")` TRONQUE avant d ecrire : entre les deux, le fichier est vide. Une exception dans yaml.safe_dump, un disque plein, un Ctrl-C, et instance/plan/serveurs.yml reste mutile. L asymetrie fait la gravite : hosts.yml se regenere d un make instancier-appliquer, le PLAN ne se regenere de rien. C est la source unique de verite. Git est le filet, mais encore faut-il savoir qu on est tombe. TREIZE SITES, UNE SEULE FONCTION Le defaut n etait pas dans le GUI seul : douze sites dans sept fichiers, dont les miroirs CLI des MEMES registres. Corriger le GUI seul aurait recree la divergence que P41 garde depuis les neuf resolutions d instance. La fonction vit donc dans inventory_rules.py, que les sept importaient deja. Une source, pas douze. TROIS DETAILS QUI FONT LA DIFFERENCE ENTRE « CA MARCHE » ET « CA TIENT » temporaire dans le MEME dossier os.replace n est atomique qu au sein d un meme systeme de fichiers ; un /tmp sur une autre partition casserait la garantie sans rien dire fsync AVANT le rename sinon le renommage peut atteindre le disque avant le contenu : au retour d une coupure brutale, un fichier neuf et VIDE — le defaut qu on ferme, deplace d un cran report des droits mkstemp cree en 0600, le plan est en 0664 et doit rester lisible par le groupe sur les runners LE TEST PORTE SON PROPRE CONTROLE NEGATIF scripts/tests/test_ecriture_atomique.py rejoue D ABORD l ancienne forme et verifie qu elle DETRUIT. Sans ce controle, « le fichier est intact » ne prouverait rien — il pourrait l etre parce que rien n a ete ecrit du tout. Une garantie qu on n a jamais vue echouer n est pas une garantie, c est une habitude. Branche sur P02, dont le titre annoncait « inventory_host » alors qu il lance maintenant trois tests. Corrige au passage. LA VOUTE DU GUI : VERIFIEE, PAS DE DEFAUT Le soupcon etait qu executer_flux pose ANSIBLE_VAULT_PASSWORD_FILE (un seul mot de passe) alors que creer une VM ouvre DEUX voutes depuis « une voute, une cle ». Eprouve contre deux voutes JETABLES a mots de passe distincts — jamais les vraies. Les deux variables se CUMULENT : Ansible essaie tous les secrets, et un PASSWORD_FILE errone n empeche rien. Confirme en sondant l environnement qu une recette make recoit reellement : le mot de passe saisi ET les cinq cles calculees par voutes.py. Ce qui sauve ce chemin n est donc pas le mot de passe saisi, c est l IDENTITY_LIST que make pose par-dessus. Chacun couvre ce que l autre ne couvre pas — le PASSWORD_FILE sert le runner qui n a que sa cle, l IDENTITY_LIST le poste qui les a toutes. Ecrit au-dessus du code, pour que personne ne « simplifie » en retirant l un des deux. make prouver : CONFORME, 59 OK, 0 echec, 1 saute. make instancier : DIFF VIDE, quatre registres relus, droits 664 preserves. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-08 14:57:57 -04:00
with ecriture_atomique(FICHIER) as fichier:
fichier.write(entete)
yaml.safe_dump({"serveurs": registre.get("serveurs", {}) or {}},
fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
def hotes_inventaire() -> dict:
return {h["nom"]: h for h in liste_hotes(charger_yaml(INVENTAIRE)) if h.get("nom")}
def bootstrap() -> dict:
serveurs = {}
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
# Les universelles ne se recopient pas dans le plan (D-33) : les reinscrire ici
# reconstituerait, a chaque bootstrap, les lignes qu'on vient de retirer.
universelles = set(integrations_universelles())
for nom, h in hotes_inventaire().items():
fonction, _ = fonction_seq(nom)
srv = {"fonction": fonction, "etat": h.get("etat", "planifie")}
for cle_src, cle_dst in CHAMPS_PLAN:
v = str(h.get(cle_src, "")).strip()
if v:
srv[cle_dst] = int(v) if v.isdigit() else v
intégrations : le rôle déclare sa politique ; le cluster passe à l'hébergeur Deux corrections de propriété, l'une dans le plan, l'autre dans les intrants. 1. Intégrations universelles (D-33/D-34, P26) Le plan portait 57 lignes d'intégration écrites à la main, dont 28 disaient oui à quelque chose de vrai pour tous les hôtes. Elles n'existaient que pour être oubliées — et elles l'avaient été : dans Chezlepro, backup-01 et infra-pki-01 n'étaient ni supervisés, ni journalisés, ni certifiés. Le rôle déclare désormais sa politique une fois, dans meta/integration.yml ; le plan ne garde que les vrais choix et refuse la recopie. Les exemptions se dérivent du service rendu (sauf_role), jamais d'un nom d'hôte : l'AC ne s'enrôle pas auprès d'elle-même, et l'exemption suit step-ca si on le déplace. Une seule fonction de résolution — integrations_de() — lue par l'inventaire, la voûte et le panneau. Sans le passage par la voûte, les secrets des intégrations universelles auraient cessé d'être exigés et P18 serait passé au vert sur une voûte incomplète. Vérifié : diff vide sur Technolibre (la politique reproduit exactement les 41 lignes retirées) ; sur Chezlepro, exactement les groupes manquants, et pas client_pki sur infra-pki-01. 2. Vue Intégrations : la matrice La fiche montrait les intégrations d'UN serveur ; le trou de Chezlepro n'a pas été trouvé par le panneau mais par le devis de pare-feu. Matrice serveurs x intégrations : colonnes de politique en lecture seule, facultatives cochables sur place, ligne de couverture n/N qui rend le motif visible sans le juger. 3. Propriété des intrants (D-35/D-36, P27) Le cluster Proxmox appartient à l'hébergeur, comme sa fabric et sa frontière. Recopié chez chaque tenant, son inventaire avait déjà divergé : deux listes de stockages contradictoires pour le même matériel. API/nœuds/stockages/ponts vont dans proxmox-hebergeur.yml, à côté d'underlay.yml, dont le chemin se dérive — l'hébergeur reste non déclaré (D-17). Restent au tenant son golden template et ses défauts de placement. Le panneau nomme désormais le propriétaire de chaque section : éditer une section « hébergeur » vaut pour tous ses tenants, et l'écran ne le disait pas. 26 preuves OK, 0 échec. --syntax-check des deux playbooks Proxmox. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-03 14:09:22 -04:00
integrations = sorted(g for g in (h.get("groupes") or [])
if g.startswith("client_") and g not in universelles)
if integrations:
srv["integrations"] = integrations
serveurs[nom] = srv
return {"serveurs": serveurs}
def lister() -> None:
registre = charger_serveurs(FICHIER)
serveurs = registre.get("serveurs", {})
if not serveurs:
print("Aucun serveur. Lance: make serveurs-bootstrap")
return
nomenclature = charger_nomenclature(FICHIER_NOMENCLATURE)
hotes = hotes_inventaire()
for nom, srv in serveurs.items():
rec = reconcilier_serveur(nom, srv, hotes.get(nom), nomenclature)
d = rec["derive"]
if rec["absent_inventaire"]:
statut = "ABSENT de l'inventaire"
elif rec["divergences"]:
statut = "DIVERGENCE"
else:
statut = "reconcilie"
print(f"{nom} [fonction {srv.get('fonction')}, etat {srv.get('etat', '?')}]"
f" derive: vmid {d.get('vmid', '?')}, ip {d.get('adresse_ip', '?')}, vlan {d.get('vlan', '?')} -> {statut}")
for dv in rec["divergences"]:
print(f" ! {dv}")
def verifier() -> int:
registre = charger_serveurs(FICHIER)
nomenclature = charger_nomenclature(FICHIER_NOMENCLATURE)
valider_serveurs(registre, nomenclature)
hotes = hotes_inventaire()
divergents = []
for nom, srv in registre.get("serveurs", {}).items():
rec = reconcilier_serveur(nom, srv, hotes.get(nom), nomenclature)
if rec["divergences"] or rec["absent_inventaire"]:
divergents.append(nom)
if divergents:
print(f"Registre valide, mais {len(divergents)} serveur(s) non reconcilie(s): {', '.join(divergents)}")
else:
print("Registre des serveurs valide et reconcilie avec l'inventaire.")
return 0
def main() -> int:
parser = argparse.ArgumentParser(description="Registre des serveurs (VM) du plan Set-OPS.")
sub = parser.add_subparsers(dest="commande", required=True)
sub.add_parser("lister", help="Affiche les serveurs et leur reconciliation avec l'inventaire.")
sub.add_parser("verifier", help="Valide le registre et signale les divergences.")
sub.add_parser("bootstrap", help="(Re)genere instance/plan/serveurs.yml depuis l'inventaire actuel.")
args = parser.parse_args()
try:
if args.commande == "lister":
lister()
elif args.commande == "verifier":
return verifier()
elif args.commande == "bootstrap":
registre = bootstrap()
valider_serveurs(registre, charger_nomenclature(FICHIER_NOMENCLATURE))
ecrire(registre)
print(f"instance/plan/serveurs.yml genere depuis l'inventaire ({len(registre['serveurs'])} serveurs).")
except Exception as exc:
print(f"erreur: {exc}", file=sys.stderr)
return 2
return 0
if __name__ == "__main__":
raise SystemExit(main())