diff --git a/roles/serveur_ops_site/defaults/main.yml b/roles/serveur_ops_site/defaults/main.yml index 4e522c7..4fac4a9 100644 --- a/roles/serveur_ops_site/defaults/main.yml +++ b/roles/serveur_ops_site/defaults/main.yml @@ -77,3 +77,14 @@ serveur_ops_site_conformite_horaire: "hourly" # Au-dela, le constat est PERIME et la sonde passe au rouge : un minuteur mort ne doit pas # laisser la derniere bonne nouvelle au vert. serveur_ops_site_conformite_age_max_h: 3 + +# LE FILET DU GENOME (2026-09-28) — voir `templates/sonde-genome-a-jour.sh.j2`. +# Les depots qu'eregion laisse lire SANS IDENTIFIANT, et leur adresse. Les depots prives n'y +# sont pas : donner au site un jeton sur une forge heritee l'y lierait. La sonde les NOMME +# comme non compares. +serveur_ops_site_genome_repere: + set-ops-public: "https://forge.alliance-boreale.ca/Alliance-Boreale/Set-OPS-Public.git" +# `make publier` pousse eregion une minute avant la forge du site : un ecart plus jeune que +# ceci n'est pas un retard. +serveur_ops_site_genome_tolerance_min: 60 +serveur_ops_site_genome_etat: "/var/lib/setops/genome-ecarts.json" diff --git a/roles/serveur_ops_site/meta/supervision.yml b/roles/serveur_ops_site/meta/supervision.yml index c44aa0e..b8188ff 100644 --- a/roles/serveur_ops_site/meta/supervision.yml +++ b/roles/serveur_ops_site/meta/supervision.yml @@ -40,3 +40,9 @@ sondes: Le pare-feu Proxmox, le SDN, la frontiere et les acces WireGuard disent-ils encore ce que le plan dit ? Le 2026-09-28, aucune VM des locataires n'avait son pare-feu actif et rien ne le signalait ; les plans existaient, personne ne les jouait sans raison. + - nom: genome-a-jour + ttl: 5400 + raison: >- + La forge du site porte-t-elle ce que le poste a publie ? Elle fait autorite, mais un + `git push` seul, ou un runner injoignable au moment de publier, la laisse en retard + sans rien dire — le 2026-08-26, quatre commits dont un correctif du pare-feu. diff --git a/roles/serveur_ops_site/tasks/main.yml b/roles/serveur_ops_site/tasks/main.yml index 72d5d42..61a4133 100644 --- a/roles/serveur_ops_site/tasks/main.yml +++ b/roles/serveur_ops_site/tasks/main.yml @@ -196,3 +196,12 @@ owner: root group: root mode: "0750" + +# LE FILET DU GENOME (2026-09-28) : la forge du site porte-t-elle ce qu'eregion porte ? +- name: Deposer la sonde « genome-a-jour » + ansible.builtin.template: + src: sonde-genome-a-jour.sh.j2 + dest: /usr/local/lib/setops/sondes/genome-a-jour.sh + owner: root + group: root + mode: "0750" diff --git a/roles/serveur_ops_site/templates/sonde-genome-a-jour.sh.j2 b/roles/serveur_ops_site/templates/sonde-genome-a-jour.sh.j2 new file mode 100644 index 0000000..34e3717 --- /dev/null +++ b/roles/serveur_ops_site/templates/sonde-genome-a-jour.sh.j2 @@ -0,0 +1,79 @@ +#!/bin/bash +# GENERE par Set-OPS (role serveur_ops_site). Ne pas editer a la main. +# +# SONDE « genome-a-jour » — la forge du site porte-t-elle ce que le poste a publie ? +# +# Contrat : docs/supervision-conception.md — une ligne, 0/1/3. +# +# POURQUOI UN FILET (2026-09-28). La forge du site fait autorite (D-81) ; les commits naissent +# sur le poste. `make publier` pousse sur eregion PUIS sur la forge du site — mais un +# `git push` fait a la main, ou un runner injoignable au moment de publier, la laisse en +# retard sans rien dire. Le 2026-08-26 : quatre commits, dont un correctif du pare-feu. +# +# LE REPERE EST EREGION, OU CHAQUE COMMIT ARRIVE EN PREMIER — comme repere seulement : eregion +# reste hors du chemin du genome. On ne compare que les depots qu'eregion laisse LIRE SANS +# IDENTIFIANT (`serveur_ops_site_genome_repere`) : donner au site un jeton pour les depots +# prives le lierait a une forge heritee. Les autres sont NOMMES comme non compares. +# +# UN ECART NEUF N'EST PAS UN RETARD : `make publier` pousse eregion une minute avant la forge +# du site. On retient depuis quand chaque ecart dure, et on ne parle qu'au-dela de +# `serveur_ops_site_genome_tolerance_min`. +set -uo pipefail + +python3 - <<'EOF' +import json, subprocess, time, os +FORGE = "https://forge.{{ domaine_interne }}/{{ serveur_ops_forge_organisation | default('genome') }}" +DEPOTS = {{ serveur_ops_depots | default([]) | to_json }} +REPERE = {{ serveur_ops_site_genome_repere | to_json }} +TOLERANCE = {{ serveur_ops_site_genome_tolerance_min }} * 60 +ETAT = "{{ serveur_ops_site_genome_etat }}" + +def tete(url, branche): + r = subprocess.run(["git", "ls-remote", url, f"refs/heads/{branche}"], capture_output=True, + text=True, timeout=8, env=dict(os.environ, GIT_TERMINAL_PROMPT="0")) + return r.stdout.split()[0] if r.returncode == 0 and r.stdout.strip() else None + +try: + vus = json.load(open(ETAT)) +except (OSError, ValueError): + vus = {} +maintenant = time.time() +en_retard, illisibles, a_jour, non_compares, neufs = [], [], [], [], [] +for d in DEPOTS: + nom, branche = d["depot"], d.get("branche", "main") + if nom not in REPERE: + non_compares.append(nom) + continue + try: + site, ref = tete(f"{FORGE}/{nom}.git", branche), tete(REPERE[nom], branche) + except subprocess.TimeoutExpired: + site = ref = None + if not site or not ref: + illisibles.append(nom + (" (forge du site)" if not site else " (repere)")) + continue + if site == ref: + a_jour.append(nom) + vus.pop(nom, None) + continue + depuis = vus.setdefault(nom, {"depuis": maintenant, "site": site, "repere": ref})["depuis"] + minutes = int((maintenant - depuis) / 60) + (en_retard if maintenant - depuis > TOLERANCE else neufs).append( + f"{nom} (forge {site[:7]}, repere {ref[:7]}, depuis {minutes} min)") + +with open(ETAT + ".tmp", "w") as f: + json.dump(vus, f) +os.replace(ETAT + ".tmp", ETAT) + +nc = f" Non compares (prives sur le repere) : {', '.join(non_compares)}." if non_compares else "" +perf = f"| a_jour={len(a_jour)} en_retard={len(en_retard)} non_compares={len(non_compares)}" +if en_retard: + print(f"FORGE DU SITE EN RETARD : {'; '.join(en_retard)}. `make publier`.{nc} {perf}") + raise SystemExit(1) +if illisibles and not a_jour: + print(f"Rien de comparable : {', '.join(illisibles)} illisible(s).{nc} {perf}") + raise SystemExit(3) +suite = f" Ecart recent, dans la tolerance : {'; '.join(neufs)}." if neufs else "" +ill = f" Illisible(s) : {', '.join(illisibles)}." if illisibles else "" +print(f"Forge du site a jour : {', '.join(a_jour) or 'aucun depot compare'}.{suite}{ill}{nc} {perf}") +raise SystemExit(0) +EOF