site : le runner travaille, et le genome remonte chez lui
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.
DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.
`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.
Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.
LE CERTIFICAT COUVRE LES NOMS DU SERVICE.
`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.
Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.
`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.
La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.
Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.
Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.
Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.
Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 08:52:37 -04:00
|
|
|
---
|
|
|
|
|
# LE RUNNER DU SITE POUSSE LE GENOME SUR LA FORGE DU SITE.
|
|
|
|
|
#
|
|
|
|
|
# POURQUOI CE DETOUR, ET POURQUOI IL EST BON (2026-08-26).
|
|
|
|
|
#
|
|
|
|
|
# La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant ne route
|
|
|
|
|
# pas — et c'est voulu : le poste n'a de patte que sur l'administration, la frontiere
|
|
|
|
|
# police le reste. Il ne peut donc pas pousser lui-meme.
|
|
|
|
|
#
|
|
|
|
|
# On ne perce pas un chemin pour lui. Le RUNNER existe exactement pour ca : il est dans le
|
|
|
|
|
# site, il a le genome, il a une identite. On lui PORTE les commits dans un `git bundle` —
|
|
|
|
|
# un fichier, verifiable, qui ne demande aucun reseau — et c'est LUI qui pousse.
|
|
|
|
|
#
|
|
|
|
|
# Mesure du 2026-08-26 : la forge du site etait restee quatre commits en arriere du poste,
|
|
|
|
|
# sans que rien ne le signale. Le runner clonait donc un moteur perime — dont, ce jour-la,
|
|
|
|
|
# le correctif qui desarme le pare-feu Proxmox. Un ecosysteme qui se reproduit depuis une
|
|
|
|
|
# forge en retard reproduit ses defauts.
|
|
|
|
|
#
|
|
|
|
|
# CE QUI EST POUSSE SE DERIVE, il ne s'ecrit pas : `serveur_ops_depots` (au plan du site)
|
|
|
|
|
# dit ce que le runner porte. On pousse ceux dont une copie locale existe a cote du
|
|
|
|
|
# moteur, et rien d'autre.
|
|
|
|
|
#
|
|
|
|
|
# make genome-pousser tous les depots portes par le runner
|
|
|
|
|
# make genome-pousser DEPOT=<nom> un seul (le nom du depot sur la forge)
|
|
|
|
|
#
|
|
|
|
|
# L'ECRITURE S'AUTHENTIFIE, LA LECTURE NON. `serveur_ops` clone en anonyme, par doctrine.
|
|
|
|
|
# Pousser est un autre acte : le runner s'y presente avec SA cle — celle que `serveur_ops`
|
|
|
|
|
# lui a generee — declaree en cle de depot, en ecriture, sur ces depots-la seulement.
|
|
|
|
|
# `url` reste en HTTPS anonyme ; seul `pushurl` passe par SSH.
|
|
|
|
|
|
|
|
|
|
- name: Le runner du site pousse le genome sur la forge du site
|
|
|
|
|
hosts: serveur_ops_site
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
gather_facts: false
|
|
|
|
|
|
|
|
|
|
vars:
|
|
|
|
|
genome_racine: "{{ serveur_ops_racine | default('/opt/setops') }}"
|
|
|
|
|
genome_depot_demande: "{{ lookup('env', 'SETOPS_DEPOT') | default('', true) }}"
|
|
|
|
|
# Le dossier qui contient le moteur ET ses depots freres, sur le CONTROLEUR.
|
|
|
|
|
genome_freres: "{{ playbook_dir | dirname | dirname | dirname }}"
|
|
|
|
|
# Le dossier du moteur, tel que le plan le nomme — jamais ecrit en dur.
|
|
|
|
|
genome_moteur: "{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}"
|
|
|
|
|
|
|
|
|
|
tasks:
|
|
|
|
|
- name: Exiger de savoir quoi pousser
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- (serveur_ops_depots | default([])) | length > 0
|
|
|
|
|
fail_msg: >-
|
|
|
|
|
Ce poste ne déclare aucun dépôt (`serveur_ops_depots`). Rien à pousser — et
|
|
|
|
|
probablement rien à piloter non plus : voir le plan de cette machine.
|
|
|
|
|
|
|
|
|
|
# Ce que le runner porte, restreint a ce dont une copie existe sur le controleur.
|
|
|
|
|
# Un depot declare mais absent d'ici n'est pas une faute : il vit ailleurs.
|
|
|
|
|
- name: Voir lesquels de ces dépôts existent sur le contrôleur
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: "{{ genome_freres }}/{{ item.dest }}/.git"
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
loop: "{{ serveur_ops_depots }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.dest }}"
|
|
|
|
|
register: genome_locaux
|
|
|
|
|
|
|
|
|
|
- name: Retenir ceux qu'on peut effectivement porter
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
genome_a_pousser: >-
|
|
|
|
|
{{ genome_locaux.results
|
|
|
|
|
| selectattr('stat.exists')
|
|
|
|
|
| map(attribute='item') | list
|
|
|
|
|
| selectattr('depot', 'equalto', genome_depot_demande)
|
|
|
|
|
| list
|
|
|
|
|
if genome_depot_demande | length > 0
|
|
|
|
|
else genome_locaux.results | selectattr('stat.exists')
|
|
|
|
|
| map(attribute='item') | list }}
|
|
|
|
|
|
|
|
|
|
- name: Dire ce qui va être poussé
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg: >-
|
|
|
|
|
{{ genome_a_pousser | map(attribute='depot') | list
|
|
|
|
|
| ternary(genome_a_pousser | map(attribute='depot') | list,
|
|
|
|
|
'aucun dépôt à pousser') }}
|
|
|
|
|
|
|
|
|
|
- name: Refuser un DEPOT= qui ne désigne rien
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that: genome_a_pousser | length > 0
|
|
|
|
|
fail_msg: >-
|
|
|
|
|
Aucun dépôt à pousser. {{ 'Le dépôt `' ~ genome_depot_demande ~ '` n est pas
|
|
|
|
|
porté par ce runner, ou n a pas de copie locale à côté du moteur.'
|
|
|
|
|
if genome_depot_demande | length > 0 else
|
|
|
|
|
'Aucun des dépôts déclarés n a de copie locale à côté du moteur.' }}
|
|
|
|
|
|
|
|
|
|
# LA CLE DU RUNNER DOIT ETRE AUTORISEE SUR CE QU'IL POUSSE — sinon la forge refuse,
|
|
|
|
|
# depot par depot, et l'exploitant doit aller cliquer. La cible s'en charge : la
|
|
|
|
|
# declaration est idempotente (422 = deja la), et sa portee est exactement ce qu'on
|
|
|
|
|
# pousse, jamais plus large.
|
|
|
|
|
- name: Lire la clé publique du runner
|
|
|
|
|
ansible.builtin.slurp:
|
|
|
|
|
src: "{{ genome_racine }}/.ssh/id_{{ serveur_ops_cle_type | default('ed25519') }}.pub"
|
|
|
|
|
register: genome_cle
|
|
|
|
|
|
|
|
|
|
- name: Autoriser cette clé en écriture sur les dépôts poussés
|
|
|
|
|
ansible.builtin.uri:
|
|
|
|
|
url: >-
|
|
|
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
|
|
|
| default('genome') }}/{{ item.depot }}/keys
|
|
|
|
|
method: POST
|
|
|
|
|
url_username: forgejo-admin
|
|
|
|
|
url_password: "{{ vault_forgejo_admin }}"
|
|
|
|
|
force_basic_auth: true
|
|
|
|
|
validate_certs: false
|
|
|
|
|
body_format: json
|
|
|
|
|
body:
|
|
|
|
|
title: "runner du site — {{ inventory_hostname }}"
|
|
|
|
|
key: "{{ genome_cle.content | b64decode | trim }}"
|
|
|
|
|
read_only: false
|
|
|
|
|
status_code: [201, 422]
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
register: genome_cles
|
|
|
|
|
changed_when: false
|
|
|
|
|
no_log: true
|
|
|
|
|
|
|
|
|
|
- name: Un endroit pour fabriquer les colis, sur le contrôleur
|
|
|
|
|
ansible.builtin.tempfile:
|
|
|
|
|
state: directory
|
|
|
|
|
suffix: -genome
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
register: genome_atelier
|
|
|
|
|
|
|
|
|
|
- name: Un endroit pour déposer les colis
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ genome_racine }}/.bundles"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
mode: "0700"
|
|
|
|
|
|
|
|
|
|
# LA LOGIQUE GIT VIT DANS `scripts/genome_colis.py`, comme partout ici : le playbook
|
|
|
|
|
# orchestre, le script fait. Il voyage AVEC le moteur — le runner en a donc une copie
|
|
|
|
|
# dès qu'il l'a cloné, et rien de spécial n'est déposé sur la machine.
|
|
|
|
|
- name: Emballer chaque dépôt sur le contrôleur
|
|
|
|
|
# `argv` ET NON `cmd` : le dossier des dépôts frères peut contenir des espaces —
|
|
|
|
|
# c'est le cas ici (« Espace Chezlepro »). Une chaîne `cmd` est découpée sur les
|
|
|
|
|
# blancs, et python recevait « /home/…/Espace » comme nom de fichier.
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv:
|
|
|
|
|
- python3
|
|
|
|
|
- "{{ genome_freres }}/{{ genome_moteur }}/scripts/genome_colis.py"
|
|
|
|
|
- emballer
|
|
|
|
|
- "{{ genome_freres }}/{{ item.dest }}"
|
|
|
|
|
- "{{ genome_atelier.path }}/{{ item.depot }}.bundle"
|
|
|
|
|
- --branche
|
|
|
|
|
- "{{ item.branche | default('main') }}"
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.dest }}"
|
2026-10-07 19:04:31 -04:00
|
|
|
register: genome_emballe
|
site : le runner travaille, et le genome remonte chez lui
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.
DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.
`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.
Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.
LE CERTIFICAT COUVRE LES NOMS DU SERVICE.
`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.
Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.
`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.
La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.
Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.
Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.
Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.
Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 08:52:37 -04:00
|
|
|
changed_when: true
|
|
|
|
|
|
|
|
|
|
- name: Porter les colis jusqu'au runner
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: "{{ genome_atelier.path }}/{{ item.depot }}.bundle"
|
|
|
|
|
dest: "{{ genome_racine }}/.bundles/{{ item.depot }}.bundle"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
mode: "0600"
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
|
|
|
|
|
# L'OUTIL VOYAGE AVEC LE COLIS, il n'est pas supposé déjà là (2026-08-26).
|
|
|
|
|
#
|
|
|
|
|
# Première version : on lançait `scripts/genome_colis.py` depuis le moteur cloné sur
|
|
|
|
|
# le runner. Circulaire — le runner ne reçoit cette version du script qu'APRÈS la
|
|
|
|
|
# poussée qu'elle sert à faire. Et ça se reproduirait à chaque évolution de l'outil :
|
|
|
|
|
# le runner en retard ne saurait jamais rattraper son retard.
|
|
|
|
|
#
|
|
|
|
|
# C'est le contrôleur qui détient la version qui fait autorité. Il la porte.
|
|
|
|
|
- name: Porter l'outil lui-même jusqu'au runner
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: "{{ genome_freres }}/{{ genome_moteur }}/scripts/genome_colis.py"
|
|
|
|
|
dest: "{{ genome_racine }}/.bundles/genome_colis.py"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur | default('setops') }}"
|
|
|
|
|
mode: "0700"
|
|
|
|
|
|
|
|
|
|
- name: Le runner vérifie, avance en avance rapide, et pousse
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv:
|
|
|
|
|
- python3
|
|
|
|
|
- "{{ genome_racine }}/.bundles/genome_colis.py"
|
|
|
|
|
- recevoir
|
|
|
|
|
- "{{ genome_racine }}/{{ item.dest }}"
|
|
|
|
|
- "{{ genome_racine }}/.bundles/{{ item.depot }}.bundle"
|
|
|
|
|
- "git@forge.{{ domaine_interne }}:{{ serveur_ops_forge_organisation | default('genome') }}/{{ item.depot }}.git"
|
|
|
|
|
- --cle
|
|
|
|
|
- "{{ genome_racine }}/.ssh/id_{{ serveur_ops_cle_type | default('ed25519') }}"
|
|
|
|
|
- --branche
|
|
|
|
|
- "{{ item.branche | default('main') }}"
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
register: genome_recu
|
|
|
|
|
# `changed_when: false` ET C'EST VOULU. Dans une boucle, ni le registre (pas encore
|
|
|
|
|
# defini) ni `stdout` nu ne sont lisibles ici — les deux ont ete essayes, les deux
|
|
|
|
|
# ont leve. Plutot qu'une condition qui ment ou qui casse, on ne pretend rien : ce
|
|
|
|
|
# qui a change est dit par le VERDICT, plus bas, qui compare la forge au runner et
|
|
|
|
|
# nomme la transition. La verite vient de la forge, pas d'un drapeau de tache.
|
|
|
|
|
changed_when: false
|
|
|
|
|
|
|
|
|
|
- name: Demander à la forge ce qu'elle porte maintenant
|
|
|
|
|
# On interroge son API plutôt que `git ls-remote` : c'est la forge elle-même qui
|
|
|
|
|
# répond, et la lecture est anonyme — aucun justificatif n'entre ici.
|
|
|
|
|
ansible.builtin.uri:
|
|
|
|
|
url: >-
|
|
|
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
|
|
|
| default('genome') }}/{{ item.depot }}/branches/{{ item.branche | default('main') }}
|
|
|
|
|
validate_certs: false
|
|
|
|
|
status_code: [200]
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
register: genome_forge
|
genome : la forge du SITE fait autorite (D-81), et `make genome-etat` le verifie
Decision de l'exploitant : la forge du SITE fait autorite pour le genome. Toute
autre copie — y compris celle d'ou le moteur a ete pousse jusqu'ici — est un
MIROIR.
Un ecosysteme se reproduit depuis la forge de son site : c'est de la qu'il clone
son moteur, ses plans, ses modeles. Si l'autorite est ailleurs, cette forge
devient un cache qu'on croit a jour — et le 2026-08-26 elle etait quatre commits
en arriere sans que rien ne le signale, dont le correctif qui desarme le pare-feu
Proxmox.
UNE AUTORITE QU'ON NE VERIFIE PAS EST UNE AUTORITE QU'ON SUPPOSE.
`make genome-etat` confronte, depot par depot, ce que le poste porte a ce que la
forge porte. Il REFUSE en cas d'ecart plutot que de le signaler : un ecart connu
et tolere redevient un ecart oublie, et la commande qui le corrige tient en trois
mots. Il dit aussi quand la copie locale n'est pas propre — des commits pas
encore faits sont une autre forme de retard.
Mesure au passage, et traitee plutot qu'ignoree : le premier contact avec la
forge echoue une fois sur six — poignee TLS expiree, puis cinq reponses de suite.
Ce n'est pas le chemin, qui est prouve ; c'est l'acceptation TLS apres un temps
d'inactivite. Les deux cibles reessaient.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 09:17:24 -04:00
|
|
|
# LE PREMIER CONTACT AVEC LA FORGE RATE PARFOIS (mesure du 2026-08-26 : une fois sur
|
|
|
|
|
# six, poignee TLS expiree, puis cinq reponses de suite). Ce n'est pas le chemin —
|
|
|
|
|
# il est prouve — c'est l'acceptation TLS apres un temps d'inactivite. On reessaie
|
|
|
|
|
# plutot que de rendre un verdict sur un alea.
|
|
|
|
|
retries: 4
|
|
|
|
|
delay: 3
|
|
|
|
|
until: genome_forge is succeeded
|
site : le runner travaille, et le genome remonte chez lui
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.
DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.
`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.
Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.
LE CERTIFICAT COUVRE LES NOMS DU SERVICE.
`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.
Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.
`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.
La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.
Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.
Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.
Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.
Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 08:52:37 -04:00
|
|
|
changed_when: false
|
|
|
|
|
|
|
|
|
|
- name: La forge porte-t-elle exactement ce que le runner porte ?
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- genome_forge.results[idx].json.commit.id
|
|
|
|
|
== (genome_recu.results[idx].stdout | from_json).apres
|
|
|
|
|
fail_msg: >-
|
|
|
|
|
{{ item.depot }} : la forge porte
|
|
|
|
|
{{ genome_forge.results[idx].json.commit.id[:8] | default('rien') }} alors que
|
|
|
|
|
le runner porte
|
|
|
|
|
{{ (genome_recu.results[idx].stdout | from_json).apres[:8] }}. La poussée n'a pas
|
|
|
|
|
abouti — ne pas cloner depuis cette forge tant que ce n'est pas réglé.
|
|
|
|
|
success_msg: >-
|
|
|
|
|
{{ item.depot }} :
|
|
|
|
|
{{ (genome_recu.results[idx].stdout | from_json).avant[:7] }} →
|
|
|
|
|
{{ (genome_recu.results[idx].stdout | from_json).apres[:7] }}, et la forge le porte.
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
index_var: idx
|
|
|
|
|
|
2026-10-07 19:04:31 -04:00
|
|
|
# LES RELEASES AUSSI (2026-10-07). Le colis ne portait que la branche : `v2026.08.21` et
|
|
|
|
|
# `v2026.10.07` manquaient a la forge du site. Il porte desormais les etiquettes ; on
|
|
|
|
|
# demande a la forge si elle a TOUTES celles du poste. 404 = aucune etiquette sur ce
|
|
|
|
|
# depot, ce qui est juste quand le poste n'en a pas non plus.
|
|
|
|
|
- name: Demander à la forge les étiquettes qu'elle porte
|
|
|
|
|
ansible.builtin.uri:
|
|
|
|
|
url: >-
|
|
|
|
|
https://forge.{{ domaine_interne }}/api/v1/repos/{{ serveur_ops_forge_organisation
|
|
|
|
|
| default('genome') }}/{{ item.depot }}/git/refs/tags
|
|
|
|
|
validate_certs: false
|
|
|
|
|
status_code: [200, 404]
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
register: genome_forge_etiquettes
|
|
|
|
|
retries: 4
|
|
|
|
|
delay: 3
|
|
|
|
|
until: genome_forge_etiquettes is succeeded
|
|
|
|
|
changed_when: false
|
|
|
|
|
|
|
|
|
|
- name: La forge porte-t-elle toutes les releases du poste ?
|
|
|
|
|
vars:
|
|
|
|
|
genome_etiquettes_poste: "{{ (genome_emballe.results[idx].stdout | from_json).etiquettes | default([]) }}"
|
|
|
|
|
genome_etiquettes_forge: >-
|
|
|
|
|
{{ (genome_forge_etiquettes.results[idx].json | default([]) if genome_forge_etiquettes.results[idx].status == 200 else [])
|
|
|
|
|
| map(attribute='ref') | map('regex_replace', '^refs/tags/', '') | list }}
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- genome_etiquettes_poste | difference(genome_etiquettes_forge) | length == 0
|
|
|
|
|
fail_msg: >-
|
|
|
|
|
{{ item.depot }} : la forge n'a pas
|
|
|
|
|
{{ genome_etiquettes_poste | difference(genome_etiquettes_forge) | join(', ') }}.
|
|
|
|
|
success_msg: >-
|
|
|
|
|
{{ item.depot }} : {{ genome_etiquettes_poste | length }} etiquette(s) du poste, toutes sur la forge.
|
|
|
|
|
loop: "{{ genome_a_pousser }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }}"
|
|
|
|
|
index_var: idx
|
|
|
|
|
|
genome pousse : celui qui pousse avance son propre clone, et sa console l'ignore
Le correctif de serveur_ops a eu sa preuve en conditions reelles : clone passe de 6367d85
a f9a20b0 chez les deux locataires, tache en changed, consoles reparties a 15h33m33 et
15h38m02 contre 15h16, sondes a 0. En la donnant, il a montre le cas qu'il ne couvre pas.
genome_pousser.yml fait avancer le clone du runner du SITE sans qu'aucun role ne passe :
c'est lui qui pousse, donc il recoit d'abord. Quand serveur_ops passera, le clone sera
deja a jour et la tache s'abstiendra a juste titre — l'hebergeur gardait le defaut corrige
chez ses locataires. Le playbook nomme deja la transition dans son verdict ; il en tire
maintenant la consequence et redemarre la console du site quand c'est le MOTEUR qui a
bouge. Un plan pousse ne coupe pas les pages ouvertes, et l'unite systemd est verifiee
avant de toucher au service.
Valide : --syntax-check contre l'inventaire du site et celui d'un locataire, ansible-lint
sur le playbook (profil production, 0 echec, 0 avertissement), condition de declenchement
eprouvee sur quatre cas dont le mode check.
Limite : le redemarrage de la console du site demande une poussee posterieure a ce commit,
il n'est donc pas encore observe. Son disque porte f9a20b0, sa console sert le code
charge a 15h16.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:39:06 -04:00
|
|
|
# LE RUNNER DU SITE AVANCE SON PROPRE CLONE, ET SA CONSOLE NE LE SAIT PAS (2026-09-20).
|
|
|
|
|
#
|
|
|
|
|
# C'est LUI qui pousse, donc il doit d'abord recevoir : ce playbook fait avancer le
|
|
|
|
|
# moteur du site sans qu'aucun role ne passe. `serveur_ops` redemarre bien la console
|
|
|
|
|
# quand le clone avance, mais il ne verra jamais cet avancement-ci — le clone sera
|
|
|
|
|
# deja a jour quand il passera. Le site garderait donc une console servant l'ancien
|
|
|
|
|
# code, exactement le defaut corrige le meme jour chez les locataires.
|
|
|
|
|
#
|
|
|
|
|
# `python3 scripts/inventory_gui.py` lit son code au demarrage, et seulement la. Ce
|
|
|
|
|
# qui a avance doit etre relu, et personne d'autre que ce playbook ne sait qu'il a
|
|
|
|
|
# avance : le verdict ci-dessus nomme la transition, il suffit de l'ecouter.
|
|
|
|
|
- name: Retenir ce que le colis du moteur a fait bouger
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
genome_moteur_colis: >-
|
|
|
|
|
{{ (genome_recu.results | default([]))
|
|
|
|
|
| selectattr('item.dest', 'equalto', genome_moteur)
|
|
|
|
|
| map(attribute='stdout') | list }}
|
|
|
|
|
|
|
|
|
|
- name: Voir si ce runner sert une console
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: /etc/systemd/system/setops-gui.service
|
|
|
|
|
register: genome_console_unite
|
|
|
|
|
|
|
|
|
|
- name: Redémarrer la console du site quand son moteur a avancé
|
|
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: setops-gui.service
|
|
|
|
|
state: restarted
|
|
|
|
|
daemon_reload: true
|
|
|
|
|
become: true
|
|
|
|
|
become_user: root
|
|
|
|
|
when:
|
|
|
|
|
- genome_console_unite.stat.exists
|
|
|
|
|
- genome_moteur_colis | length > 0
|
|
|
|
|
- (genome_moteur_colis[0] | from_json).avant
|
|
|
|
|
!= (genome_moteur_colis[0] | from_json).apres
|
|
|
|
|
|
site : le runner travaille, et le genome remonte chez lui
`serveur_ops` et `serveur_ops_site` sont deployes sur `site-ops-01` : le moteur,
les plans des trois tenants, les collections hors ligne, et la voute de
l'underlay deposee CHIFFREE. Le site a son runner.
DEUX FORMES DE RUNNER, ET UNE SEULE ETAIT PREVUE.
`serveur_ops` exigeait un depot `role: instance` et un `serveur_ops_instance` —
la forme d'un runner de TENANT, qui pilote un ecosysteme, monte son plan par le
lien `instance`, detient sa voute.
Un runner de SITE ne pilote rien : il MATERIALISE le terrain que d'autres
occuperont, et son inventaire est dynamique. Son plan declarait donc ses depots
de tenants en `role: tenant`, a dessein et avec ses raisons ecrites — et le role
le refusait au nom d'une exigence qui ne le concernait pas. Il exige desormais
que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
Et il posait le lien quand meme : `serveur_ops_instance: ""` donnait
`instance -> /opt/setops/`, un repertoire qui n'est l'ecosysteme de personne. Un
lien qui existe et ne designe rien est pire qu'un lien absent : tout ce qui le
suit croit avoir trouve une instance.
LE CERTIFICAT COUVRE LES NOMS DU SERVICE.
`client_pki` ne mettait dans ses SAN que le FQDN, le nom court et l'IP. Le
clonage du genome a bute dessus : « certificate subject name
(site-forge-01.genese.internal) does not match target hostname
'forge.genese.internal' ». Le nom declare `expose:` etait publie partout —
plancher, zone DNS — et couvert nulle part.
Les expositions que cet hote SERT REELLEMENT entrent maintenant dans le
certificat. Un certificat qui revendiquerait le nom d'un service rendu ailleurs
serait une usurpation, pas une commodite.
`make genome-pousser` — LE RUNNER POUSSE, LE POSTE NE ROUTE PAS.
La forge du genome vit DANS le site, sur un reseau que le poste de l'exploitant
ne route pas. On ne perce pas un chemin pour lui : on lui retire le role. Le
poste emballe les commits dans un `git bundle` — un fichier, verifiable, qui ne
demande aucun reseau — et le runner verifie, avance EN AVANCE RAPIDE SEULEMENT,
puis pousse avec SA cle, autorisee en ecriture sur ces depots-la seulement.
Ce qui est pousse se DERIVE de `serveur_ops_depots`. `DEPOT=<nom>` en cible un
seul.
Trois pieges rencontres, tous inscrits dans le code : l'outil ne peut pas etre
supposé present sur le runner (il ne recevrait cette version qu'APRES la poussee
qu'elle sert a faire — le controleur le porte) ; la branche n'est pas toujours
`main` (deux depots vivent sur `master`, et le plan le declarait deja) ; le
dossier des depots freres contient un espace, d'ou `argv` et non `cmd`.
Le juge n'est pas le journal du playbook mais LA FORGE : on demande a son API ce
qu'elle porte, et on refuse si ca differe de ce que porte le runner. Six depots
verifies.
Pourquoi ca comptait : la forge du site etait restee quatre commits derriere le
poste, sans que rien ne le signale — dont le correctif qui desarme le pare-feu
Proxmox. Un ecosysteme qui se reproduit depuis une forge en retard reproduit ses
defauts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-26 08:52:37 -04:00
|
|
|
# Le genome n'a pas a trainer en double sur la machine : ce qui compte est dans les
|
|
|
|
|
# depots et sur la forge.
|
|
|
|
|
- name: Retirer les colis
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ genome_racine }}/.bundles"
|
|
|
|
|
state: absent
|
|
|
|
|
|
|
|
|
|
- name: Retirer l'atelier du contrôleur
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ genome_atelier.path }}"
|
|
|
|
|
state: absent
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|