diff --git a/CHANGELOG.md b/CHANGELOG.md index b6998e5..2348172 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,84 @@ # CHANGELOG — Set-OPS +## 2026-08-23 — Les miroirs du génome, et un nom qui ne résout pas pareil selon d'où on le demande + +La copie du génome sur patient 0 était **figée au jour du poussage**. Elle ne l'est plus : +les deux dépôts publics sont désormais des **miroirs** Forgejo, resynchronisés toutes les +huit heures depuis la forge amont. L'étiquette signée `v2026.08.21` traverse le miroir +intacte — vérifié `SSH SIGNATURE` présente sur le dépôt reconstruit. + +Les trois dépôts privés attendent un jeton amont en **lecture seule**. Le premier jeton +fourni pouvait ÉCRIRE : un `PATCH` sur le dépôt parent a été accepté (HTTP 200), alors que +`administration`, `organisation` et `user` rendaient bien 403 — l'instrument était donc +fiable, et le verdict aussi. Un enfant qui peut réécrire son parent inverse le sens de la +filiation ; le jeton est refusé tant qu'il n'est pas ramené à `read:repository`. + +### Le piège du jour : un nom, deux réponses + +`forge.alliance-boreale.ca` ne résout pas pareil selon l'endroit d'où on le demande : + +``` +depuis le poste d'administration -> 192.168.14.66 (LAN de l'hébergeur) +depuis l'overlay de patient 0 -> 69.70.26.51 (adresse publique) +``` + +Et depuis patient 0, **c'est l'adresse publique qui marche** : le tenant a le droit de +sortir sur l'internet et pas d'entrer dans le LAN `192.168.x` de son hôte — le +default-deny entre les deux mondes, qui fait exactement son travail. + +La mesure, prise depuis `forge-01` : + +``` +ping 100 octets -> 192.168.14.66 BLOQUÉ (même 100 octets : pas un problème de MTU) +ping / https -> internet passe, toutes tailles +curl https://69.70.26.51/api/v1/version -> {"version":"8.0.3"} la vraie forge amont +la forge locale de patient 0 -> {"version":"16.0.2"} bien distincte +8 essais sur 8 -> HTTP 200 en ~6,7 ms (retour en épingle local, stable) +``` + +Et une fois de plus, **la poignée TCP a menti** : `192.168.14.66:443` répondait « ouvert » +alors qu'aucune donnée ne passait. Seule la livraison compte. + +### `hosts_statiques_externes` — nommer ce qui est hors de l'écosystème + +Le plancher `/etc/hosts` ne savait nommer que ce que l'écosystème contient. Or un +écosystème doit atteindre des noms du dehors, à commencer par la forge dont il descend. +Poser l'entrée à la main ne tiendrait pas : le fichier est **regénéré intégralement** à +chaque passage du rôle. La déclaration vit donc au plan : + +```yaml +hosts_statiques_externes: + - ip: "69.70.26.51" + noms: ["forge.alliance-boreale.ca"] + pourquoi: "forge amont du génome — l'adresse interne est bloquée par la frontière" +``` + +On épingle l'adresse dont on a **prouvé** qu'elle livre. Si un enregistrement à horizon +partagé rendait un jour l'adresse interne, le miroir s'arrêterait sans bruit — et une +copie du génome qui vieillit en silence est précisément le défaut que ce dépôt combat. + +### Ce que la conversion a coûté, et pourquoi elle est gardée + +Forgejo ne convertit pas un dépôt ordinaire en miroir : il faut le détruire et le +recréer. Trois gardes encadrent donc l'opération, et deux ont mordu pour de bon : + +- **l'amont contient-il déjà le local ?** La première version exigeait l'égalité et a + refusé la conversion pour un simple commit de retard. Le bon critère est l'inclusion — + `merge-base --is-ancestor`, qui distingue « en retard » (sans risque) de « en avance » + (destruction de travail). +- **un répertoire résiduel est-il vraiment vide ?** Une migration avortée avait laissé + une coquille de `set-ops-public.git` — 0 référence — qui bloquait la suivante avec + « Files already exist ». On ne la retire qu'après avoir compté les références. + +Leçon d'outillage, aussi : `no_log: true` avait masqué l'erreur `409` **et** l'avait +rendue indéchiffrable. La tâche qui porte le mot de passe reste muette, mais un `debug` +séparé rend maintenant le statut et le message. + +### À suivre + +Les hôtes de patient 0 résolvent par Quad9 et **n'interrogent pas leur propre serveur +`infra-dns-01`**. L'écosystème fait tourner un résolveur que personne n'utilise. + ## 2026-08-23 — Patient 0 porte le génome : la boucle est fermée Les cinq dépôts qui fabriquent la lignée vivent désormais sur la forge de patient 0 — diff --git a/roles/hosts_statiques/defaults/main.yml b/roles/hosts_statiques/defaults/main.yml index 073799a..57901d2 100644 --- a/roles/hosts_statiques/defaults/main.yml +++ b/roles/hosts_statiques/defaults/main.yml @@ -9,3 +9,25 @@ hosts_statiques_domaine: "{{ domaine_interne | default('') }}" # du DNS. Best-effort : ignoré si le plan n'est pas disponible (ex. préparation du template). hosts_statiques_publier_expositions: true hosts_statiques_expositions: [] + +# --- Hôtes EXTERNES à l'écosystème ------------------------------------------- +# +# Le plancher ne savait nommer que ce que l'écosystème contient. Or un écosystème a +# besoin d'atteindre des noms du dehors — à commencer par la forge dont il descend. +# +# LE PIÈGE, MESURÉ LE 2026-08-23. `forge.alliance-boreale.ca` résout vers 192.168.14.66 +# depuis le poste d'administration, et vers 69.70.26.51 — l'adresse PUBLIQUE — depuis +# l'overlay de patient 0, qui ne sait pas l'atteindre de l'intérieur (pas de retour en +# épingle). Le clone initial du miroir avait pourtant réussi : la résolution n'est pas +# stable, elle dépend du résolveur interrogé. Un miroir qui se synchronise toutes les +# huit heures serait tombé dessus tôt ou tard, et le silence aurait duré. +# +# Poser l'entrée à la main dans /etc/hosts ne tiendrait pas : ce fichier est REGÉNÉRÉ +# intégralement à chaque passage du rôle. La déclaration doit donc vivre au plan. +# +# Format : +# hosts_statiques_externes: +# - ip: "192.168.14.66" +# noms: ["forge.alliance-boreale.ca"] +# pourquoi: "forge amont — l'adresse publique est injoignable de l'intérieur" +hosts_statiques_externes: [] diff --git a/roles/hosts_statiques/templates/hosts.j2 b/roles/hosts_statiques/templates/hosts.j2 index 37e884d..fc307fa 100644 --- a/roles/hosts_statiques/templates/hosts.j2 +++ b/roles/hosts_statiques/templates/hosts.j2 @@ -26,3 +26,12 @@ ff02::2 ip6-allrouters {% endif %} {% endfor %} {% endif %} +{% if hosts_statiques_externes %} + +# --- Hôtes EXTERNES à l'écosystème (déclarés au plan) --- +# Un nom qui existe hors de l'écosystème peut résoudre vers une adresse PUBLIQUE que +# l'écosystème ne sait pas atteindre depuis l'intérieur. Ces entrées le corrigent. +{% for ext in hosts_statiques_externes %} +{{ "%-15s" | format(ext.ip) }} {{ ext.noms | join(' ') }}{{ (' # ' ~ ext.pourquoi) if ext.pourquoi is defined else '' }} +{% endfor %} +{% endif %}