Path.relative_to(RACINE) levait quand l'instance est externe (SETOPS_INSTANCE = ../OPS-laPreuve par ex.). Remplace par os.path.relpath dans instancier.py / inventory_gui.py / config_proxmox.py. Revele par une instance d'essai agnostique (lapreuve.local, 10.9.0.0/16) : le moteur genere un inventaire distinct, zero fuite Chezlepro. Cas normal (OPS-Chezlepro via symlink) inchange : diff vide, lint 0. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
171 lines
5.7 KiB
Python
171 lines
5.7 KiB
Python
#!/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
|
|
|
|
|
|
RACINE = Path(__file__).resolve().parents[1]
|
|
INSTANCE = Path(os.environ.get("SETOPS_INSTANCE") or (RACINE / "instance"))
|
|
FICHIER_PROXMOX = INSTANCE / "inventories/lab/group_vars/proxmox.yml"
|
|
FICHIER_VAULT = INSTANCE / "inventories/lab/group_vars/proxmox.vault.yml"
|
|
|
|
|
|
VALEURS_DEFAUT = {
|
|
"proxmox_api_host": "",
|
|
"proxmox_api_user": "",
|
|
"proxmox_api_port": "",
|
|
"proxmox_validate_certs": False,
|
|
"proxmox_clone_noeud": "",
|
|
"proxmox_clone_vmid_modele": 9000,
|
|
"proxmox_clone_source_nom": "modele-debian13",
|
|
"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")
|
|
fichier.write("# Les secrets API doivent aller dans proxmox.vault.yml.\n\n")
|
|
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:
|
|
courant = VALEURS_DEFAUT | charger_yaml(FICHIER_PROXMOX)
|
|
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]))
|
|
|
|
ecrire_yaml(FICHIER_PROXMOX, nouveau)
|
|
print(f"\nEcrit: {os.path.relpath(FICHIER_PROXMOX, RACINE)}")
|
|
|
|
|
|
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:
|
|
if not demander_oui_non("Configurer les secrets API Proxmox maintenant"):
|
|
return
|
|
|
|
if FICHIER_VAULT.exists():
|
|
print(f"Ouverture du Vault existant: {os.path.relpath(FICHIER_VAULT, RACINE)}")
|
|
subprocess.run(["ansible-vault", "edit", str(FICHIER_VAULT)], check=True)
|
|
return
|
|
|
|
token_id = input("Token ID Proxmox [set-ops] : ").strip() or "set-ops"
|
|
token_secret = getpass("Token secret Proxmox : ").strip()
|
|
if not token_secret:
|
|
print("Secret vide: Vault non cree.")
|
|
return
|
|
|
|
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:
|
|
yaml.safe_dump(
|
|
{
|
|
"proxmox_api_token_id": token_id,
|
|
"proxmox_api_token_secret": token_secret,
|
|
},
|
|
fichier,
|
|
default_flow_style=False,
|
|
sort_keys=False,
|
|
)
|
|
subprocess.run(["ansible-vault", "encrypt", str(FICHIER_VAULT)], check=True)
|
|
print(f"Vault cree: {os.path.relpath(FICHIER_VAULT, RACINE)}")
|
|
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())
|