2026-06-24 20:17:46 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
"""Assistant de configuration Proxmox pour Set-OPS."""
|
|
|
|
|
|
|
|
|
|
from __future__ import annotations
|
|
|
|
|
|
|
|
|
|
from getpass import getpass
|
|
|
|
|
from pathlib import Path
|
|
|
|
|
import os
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
|
|
|
|
|
import yaml
|
|
|
|
|
|
P03 : la preuve comparait chaque instance a l'inventaire d'UNE SEULE
Trouve en validant une mise a jour du CHANGELOG. Deux invocations de la meme preuve,
deux verdicts : `make prouver` -> NON CONFORME (« lab : 17 hotes avec ecart »),
`python3 scripts/prouver.py` -> CONFORME 37/37. Le lab n'avait aucun ecart.
DEUX VARIABLES DESIGNENT LA CIBLE, ET LA SECONDE GAGNE. Le Makefile exporte
SETOPS_INVENTAIRE (ligne 13), derive de l'instance ACTIVE ; instancier.py:68 lui fait
FORCER la cible par-dessus SETOPS_INSTANCE. P03 (prouver.py:505) ne redirigeait que
SETOPS_INSTANCE : elle generait le plan de CHAQUE instance federee et le comparait a
l'inventaire applique de la SEULE instance active.
LE ROUGE N'ETAIT PAS LE PROBLEME, LE VERT L'ETAIT. Sous `make`, l'inventaire applique
de lab et de Technolibre n'etait JAMAIS lu — l'angle meme pour lequel P03 a ete ecrite
le 2026-08-12 (un tenant qu'on ne regarde pas imposant ses vieilles adresses au pare-feu
partage). La preuve etait aveugle a son propre cas, par l'invocation documentee. Les
rapports du 13 et du 14 sortent de cette invocation-la. Signature visible sans lire le
code : les hosts.genere.yml de lab et de Technolibre ne bougeaient pas.
CORRECTIF, cinq sites : env.pop("SETOPS_INVENTAIRE") partout ou l'on redirige
SETOPS_INSTANCE — P03 et P15, plus les trois applicateurs (opnsense, proxmox_fw, sdn) qui
pointent vers l'HEBERGEUR. Ces trois sont sans effet tant qu'hebergeur et tenant actif
coincident, c'est-a-dire jusqu'au second site. Le geste existait deja (modeles.py:96).
GARDE, pour que la classe cesse d'etre silencieuse : inventory_rules.inventaire_force()
REFUSE une cible hors de l'instance visee, en nommant les deux valeurs. Eprouvee dans les
deux sens (contradiction -> code 1 ; cible legitime dans l'instance -> passe). Branchee
sur les quatre resolutions de _inventaire (instancier, serveurs, applications,
config_proxmox). Le GUI garde la sienne : il ne redirige jamais SETOPS_INSTANCE pour un
fils et resout par symlink a chaque requete.
make prouver : 37 OK, 0 echec, 0 saute — et les hosts.genere.yml des TROIS instances
portent l'horodatage du passage. make test 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:35:01 -04:00
|
|
|
from inventory_rules import inventaire_force
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
|
|
|
|
|
RACINE = Path(__file__).resolve().parents[1]
|
|
|
|
|
INSTANCE = Path(os.environ.get("SETOPS_INSTANCE") or (RACINE / "instance"))
|
2026-06-30 16:08:25 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def _inventaire_dir(instance: Path, *noms: str) -> Path:
|
|
|
|
|
"""Répertoire de l'inventaire modèle (porte la config Proxmox / le clonage)."""
|
P03 : la preuve comparait chaque instance a l'inventaire d'UNE SEULE
Trouve en validant une mise a jour du CHANGELOG. Deux invocations de la meme preuve,
deux verdicts : `make prouver` -> NON CONFORME (« lab : 17 hotes avec ecart »),
`python3 scripts/prouver.py` -> CONFORME 37/37. Le lab n'avait aucun ecart.
DEUX VARIABLES DESIGNENT LA CIBLE, ET LA SECONDE GAGNE. Le Makefile exporte
SETOPS_INVENTAIRE (ligne 13), derive de l'instance ACTIVE ; instancier.py:68 lui fait
FORCER la cible par-dessus SETOPS_INSTANCE. P03 (prouver.py:505) ne redirigeait que
SETOPS_INSTANCE : elle generait le plan de CHAQUE instance federee et le comparait a
l'inventaire applique de la SEULE instance active.
LE ROUGE N'ETAIT PAS LE PROBLEME, LE VERT L'ETAIT. Sous `make`, l'inventaire applique
de lab et de Technolibre n'etait JAMAIS lu — l'angle meme pour lequel P03 a ete ecrite
le 2026-08-12 (un tenant qu'on ne regarde pas imposant ses vieilles adresses au pare-feu
partage). La preuve etait aveugle a son propre cas, par l'invocation documentee. Les
rapports du 13 et du 14 sortent de cette invocation-la. Signature visible sans lire le
code : les hosts.genere.yml de lab et de Technolibre ne bougeaient pas.
CORRECTIF, cinq sites : env.pop("SETOPS_INVENTAIRE") partout ou l'on redirige
SETOPS_INSTANCE — P03 et P15, plus les trois applicateurs (opnsense, proxmox_fw, sdn) qui
pointent vers l'HEBERGEUR. Ces trois sont sans effet tant qu'hebergeur et tenant actif
coincident, c'est-a-dire jusqu'au second site. Le geste existait deja (modeles.py:96).
GARDE, pour que la classe cesse d'etre silencieuse : inventory_rules.inventaire_force()
REFUSE une cible hors de l'instance visee, en nommant les deux valeurs. Eprouvee dans les
deux sens (contradiction -> code 1 ; cible legitime dans l'instance -> passe). Branchee
sur les quatre resolutions de _inventaire (instancier, serveurs, applications,
config_proxmox). Le GUI garde la sienne : il ne redirige jamais SETOPS_INSTANCE pour un
fils et resout par symlink a chaque requete.
make prouver : 37 OK, 0 echec, 0 saute — et les hosts.genere.yml des TROIS instances
portent l'horodatage du passage. make test 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:35:01 -04:00
|
|
|
forced = inventaire_force(instance) # refuse une cible hors de `instance`
|
2026-06-30 16:08:25 -04:00
|
|
|
if forced:
|
P03 : la preuve comparait chaque instance a l'inventaire d'UNE SEULE
Trouve en validant une mise a jour du CHANGELOG. Deux invocations de la meme preuve,
deux verdicts : `make prouver` -> NON CONFORME (« lab : 17 hotes avec ecart »),
`python3 scripts/prouver.py` -> CONFORME 37/37. Le lab n'avait aucun ecart.
DEUX VARIABLES DESIGNENT LA CIBLE, ET LA SECONDE GAGNE. Le Makefile exporte
SETOPS_INVENTAIRE (ligne 13), derive de l'instance ACTIVE ; instancier.py:68 lui fait
FORCER la cible par-dessus SETOPS_INSTANCE. P03 (prouver.py:505) ne redirigeait que
SETOPS_INSTANCE : elle generait le plan de CHAQUE instance federee et le comparait a
l'inventaire applique de la SEULE instance active.
LE ROUGE N'ETAIT PAS LE PROBLEME, LE VERT L'ETAIT. Sous `make`, l'inventaire applique
de lab et de Technolibre n'etait JAMAIS lu — l'angle meme pour lequel P03 a ete ecrite
le 2026-08-12 (un tenant qu'on ne regarde pas imposant ses vieilles adresses au pare-feu
partage). La preuve etait aveugle a son propre cas, par l'invocation documentee. Les
rapports du 13 et du 14 sortent de cette invocation-la. Signature visible sans lire le
code : les hosts.genere.yml de lab et de Technolibre ne bougeaient pas.
CORRECTIF, cinq sites : env.pop("SETOPS_INVENTAIRE") partout ou l'on redirige
SETOPS_INSTANCE — P03 et P15, plus les trois applicateurs (opnsense, proxmox_fw, sdn) qui
pointent vers l'HEBERGEUR. Ces trois sont sans effet tant qu'hebergeur et tenant actif
coincident, c'est-a-dire jusqu'au second site. Le geste existait deja (modeles.py:96).
GARDE, pour que la classe cesse d'etre silencieuse : inventory_rules.inventaire_force()
REFUSE une cible hors de l'instance visee, en nommant les deux valeurs. Eprouvee dans les
deux sens (contradiction -> code 1 ; cible legitime dans l'instance -> passe). Branchee
sur les quatre resolutions de _inventaire (instancier, serveurs, applications,
config_proxmox). Le GUI garde la sienne : il ne redirige jamais SETOPS_INSTANCE pour un
fils et resout par symlink a chaque requete.
make prouver : 37 OK, 0 echec, 0 saute — et les hosts.genere.yml des TROIS instances
portent l'horodatage du passage. make test 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 14:35:01 -04:00
|
|
|
return forced.parent
|
2026-06-30 16:08:25 -04:00
|
|
|
for nom in noms:
|
|
|
|
|
d = instance / "inventories" / nom
|
|
|
|
|
if (d / "hosts.yml").exists():
|
|
|
|
|
return d
|
|
|
|
|
return instance / "inventories" / noms[0]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
_INV_DIR = _inventaire_dir(INSTANCE, "lab", "principal", "production")
|
|
|
|
|
FICHIER_PROXMOX = _INV_DIR / "group_vars/proxmox.yml"
|
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
|
|
|
# Le CLUSTER appartient a l'HEBERGEUR : son fichier vit dans le depot que le symlink
|
|
|
|
|
# `underlay.yml` designe deja (D-14/D-17). Recopiees chez chaque tenant, ces cles
|
|
|
|
|
# avaient deja diverge. Repli sur le fichier du tenant si aucun underlay n'est monte
|
|
|
|
|
# (depot autonome) — `make config` reste utilisable tel quel.
|
|
|
|
|
_UNDERLAY = Path(os.environ.get("SETOPS_UNDERLAY") or (RACINE / "underlay.yml"))
|
|
|
|
|
FICHIER_PROXMOX_HEBERGEUR = (_UNDERLAY.resolve().parent / "proxmox-hebergeur.yml"
|
|
|
|
|
if _UNDERLAY.exists() else FICHIER_PROXMOX)
|
|
|
|
|
# Cles ecrites chez l'HEBERGEUR ; tout le reste (modele, defauts de placement) au tenant.
|
|
|
|
|
CLES_HEBERGEUR = {"proxmox_api_host", "proxmox_api_user", "proxmox_api_port",
|
|
|
|
|
"proxmox_validate_certs"}
|
2026-06-30 16:08:25 -04:00
|
|
|
# Voûte UNIQUE de l'instance : tous les secrets (token Proxmox + vault_*) au même
|
|
|
|
|
# endroit, un seul mot de passe. Gabarit : exemples/vault.exemple.yml.
|
|
|
|
|
FICHIER_VAULT = _INV_DIR / "group_vars/all/vault.yml"
|
2026-06-30 15:42:12 -04:00
|
|
|
GABARIT_VAULT = RACINE / "exemples/vault.exemple.yml"
|
2026-06-24 20:17:46 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
VALEURS_DEFAUT = {
|
|
|
|
|
"proxmox_api_host": "",
|
|
|
|
|
"proxmox_api_user": "",
|
|
|
|
|
"proxmox_api_port": "",
|
|
|
|
|
"proxmox_validate_certs": False,
|
|
|
|
|
"proxmox_clone_noeud": "",
|
|
|
|
|
"proxmox_clone_vmid_modele": 9000,
|
gabarit : modeleChezlepro -> modeleSetOPS, et une porte pour l'hebergeur
Le gabarit portait le nom du mauvais proprietaire. Les TROIS instances
(Chezlepro, Technolibre, lab) le declaraient et pointaient deja toutes sur le
MEME VMID 99998 — alors que le commentaire affirmait « chaque tenant a SON
golden template ». Faux depuis longtemps, et invisible en lisant un seul fichier.
Renomme sur le cluster et dans les trois instances. Le gabarit est un artefact du
MOTEUR, pas d'un tenant ; le nom d'un tenant sur le gabarit d'un autre etait un
piege qui n'attendait qu'un troisieme hebergeur. model_creer.py et
config_proxmox.py proposaient encore modele-debian13 : alignes.
Sans risque : le clonage se fait par VMID depuis le 2026-08-10, le nom ne sert
plus qu'a l'affichage.
NOUVELLE PORTE dans l'aiguillage : docs/preparer-un-site-hebergeur.md, pour
quelqu'un qui prete son materiel sans rien connaitre de Set-OPS. Ecrit a partir
du depot — VLAN et MTU d'underlay.yml, bloc par tenant de
inventory_rules.supernet_de(), privileges du jeton de config-proxmox.md,
dimensionnement (~460 Go, ~37 Go de RAM) mesure sur la flotte vivante.
Deux avertissements y figurent parce qu'ils ont deja coute cher ici : un gabarit
personnalise recopie son identite dans chaque clone, et un blocage contourne en
silence se paie en heures.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:36:44 -04:00
|
|
|
"proxmox_clone_source_nom": "modeleSetOPS",
|
2026-06-24 20:17:46 -04:00
|
|
|
"proxmox_clone_stockage": "",
|
|
|
|
|
"proxmox_clone_format": "",
|
|
|
|
|
"proxmox_clone_complet": True,
|
|
|
|
|
"proxmox_clone_timeout": 600,
|
|
|
|
|
"proxmox_clone_disque": "scsi0",
|
|
|
|
|
"proxmox_clone_interface": "net0",
|
|
|
|
|
"proxmox_clone_pont": "vmbr0",
|
|
|
|
|
"proxmox_clone_parefeu_interface": False,
|
|
|
|
|
"proxmox_clone_demarrer": True,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
LIBELLES = {
|
|
|
|
|
"proxmox_api_host": "Hote API Proxmox",
|
|
|
|
|
"proxmox_api_user": "Utilisateur API Proxmox",
|
|
|
|
|
"proxmox_api_port": "Port API Proxmox",
|
|
|
|
|
"proxmox_validate_certs": "Valider les certificats TLS",
|
|
|
|
|
"proxmox_clone_noeud": "Noeud Proxmox par defaut",
|
|
|
|
|
"proxmox_clone_vmid_modele": "VMID du modele Debian 13",
|
|
|
|
|
"proxmox_clone_source_nom": "Nom logique du modele",
|
|
|
|
|
"proxmox_clone_stockage": "Stockage Proxmox par defaut",
|
|
|
|
|
"proxmox_clone_format": "Format disque par defaut",
|
|
|
|
|
"proxmox_clone_complet": "Clone complet",
|
|
|
|
|
"proxmox_clone_timeout": "Timeout operations Proxmox",
|
|
|
|
|
"proxmox_clone_disque": "Disque principal",
|
|
|
|
|
"proxmox_clone_interface": "Interface reseau",
|
|
|
|
|
"proxmox_clone_pont": "Pont Proxmox",
|
|
|
|
|
"proxmox_clone_parefeu_interface": "Pare-feu interface Proxmox",
|
|
|
|
|
"proxmox_clone_demarrer": "Demarrer le clone apres creation",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def charger_yaml(path: Path) -> dict:
|
|
|
|
|
if not path.exists():
|
|
|
|
|
return {}
|
|
|
|
|
with path.open("r", encoding="utf-8") as fichier:
|
|
|
|
|
data = yaml.safe_load(fichier) or {}
|
|
|
|
|
if not isinstance(data, dict):
|
|
|
|
|
raise ValueError(f"{path} ne contient pas une table YAML.")
|
|
|
|
|
return data
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def ecrire_yaml(path: Path, data: dict) -> None:
|
|
|
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
with path.open("w", encoding="utf-8") as fichier:
|
|
|
|
|
fichier.write("---\n")
|
|
|
|
|
fichier.write("# Parametres non sensibles pour les operations Proxmox.\n")
|
2026-06-30 15:42:12 -04:00
|
|
|
fichier.write("# Les secrets vont dans la voûte unique group_vars/all/vault.yml.\n\n")
|
2026-06-24 20:17:46 -04:00
|
|
|
yaml.safe_dump(data, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def demander(identifiant: str, courant: object) -> object:
|
|
|
|
|
libelle = LIBELLES[identifiant]
|
|
|
|
|
if isinstance(courant, bool):
|
|
|
|
|
defaut = "oui" if courant else "non"
|
|
|
|
|
reponse = input(f"{libelle} [{defaut}] : ").strip().lower()
|
|
|
|
|
if not reponse:
|
|
|
|
|
return courant
|
|
|
|
|
return reponse in {"o", "oui", "y", "yes", "true", "1"}
|
|
|
|
|
|
|
|
|
|
reponse = input(f"{libelle} [{courant}] : ").strip()
|
|
|
|
|
if not reponse:
|
|
|
|
|
return courant
|
|
|
|
|
|
|
|
|
|
if isinstance(courant, int):
|
|
|
|
|
return int(reponse)
|
|
|
|
|
return reponse
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def configurer_proxmox() -> None:
|
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
|
|
|
# Deux fichiers, deux proprietaires : on fusionne pour AFFICHER l'etat courant,
|
|
|
|
|
# on separe pour ECRIRE. Sans quoi `make config` reecrirait chez le tenant les
|
|
|
|
|
# cles du cluster, et la recopie recommencerait a la premiere execution.
|
|
|
|
|
courant = (VALEURS_DEFAUT | charger_yaml(FICHIER_PROXMOX)
|
|
|
|
|
| charger_yaml(FICHIER_PROXMOX_HEBERGEUR))
|
2026-06-24 20:17:46 -04:00
|
|
|
nouveau = {}
|
|
|
|
|
|
|
|
|
|
print("Configuration Proxmox non sensible")
|
|
|
|
|
print("Entrer pour conserver la valeur entre crochets.\n")
|
|
|
|
|
print("Les valeurs Cloud-Init deja portees par le modele ne sont pas redemandees.\n")
|
|
|
|
|
|
|
|
|
|
for identifiant in VALEURS_DEFAUT:
|
|
|
|
|
nouveau[identifiant] = demander(identifiant, courant.get(identifiant, VALEURS_DEFAUT[identifiant]))
|
|
|
|
|
|
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
|
|
|
separe = FICHIER_PROXMOX_HEBERGEUR != FICHIER_PROXMOX
|
|
|
|
|
tenant = {k: v for k, v in nouveau.items() if not (separe and k in CLES_HEBERGEUR)}
|
|
|
|
|
ecrire_yaml(FICHIER_PROXMOX, tenant)
|
2026-06-24 20:17:46 -04:00
|
|
|
print(f"\nEcrit: {os.path.relpath(FICHIER_PROXMOX, RACINE)}")
|
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
|
|
|
if separe:
|
|
|
|
|
# Fusion : preserve les catalogues (noeuds/stockages/ponts) que ce script ne
|
|
|
|
|
# demande pas mais que le panneau ecrit dans le meme fichier.
|
|
|
|
|
heb = charger_yaml(FICHIER_PROXMOX_HEBERGEUR)
|
|
|
|
|
heb.update({k: nouveau[k] for k in CLES_HEBERGEUR if k in nouveau})
|
|
|
|
|
ecrire_yaml(FICHIER_PROXMOX_HEBERGEUR, heb)
|
|
|
|
|
print(f"Ecrit: {FICHIER_PROXMOX_HEBERGEUR} (HEBERGEUR — vaut pour tous ses tenants)")
|
2026-06-24 20:17:46 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def demander_oui_non(question: str, defaut: bool = False) -> bool:
|
|
|
|
|
suffixe = "O/n" if defaut else "o/N"
|
|
|
|
|
reponse = input(f"{question} [{suffixe}] : ").strip().lower()
|
|
|
|
|
if not reponse:
|
|
|
|
|
return defaut
|
|
|
|
|
return reponse in {"o", "oui", "y", "yes", "true", "1"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def configurer_vault() -> None:
|
2026-06-30 15:42:12 -04:00
|
|
|
if not demander_oui_non("Configurer la voûte de secrets maintenant"):
|
2026-06-24 20:17:46 -04:00
|
|
|
return
|
|
|
|
|
|
|
|
|
|
if FICHIER_VAULT.exists():
|
2026-06-30 15:42:12 -04:00
|
|
|
print(f"Ouverture de la voûte existante: {os.path.relpath(FICHIER_VAULT, RACINE)}")
|
2026-06-24 20:17:46 -04:00
|
|
|
subprocess.run(["ansible-vault", "edit", str(FICHIER_VAULT)], check=True)
|
|
|
|
|
return
|
|
|
|
|
|
2026-06-30 15:42:12 -04:00
|
|
|
# Voûte absente : on la sème à partir du gabarit (toutes les clés vides),
|
|
|
|
|
# on y place le token Proxmox saisi, puis on chiffre. Les autres secrets se
|
|
|
|
|
# renseignent ensuite via `ansible-vault edit`.
|
2026-06-24 20:17:46 -04:00
|
|
|
token_id = input("Token ID Proxmox [set-ops] : ").strip() or "set-ops"
|
|
|
|
|
token_secret = getpass("Token secret Proxmox : ").strip()
|
|
|
|
|
if not token_secret:
|
2026-06-30 15:42:12 -04:00
|
|
|
print("Secret vide: voûte non créée.")
|
2026-06-24 20:17:46 -04:00
|
|
|
return
|
|
|
|
|
|
2026-06-30 15:42:12 -04:00
|
|
|
contenu = charger_yaml(GABARIT_VAULT)
|
|
|
|
|
contenu["proxmox_api_token_id"] = token_id
|
|
|
|
|
contenu["proxmox_api_token_secret"] = token_secret
|
|
|
|
|
|
2026-06-24 20:17:46 -04:00
|
|
|
FICHIER_VAULT.parent.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
try:
|
|
|
|
|
fd = os.open(FICHIER_VAULT, os.O_WRONLY | os.O_CREAT | os.O_EXCL, 0o600)
|
|
|
|
|
with os.fdopen(fd, "w", encoding="utf-8") as fichier:
|
2026-06-30 15:42:12 -04:00
|
|
|
yaml.safe_dump(contenu, fichier, default_flow_style=False, sort_keys=False, allow_unicode=True)
|
2026-06-24 20:17:46 -04:00
|
|
|
subprocess.run(["ansible-vault", "encrypt", str(FICHIER_VAULT)], check=True)
|
2026-06-30 15:42:12 -04:00
|
|
|
print(f"Voûte créée: {os.path.relpath(FICHIER_VAULT, RACINE)}")
|
|
|
|
|
print("Renseigne les autres secrets avec : ansible-vault edit "
|
|
|
|
|
f"{os.path.relpath(FICHIER_VAULT, RACINE)}")
|
2026-06-24 20:17:46 -04:00
|
|
|
except Exception:
|
|
|
|
|
if FICHIER_VAULT.exists():
|
|
|
|
|
FICHIER_VAULT.unlink()
|
|
|
|
|
raise
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def main() -> int:
|
|
|
|
|
try:
|
|
|
|
|
configurer_proxmox()
|
|
|
|
|
configurer_vault()
|
|
|
|
|
except KeyboardInterrupt:
|
|
|
|
|
print("\nConfiguration interrompue.", file=sys.stderr)
|
|
|
|
|
return 130
|
|
|
|
|
except Exception as exc:
|
|
|
|
|
print(f"erreur: {exc}", file=sys.stderr)
|
|
|
|
|
return 2
|
|
|
|
|
return 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
raise SystemExit(main())
|