diff --git a/Makefile b/Makefile index b9e901c..c2f9ac4 100644 --- a/Makefile +++ b/Makefile @@ -821,17 +821,33 @@ schema: ## Regenere docs/audit/schema-plan.json depuis les registres et les vali .PHONY: wiki-publier wiki-publier: ## Publie le wiki (wiki/) vers la forge + @# CHAQUE RUNNER PUBLIE POUR SA FORGE (2026-09-14). Sans `WIKI_REMOTE`, l'adresse se + @# DERIVE de l'ecosysteme monte : le nom que son plan expose pour `serveur_forgejo`. + @# + @# DEUX NOTIONS A NE PAS CONFONDRE. La forge AMONT est celle ou l'on LIT le genome ; MA + @# forge est celle ou l'on SERT les siens. Le runner d'un locataire lit chez son + @# hebergeur et sert chez lui — un employe de TechnoLibre lit la forge de TechnoLibre. + @# + @# `WIKI_REMOTE` L'EMPORTE TOUJOURS, et c'est necessaire : le poste du mainteneur n'est + @# le runner d'aucun ecosysteme, et publie vers le domicile PUBLIC du projet, qui n'est + @# la forge d'aucun plan. @set -e; \ - if [[ -z "$(WIKI_REMOTE)" ]]; then \ - printf '%s\n' 'Refus: URL du wiki Forgejo requise.'; \ - printf '%s\n' 'Ex: make wiki-publier WIKI_REMOTE=https://forge.//.wiki.git'; \ + remote="$$remote"; \ + if [[ -z "$$remote" ]]; then \ + remote="$$(python3 scripts/ma_forge.py 2>/dev/null || true)"; \ + fi; \ + if [[ -z "$$remote" ]]; then \ + printf '%s\n' 'Refus: aucune forge — ni WIKI_REMOTE, ni derivable du plan monte.'; \ + python3 scripts/ma_forge.py >/dev/null || true; \ + printf '%s\n' 'Ex: make wiki-publier WIKI_REMOTE=ssh://git@forge.//.wiki.git'; \ exit 2; \ fi; \ + printf 'Forge visee : %s\n' "$$remote"; \ src="$(CURDIR)/wiki"; \ tmp="$$(mktemp -d)"; \ trap 'rm -rf "$$tmp"' EXIT; \ - printf '%s\n' "Clonage du wiki: $(WIKI_REMOTE)"; \ - if ! git clone --quiet --depth 1 "$(WIKI_REMOTE)" "$$tmp/wiki"; then \ + printf '%s\n' "Clonage du wiki: $$remote"; \ + if ! git clone --quiet --depth 1 "$$remote" "$$tmp/wiki"; then \ printf '%s\n' 'Echec du clone (URL ou acces ?). Le wiki doit exister (creer une 1re page dans Forgejo).'; \ exit 1; \ fi; \ @@ -861,7 +877,7 @@ wiki-publier: ## Publie le wiki (wiki/) vers la forge 'le wiki deja en service vit sur `main`. Deux issues :' \ ' - demander a Forgejo : GET /api/v1/repos// -> `wiki_branch`,' \ ' puis make wiki-publier WIKI_REMOTE=... WIKI_BRANCHE=' \ - " - ou creer une premiere page dans l interface Forgejo de $(WIKI_REMOTE)"; \ + " - ou creer une premiere page dans l interface Forgejo de $$remote"; \ exit 2; \ fi; \ fi; \ @@ -895,7 +911,7 @@ wiki-publier: ## Publie le wiki (wiki/) vers la forge : ' APRES le push : un temoin ne doit jamais dire qu on a publie si on a echoue.'; \ { printf '%s\n' '---' \ '# Ecrit par `make wiki-publier`, lu par la preuve P60. Ne pas editer a la main.' \ - "remote: $(WIKI_REMOTE)" \ + "remote: $$remote" \ "source: $$sha" \ "date: $$(date +%F)"; } > "$(CURDIR)/docs/audit/wiki-publie.yml"; \ printf '%s\n' 'Temoin depose : docs/audit/wiki-publie.yml (a committer).' diff --git a/docs/audit/preuve-2026-09-14.md b/docs/audit/preuve-2026-09-14.md index 5cd4472..e5b38fa 100644 --- a/docs/audit/preuve-2026-09-14.md +++ b/docs/audit/preuve-2026-09-14.md @@ -43,7 +43,7 @@ | P28 | Pools Proxmox : un par tenant, sans collision | AFF-110 | ✅ OK | CONFORME : 5 pool(s) Proxmox, 44 VM placee(s), aucun nom ni VMID en collision. | | P29 | Authentification : chaque role declare sa position | AFF-111 | ✅ OK | 33 role(s) serveur declares (interne-sans-auth 2, ldap-direct 2, sans-auth-humaine 22, socle-identite 2, web-sso 5) ; 2 lacune(s) nommee(s) : serveur_loki, serv | | P30 | SDN EVPN : zones, VNets et sous-reseaux derives | AFF-112 | ✅ OK | CONFORME : SDN EVPN, 3 zone(s), 15 VNet(s), 15 sous-reseau(x), aucune collision. | -| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 66 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. | +| P31 | Documentation : tout ce que le depot FAIT est nomme | — | ✅ OK | 67 scripts expliques et atteignables, 123 cibles make documentees, 68 roles avec README. | | P32 | Intrants exiges par les roles : tous fournis | — | ✅ OK | CONFORME : 38 exigence(s) de role, toutes satisfaites (149 cle(s) declaree(s) par l'instance). | | P33 | Aucune collision de port entre roles co-localises | — | ✅ OK | CONFORME : 35 revendication(s) de port, aucune collision entre roles co-localises (36 groupes). | | P34 | Chaque document declare son lecteur | — | ✅ OK | 46 document(s) declarent leur lecteur (40 genere(s) exempte(s)). | @@ -53,7 +53,7 @@ | P38 | Catalogue des services : la carte dit ce que le moteur fait | — | ✅ OK | Catalogue a jour : 41 role(s) serveur/client tous nommes, 41 groupe(s) cite(s) en table existent tous. | | P39 | Glossaire : tout mot employe est enseigne | — | ✅ OK | Glossaire complet : 81 terme(s) du jargon expliques, 15 lien(s) valides, 96 page(s) de wiki toutes atteignables. | | P40 | Parente : l'ecosysteme sait de quoi il descend | — | ✅ OK | Parente coherente : 4 depot(s), tous retrouves, tous porteurs d'un remote. | -| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 62 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | +| P41 | Resolution d'instance : une seule, partagee | — | ✅ OK | Resolution unique : 63 script(s) passent par `inventory_rules`, 3 exemption(s) nommee(s). | | P42 | L'edge porte les noms qu'il publie | — | ✅ OK | 5 edge(s) emettent un certificat portant les noms publies (instance-ci-1646753/production, OPS-Chezlepro-lab/principal, OPS-Chezlepro/principal, OPS-Technolibre | | P43 | Frontiere : le devis voit les machines du site | — | ✅ OK | Devis de la frontiere : 7 machine(s) du plan retrouvees, 154 regle(s) du site. | | P44 | Integrations : le serveur avant ses clients | — | ✅ OK | 5 integration(s) appliquent leur serveur avant leurs clients. | diff --git a/scripts/ma_forge.py b/scripts/ma_forge.py new file mode 100755 index 0000000..2c29a99 --- /dev/null +++ b/scripts/ma_forge.py @@ -0,0 +1,79 @@ +#!/usr/bin/env python3 +"""L'adresse du wiki de MA forge — celle de l'ecosysteme monte. + +CHAQUE RUNNER PUBLIE POUR SA FORGE (decide le 2026-09-14). + +La premiere idee etait qu'un controleur pousse vers plusieurs forges, avec un temoin +portant une liste. Elle CENTRALISAIT : un seul poste aurait eu besoin d'un acces sur +chaque forge, et le temoin serait devenu un fait global que personne ne detient. + +Le modele retenu suit la ligne du reste du systeme — les voutes, les runners, les +sauvegardes : **chacun sert les siens**. Le runner du site publie sur la forge du site ; +celui d'un locataire sur la forge de ce locataire. Un employe de TechnoLibre lit la forge +de TechnoLibre, pas celle de son hebergeur. + +DEUX NOTIONS A NE PAS CONFONDRE, et c'est ce qui a rendu le sujet trouble : + + la forge AMONT (`serveur_ops_forge_amont`) ou je LIS le genome + MA forge (derivee ici) ou je SERS les miens + +Le runner d'un locataire lit sur la forge du SITE et sert sur la SIENNE. Les deux adresses +different, et le wiki appartient a la seconde. + +CE QUI N'EST PAS DERIVABLE, ET C'EST DIT PLUTOT QUE DEVINE : le poste du mainteneur n'est +le runner d'aucun ecosysteme. Il publie vers le domicile PUBLIC du projet, qui n'est la +forge d'aucun plan. La, `WIKI_REMOTE` reste explicite — et il l'emporte toujours. +""" +from __future__ import annotations + +import sys +from pathlib import Path + +RACINE = Path(__file__).resolve().parents[1] +sys.path.insert(0, str(RACINE / "scripts")) + + +def ma_forge() -> tuple[str | None, str]: + """(url du wiki, explication). `None` quand rien n'est derivable.""" + try: + from inventory_rules import charger_applications, instance_courante + except Exception as e: + return None, f"moteur illisible ({e.__class__.__name__})" + + inst = instance_courante() + if not inst: + return None, "aucune instance montee" + + # `charger_applications` VEUT UN CHEMIN, pas rien. L'appeler sans argument levait un + # `TypeError` que le `except` transformait en « plan illisible » — un message poli pour + # une faute de programmation. Le voila nomme. + try: + apps = (charger_applications(Path(inst) / "plan" / "applications.yml") + or {}).get("applications") or {} + except (OSError, ValueError) as e: + return None, f"plan illisible ({e.__class__.__name__})" + + # LE NOM DU SERVICE, PAS CELUI DE LA MACHINE. Un FQDN expose survit au demenagement du + # service ; `forge-01` non. C'est la meme regle que partout ailleurs dans ce depot. + for nom, app in (apps.items() if isinstance(apps, dict) else []): + if str((app or {}).get("groupe")) != "serveur_forgejo": + continue + exposes = (app or {}).get("expose") or [] + if not exposes: + return None, "`serveur_forgejo` est au plan mais n'expose aucun nom" + fqdn = str(exposes[0]) + depot = RACINE.name.lower() + return (f"ssh://git@{fqdn}/genome/{depot}.wiki.git", + f"forge de l'ecosysteme monte ({Path(inst).resolve().name}), exposee en {fqdn}") + + return None, f"{Path(inst).resolve().name} ne declare aucun `serveur_forgejo`" + + +if __name__ == "__main__": + url, pourquoi = ma_forge() + if url: + print(url) + print(f"note : {pourquoi}", file=sys.stderr) + raise SystemExit(0) + print(f"note : pas de forge derivable — {pourquoi}", file=sys.stderr) + raise SystemExit(1)