serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
---
|
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
|
|
|
# UN POSTE QUI NE SAIT PAS SUR QUOI IL TRAVAILLE N'A RIEN À PILOTER.
|
2026-08-24 11:42:32 -04:00
|
|
|
#
|
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
|
|
|
# On exige explicitement ce qu'il pilote, plutôt que de retomber sur un défaut : le
|
2026-09-27 21:53:36 -04:00
|
|
|
# défaut nommait un écosystème précis, et un écosystème distrait aurait cloné le génome d'un autre
|
2026-08-24 11:42:32 -04:00
|
|
|
# sans qu'aucune erreur ne le signale.
|
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
|
|
|
#
|
|
|
|
|
# DEUX FORMES DE RUNNER, ET UNE SEULE ÉTAIT PRÉVUE (2026-08-25).
|
|
|
|
|
#
|
|
|
|
|
# Un runner de TENANT pilote un écosystème : il monte son plan par le lien `instance`,
|
|
|
|
|
# détient sa voûte, et configure ses services. Il lui faut donc un dépôt `role: instance`
|
|
|
|
|
# et `serveur_ops_instance`.
|
|
|
|
|
#
|
|
|
|
|
# Un runner de SITE ne pilote aucun écosystème — il MATÉRIALISE le terrain que d'autres
|
|
|
|
|
# occuperont : VNets, VM, routes, flux. Son propre inventaire est DYNAMIQUE
|
|
|
|
|
# (`site_inventaire.py`), il n'a pas de lien `instance` à monter, et les dépôts de
|
|
|
|
|
# tenants qu'il porte sont marqués `role: tenant` précisément pour dire qu'il ne les
|
|
|
|
|
# pilote pas. Ce qu'il pilote, c'est la CARTE de l'hébergeur : `role: hebergeur` et
|
|
|
|
|
# `serveur_ops_underlay`.
|
|
|
|
|
#
|
|
|
|
|
# L'assertion n'admettait que la première forme. Le plan du site déclarait pourtant tout
|
|
|
|
|
# ce qu'il fallait, correctement et avec ses raisons écrites — et le rôle le refusait au
|
|
|
|
|
# nom d'une exigence qui ne le concernait pas.
|
|
|
|
|
#
|
|
|
|
|
# On exige donc que le runner pilote QUELQUE CHOSE, sans prescrire quoi.
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
- name: Exiger les intrants du poste d'exploitation
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- domaine_interne | default('') | length > 0
|
2026-08-24 11:42:32 -04:00
|
|
|
- (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | list | length) == 1
|
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
|
|
|
- >-
|
|
|
|
|
((serveur_ops_depots | selectattr('role', 'eq', 'instance') | list | length) >= 1
|
|
|
|
|
and serveur_ops_instance | default('') | length > 0)
|
|
|
|
|
or
|
|
|
|
|
((serveur_ops_depots | selectattr('role', 'eq', 'hebergeur') | list | length) >= 1
|
|
|
|
|
and serveur_ops_underlay | default('') | length > 0)
|
2026-08-24 14:10:10 -04:00
|
|
|
- not (serveur_ops_forge_externe | bool) or (serveur_ops_forge_amont | length > 0)
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
fail_msg: >-
|
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
|
|
|
serveur_ops exige `domaine_interne`, exactement un dépôt `role: moteur`, et de
|
|
|
|
|
savoir sur quoi il travaille — SOIT un dépôt `role: instance` avec
|
|
|
|
|
`serveur_ops_instance` (runner de TENANT : il pilote un écosystème), SOIT un dépôt
|
|
|
|
|
`role: hebergeur` avec `serveur_ops_underlay` (runner de SITE : il matérialise le
|
|
|
|
|
terrain, et son inventaire est dynamique). Et si `serveur_ops_forge_externe` est
|
|
|
|
|
levé — écosystème au premier âge, qui lit son génome chez son hôte —
|
|
|
|
|
`serveur_ops_forge_amont` doit nommer cette forge.
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
|
|
|
|
|
- name: Installer l'outillage du poste
|
|
|
|
|
ansible.builtin.apt:
|
|
|
|
|
name: "{{ serveur_ops_paquets }}"
|
|
|
|
|
state: present
|
|
|
|
|
update_cache: true
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Créer l'utilisateur d'exploitation
|
|
|
|
|
ansible.builtin.user:
|
|
|
|
|
name: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
home: "{{ serveur_ops_racine }}"
|
|
|
|
|
shell: /bin/bash
|
|
|
|
|
create_home: true
|
|
|
|
|
system: false
|
|
|
|
|
|
|
|
|
|
- name: Poser les droits de la racine d'exploitation
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_ops_racine }}"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0750"
|
|
|
|
|
|
|
|
|
|
# L'ENVIRONNEMENT PYTHON EST ISOLÉ, PAS SYSTÈME. Debian gère ansible en paquet, mais la
|
|
|
|
|
# version qu'il propose suit son propre calendrier : reconstruire un écosystème avec un
|
|
|
|
|
# ansible plus récent que celui qui l'a construit, c'est changer la recette sans le dire.
|
|
|
|
|
# Le venv permet d'épingler exactement la famille utilisée par le mainteneur.
|
|
|
|
|
- name: Créer l'environnement Python isolé
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: "python3 -m venv {{ serveur_ops_venv }}"
|
|
|
|
|
creates: "{{ serveur_ops_venv }}/bin/python"
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
# LES BIBLIOTHEQUES DU CONTROLEUR SE DECLARENT, ELLES AUSSI (2026-08-27). Cette liste
|
|
|
|
|
# etait ecrite en dur — `ansible-core` et `pyyaml`, rien de plus — alors que les modules
|
|
|
|
|
# Proxmox du moteur exigent `proxmoxer` sur la machine qui les execute. Le runner du SITE
|
|
|
|
|
# a donc echoue a sa premiere materialisation de VM, sur une bibliotheque que le poste du
|
|
|
|
|
# mainteneur possedait par son paquet systeme sans que personne ne l'ait declaree.
|
|
|
|
|
# `requirements-python.txt` est desormais la source, au meme titre que `requirements.yml`
|
|
|
|
|
# pour les collections. Voir la preuve P51.
|
|
|
|
|
- name: Deposer la liste des bibliotheques Python du controleur
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: "{{ playbook_dir }}/../../requirements-python.txt"
|
|
|
|
|
dest: "{{ serveur_ops_racine }}/requirements-python.txt"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0644"
|
|
|
|
|
|
roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors
LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.
Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :
ERROR: Could not find a version that satisfies the requirement ansible-core
Echec temporaire dans la resolution du nom
PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.
--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.
LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:37:09 -04:00
|
|
|
# --- LA ROUE HORS-LIGNE ------------------------------------------------------
|
|
|
|
|
#
|
|
|
|
|
# MEME IDIOME QUE LES COLLECTIONS, quelques lignes plus bas : le CONTROLEUR telecharge
|
|
|
|
|
# une fois dans son cache, puis pousse par SSH. La cible n'ouvre aucun flux, et
|
|
|
|
|
# l'artefact devient deployable hors ligne.
|
|
|
|
|
|
|
|
|
|
- name: Preparer le cache des roues Python sur le controleur
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_ops_cache_roues }}"
|
|
|
|
|
state: directory
|
|
|
|
|
mode: "0755"
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
run_once: true
|
|
|
|
|
|
|
|
|
|
# LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
|
|
|
|
|
#
|
|
|
|
|
# `pip download` rend les roues du systeme qui telecharge. `ansible-core`, `proxmoxer` et
|
|
|
|
|
# `requests` sont universelles (`py3-none-any`), mais `pyyaml` porte du C compile : une
|
|
|
|
|
# roue `cp313` posee sur un python 3.12 ne s'installe pas. L'echec serait « No matching
|
|
|
|
|
# distribution found » a l'installation — un message qui accuse le depot local alors que
|
|
|
|
|
# le coupable est l'ecart entre deux machines. On le mesure ici, ou on peut encore le dire.
|
|
|
|
|
- name: Lire le Python du controleur
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv: ["python3", "-c", "import sys; print('%d.%d' % sys.version_info[:2])"]
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
run_once: true
|
|
|
|
|
changed_when: false
|
2026-09-14 11:02:37 -04:00
|
|
|
# LIRE UNE VERSION N'EST PAS CHANGER, ET `--check` RENDAIT LA GARDE MUETTE (2026-09-14).
|
|
|
|
|
#
|
|
|
|
|
# Sans `check_mode: false`, cette commande est sautee en mode idempotent : la garde qui
|
|
|
|
|
# suit compare alors contre du VIDE et refuse.
|
|
|
|
|
#
|
|
|
|
|
# Le controleur tourne Python et site-ops-01 tourne Python 3.13
|
|
|
|
|
#
|
|
|
|
|
# L'espace apres « Python » est tout le diagnostic — il n'y avait rien a comparer. Un
|
|
|
|
|
# essai a blanc rendait donc un echec sur le runner, alors que le deploiement reel passe.
|
|
|
|
|
check_mode: false
|
roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors
LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.
Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :
ERROR: Could not find a version that satisfies the requirement ansible-core
Echec temporaire dans la resolution du nom
PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.
--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.
LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:37:09 -04:00
|
|
|
register: serveur_ops_python_controleur
|
|
|
|
|
|
|
|
|
|
- name: Exiger que le controleur et la cible partagent leur version de Python
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- serveur_ops_python_controleur.stdout | trim
|
|
|
|
|
== ansible_python_version.split('.')[0:2] | join('.')
|
|
|
|
|
fail_msg: >-
|
|
|
|
|
Le controleur tourne Python {{ serveur_ops_python_controleur.stdout | trim }} et
|
|
|
|
|
{{ inventory_hostname }} tourne Python
|
|
|
|
|
{{ ansible_python_version.split('.')[0:2] | join('.') }}. Les roues telechargees ne
|
|
|
|
|
s'installeraient pas sur la cible, et l'echec accuserait le depot local au lieu de
|
|
|
|
|
cet ecart. Materialiser depuis un controleur de meme famille — pendant une
|
|
|
|
|
insemination, c'est le runner du SITE, qui porte le meme Debian que la machine
|
|
|
|
|
qu'il amorce.
|
|
|
|
|
|
|
|
|
|
# `argv` ET NON `cmd` : le chemin du controleur peut contenir une ESPACE (« Espace
|
|
|
|
|
# Chezlepro/… » chez le mainteneur). Meme piege que pour ansible-galaxy le 2026-08-23.
|
|
|
|
|
- name: Telecharger les roues sur le controleur (une seule fois par empreinte)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv:
|
|
|
|
|
- "python3"
|
|
|
|
|
- "-m"
|
|
|
|
|
- "pip"
|
|
|
|
|
- "download"
|
|
|
|
|
- "--dest"
|
|
|
|
|
- "{{ serveur_ops_cache_roues }}"
|
|
|
|
|
- "--requirement"
|
|
|
|
|
- "{{ serveur_ops_roues_source }}"
|
|
|
|
|
- "{{ serveur_ops_ansible }}"
|
|
|
|
|
- "pyyaml"
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
run_once: true
|
|
|
|
|
register: serveur_ops_roues_telechargees
|
|
|
|
|
changed_when: "'Saved' in serveur_ops_roues_telechargees.stdout"
|
|
|
|
|
# LE GARDE-FOU SUIT LE RESULTAT, PAS LE GESTE (lecon du 2026-08-27) : on saute quand le
|
|
|
|
|
# cache PORTE deja des roues, pas quand une commande a deja tourne un jour.
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Deposer les roues sur le runner
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: "{{ serveur_ops_cache_roues }}/"
|
|
|
|
|
dest: "{{ serveur_ops_roues_depot }}/"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0644"
|
|
|
|
|
directory_mode: "0755"
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
# `--no-index` EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
|
|
|
|
|
# sortir vers PyPI le jour ou le depot local serait incomplet — et la reussite dependrait
|
|
|
|
|
# alors d'un flux que ce tenant n'a pas le droit d'avoir. Un repli silencieux vers
|
|
|
|
|
# l'Internet transformerait une lignee autonome en lignee qui a l'air autonome.
|
|
|
|
|
- name: Installer Ansible dans l'environnement isolé (hors ligne)
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
ansible.builtin.pip:
|
|
|
|
|
name:
|
|
|
|
|
- "{{ serveur_ops_ansible }}"
|
|
|
|
|
- "pyyaml"
|
|
|
|
|
virtualenv: "{{ serveur_ops_venv }}"
|
|
|
|
|
state: present
|
roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors
LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.
Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :
ERROR: Could not find a version that satisfies the requirement ansible-core
Echec temporaire dans la resolution du nom
PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.
--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.
LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:37:09 -04:00
|
|
|
extra_args: "--no-index --find-links {{ serveur_ops_roues_depot }}"
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
register: serveur_ops_pip
|
|
|
|
|
retries: 3
|
|
|
|
|
delay: 6
|
|
|
|
|
until: serveur_ops_pip is succeeded
|
|
|
|
|
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
# TACHE SEPAREE : dans `ansible.builtin.pip`, `name` et `requirements` sont MUTUELLEMENT
|
|
|
|
|
# EXCLUSIFS. Les fusionner ne produit pas une erreur parlante — le module ignore l'un des
|
|
|
|
|
# deux, et la bibliotheque manquante ne se revele qu'au premier module qui en depend.
|
roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors
LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.
Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :
ERROR: Could not find a version that satisfies the requirement ansible-core
Echec temporaire dans la resolution du nom
PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.
--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.
LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:37:09 -04:00
|
|
|
- name: Installer les bibliotheques Python du controleur (source declaree, hors ligne)
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
ansible.builtin.pip:
|
|
|
|
|
requirements: "{{ serveur_ops_racine }}/requirements-python.txt"
|
|
|
|
|
virtualenv: "{{ serveur_ops_venv }}"
|
|
|
|
|
state: present
|
roues : pip par le meme chemin que les collections — le controleur telecharge, la cible n installe rien du dehors
LA DERNIERE DEPENDANCE QUI SORTAIT PAR ELLE-MEME. Les collections Ansible suivent
depuis longtemps l idiome de ce depot : le CONTROLEUR telecharge une fois puis
pousse par SSH, aucun flux nouveau depuis la cible, et l artefact devient
deployable hors ligne. pip sortait encore vers PyPI, en HTTPS et par nom.
Un tenant n a NI DNS SORTANT NI 443 vers l Internet, et c est voulu. Mesure sur
ops-01, premiere machine de la reconstruction de Chezlepro :
ERROR: Could not find a version that satisfies the requirement ansible-core
Echec temporaire dans la resolution du nom
PAS LE PAQUET DEBIAN : trixie propose ansible-core 2.19.4, hors de la plage
epinglee. Y aller demanderait de deplacer l epinglage vers une version majeure aux
changements de gabarits connus, trois jours apres l avoir pose pour cause de
portabilite.
--no-index EST LE POINT, PAS UNE OPTIMISATION. Sans lui, pip resterait capable de
sortir vers PyPI le jour ou le depot local serait incomplet : la reussite
dependrait d un flux que ce tenant n a pas le droit d avoir, et une lignee
autonome deviendrait une lignee qui a l air autonome.
LE CONTROLEUR ET LA CIBLE DOIVENT PARTAGER LEUR PYTHON, et ca ne se devine pas.
pyyaml porte du C compile ; une roue cp313 posee sur un python 3.12 ne s installe
pas, et l echec accuserait le depot local au lieu de l ecart entre deux machines.
Mesure et dit ici, ou on peut encore le nommer.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019f91zs9SDdwSzL2CVei6on
2026-08-28 15:37:09 -04:00
|
|
|
extra_args: "--no-index --find-links {{ serveur_ops_roues_depot }}"
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
register: serveur_ops_pip_controleur
|
|
|
|
|
retries: 3
|
|
|
|
|
delay: 6
|
|
|
|
|
until: serveur_ops_pip_controleur is succeeded
|
|
|
|
|
|
2026-08-26 16:42:24 -04:00
|
|
|
# L'OUTILLAGE DOIT ETRE SUR LE CHEMIN DE CELUI QUI L'EXPLOITE (2026-08-26).
|
|
|
|
|
#
|
|
|
|
|
# Ansible vit dans un venv isole — c'est voulu, il est epingle. Mais rien ne le mettait sur
|
|
|
|
|
# le PATH du compte d'exploitation : `make instancier` y echouait sur
|
|
|
|
|
# « [Errno 2] No such file or directory: 'ansible-inventory' », un message qui accuse un
|
|
|
|
|
# fichier manquant alors que le fichier est la, deux dossiers plus loin.
|
|
|
|
|
#
|
|
|
|
|
# Mesure du 2026-08-26, en eprouvant la sequence depuis le runner du site. Un exploitant y
|
|
|
|
|
# serait tombe exactement de la meme facon — et c'est precisement ce qu'un outil « utilisable
|
|
|
|
|
# sans IA » ne doit pas faire.
|
|
|
|
|
#
|
|
|
|
|
# Un fragment `profile.d` plutot qu'un `.bashrc` : il vaut pour toute session du compte, y
|
|
|
|
|
# compris `sudo -iu setops`, et se retire d'un seul geste.
|
|
|
|
|
- name: Mettre l'outillage du poste sur le chemin du compte d'exploitation
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
dest: /etc/profile.d/50-setops-venv.sh
|
|
|
|
|
content: |
|
|
|
|
|
# GÉNÉRÉ par Set-OPS (rôle serveur_ops). NE PAS éditer à la main.
|
|
|
|
|
# Ansible vit dans un venv épinglé ; sans cette ligne, `make` ne le trouve pas.
|
|
|
|
|
if [ "$(id -un)" = "{{ serveur_ops_utilisateur }}" ]; then
|
|
|
|
|
PATH="{{ serveur_ops_venv }}/bin:$PATH"
|
|
|
|
|
export PATH
|
|
|
|
|
fi
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0644"
|
|
|
|
|
|
2026-08-24 18:55:44 -04:00
|
|
|
# LA CONFIANCE, AVANT LE CLONE — et limitee a une seule url.
|
|
|
|
|
#
|
|
|
|
|
# Voir defaults/main.yml : on ne pose pas cette racine dans le magasin systeme. `git
|
|
|
|
|
# config http.<url>.sslCAInfo` ne l'applique qu'aux echanges avec CETTE forge.
|
|
|
|
|
- name: Deposer la racine de la forge amont
|
|
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: "{{ serveur_ops_forge_amont_ac }}"
|
|
|
|
|
dest: "{{ serveur_ops_forge_amont_ac_depot }}"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0644"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_forge_amont_ac | length > 0
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Limiter la confiance a la forge amont, et a elle seule
|
|
|
|
|
community.general.git_config:
|
|
|
|
|
name: "http.{{ serveur_ops_forge_url }}/.sslCAInfo"
|
|
|
|
|
scope: global
|
|
|
|
|
value: "{{ serveur_ops_forge_amont_ac_depot }}"
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_forge_amont_ac | length > 0
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
# --- LE GÉNOME, DEPUIS SA PROPRE FORGE ---------------------------------------
|
|
|
|
|
#
|
|
|
|
|
# Cloné en anonyme : les dépôts du génome sont lisibles sur la forge de l'écosystème, et
|
|
|
|
|
# le poste n'a donc AUCUN justificatif à détenir pour se reconstruire. C'est délibéré —
|
|
|
|
|
# un secret de moins sur la machine qui en concentre déjà beaucoup.
|
|
|
|
|
#
|
2026-08-24 18:55:44 -04:00
|
|
|
# La confiance TLS vient de `client_pki` quand la forge est celle de CET écosystème
|
|
|
|
|
# (racine step-ca dans le magasin système). Quand elle est celle d'un voisin, elle vient
|
|
|
|
|
# de la tâche ci-dessus — limitée à cette seule url. Sans l'une ou l'autre, `git clone`
|
|
|
|
|
# refuse le certificat, et c'est bien qu'il refuse.
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
- name: Cloner le génome depuis la forge de l'écosystème
|
|
|
|
|
ansible.builtin.git:
|
|
|
|
|
repo: "{{ serveur_ops_forge_url }}/{{ serveur_ops_forge_organisation }}/{{ item.depot }}.git"
|
|
|
|
|
dest: "{{ serveur_ops_racine }}/{{ item.dest }}"
|
|
|
|
|
version: "{{ item.branche | default('main') }}"
|
|
|
|
|
update: true
|
|
|
|
|
force: false
|
|
|
|
|
loop: "{{ serveur_ops_depots }}"
|
|
|
|
|
loop_control:
|
|
|
|
|
label: "{{ item.depot }} → {{ item.dest }}"
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
register: serveur_ops_clone
|
|
|
|
|
retries: 3
|
|
|
|
|
delay: 6
|
|
|
|
|
until: serveur_ops_clone is succeeded
|
|
|
|
|
|
console des runners : le code sur le disque n'est pas le code servi
Les trois consoles portaient le moteur du jour et servaient celui du 16 septembre. Le
clone du genome ne notifiait rien et la tache de service demande state: started, qui ne
fait rien quand le service tourne deja ; inventory_gui.py lit son code au demarrage, et
seulement la. Le role retient desormais si le MOTEUR a avance — designe par
serveur_ops_depot_moteur, jamais un chemin recopie — et redemarre la console dans ce seul
cas : un plan qui avance ne coupe pas les pages ouvertes.
La sonde console-ops criait au vestibule ouvert sur les deux runners de locataire, sur
des consoles fermees. Elle frappait la boucle locale, seul endroit d'ou un 200 est le
resultat attendu en mode oidc, ou nginx est reduit a 127.0.0.1 pour qu'on ne contourne
pas la passerelle SSO. Elle connait maintenant son mode : en locale le verdict ne bouge
pas, en oidc la serrure mesuree est l'adresse d'ecoute. Le refus de la passerelle reste
l'affaire de la sonde passerelle de serveur_oauth2_proxy, sur le meme hote.
Valide : --syntax-check sur playbooks/groupes/serveur_ops.yml, ansible-lint sur le role
(profil production, 0 echec, 0 avertissement, 14 fichiers), le gabarit rendu dans ses deux
modes sans reste Jinja et bash -n propre, le filtre d'ecoute eprouve sur cinq formes
d'adresses, l'expression du when sur cinq cas dont le mode check. Le role est applique aux
trois runners, failed=0, et la sonde rejouee sur chacun rend 0.
Limite : le redemarrage automatique ne s'est pas declenche, les clones etant deja au
niveau de la forge — la tache s'est correctement abstenue. La preuve du declenchement
viendra au prochain genome pousse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:27:50 -04:00
|
|
|
# CE QUI A AVANCE, ET CE QUE CA OBLIGE A RELIRE (2026-09-20).
|
|
|
|
|
#
|
|
|
|
|
# SEUL LE MOTEUR PORTE LE CODE DE LA CONSOLE. Un plan qui avance ne change pas
|
|
|
|
|
# `inventory_gui.py` ; le redemarrer alors couperait les pages ouvertes pour rien. Le
|
|
|
|
|
# dossier du moteur est celui que `serveur_ops_depot_moteur` derive de la liste des
|
|
|
|
|
# depots — jamais un chemin recopie ici.
|
|
|
|
|
- name: Retenir si le moteur du génome a avancé
|
|
|
|
|
ansible.builtin.set_fact:
|
|
|
|
|
serveur_ops_moteur_avance: >-
|
|
|
|
|
{{ (serveur_ops_clone.results | default([]))
|
|
|
|
|
| selectattr('item.dest', 'equalto', serveur_ops_depot_moteur)
|
|
|
|
|
| selectattr('changed', 'defined') | selectattr('changed')
|
|
|
|
|
| list | length > 0 }}
|
|
|
|
|
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
# --- LES COLLECTIONS, SANS DEPENDRE DE GALAXY ---------------------------------
|
|
|
|
|
#
|
|
|
|
|
# UN POSTE D'EXPLOITATION QUI APPELLE galaxy.ansible.com POUR SE CONSTRUIRE N'EST PAS
|
|
|
|
|
# SOUVERAIN. Mesure du 2026-08-23, premier deploiement : `ansible-galaxy collection
|
2026-09-27 21:53:36 -04:00
|
|
|
# install` a echoue depuis l'overlay d'un écosystème —
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
# `SSL: UNEXPECTED_EOF_WHILE_READING` — parce que la frontiere ne laisse pas passer ce
|
|
|
|
|
# flux, et c'est tres bien ainsi. Ouvrir une regle vers un serveur americain pour que
|
|
|
|
|
# l'ecosysteme sache se reconstruire aurait ete la mauvaise reponse.
|
|
|
|
|
#
|
|
|
|
|
# MEME IDIOME QUE serveur_forgejo DEVANT SON PROPRE TIERS : le CONTROLEUR telecharge une
|
|
|
|
|
# fois, dans son cache, puis pousse par SSH. Aucun flux nouveau depuis la cible, et
|
|
|
|
|
# l'artefact devient deployable hors ligne — un poste peut se reconstruire sans internet
|
|
|
|
|
# du tout, ce qui est le sens meme d'une lignee autonome.
|
|
|
|
|
- name: Preparer le cache des collections sur le controleur
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_ops_cache_collections }}"
|
|
|
|
|
state: directory
|
|
|
|
|
mode: "0755"
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
run_once: true
|
|
|
|
|
|
|
|
|
|
# `argv` ET NON `cmd` : le chemin du controleur peut contenir une ESPACE (« Espace
|
|
|
|
|
# Chezlepro/… » chez le mainteneur), et `cmd` la lit comme un separateur d'arguments.
|
|
|
|
|
# Constate le 2026-08-23 : ansible-galaxy recevait « /home/…/Espace » puis
|
|
|
|
|
# « Chezlepro/… » comme deux arguments, et refusait — « positional collection_name arg
|
|
|
|
|
# and --requirements-file are mutually exclusive ». Le message ne parlait pas du tout du
|
|
|
|
|
# vrai probleme.
|
|
|
|
|
- name: Telecharger les collections dans le cache du controleur (une seule fois)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv:
|
|
|
|
|
- "ansible-galaxy"
|
|
|
|
|
- "collection"
|
|
|
|
|
- "download"
|
|
|
|
|
- "-r"
|
|
|
|
|
- "{{ serveur_ops_requirements_controleur }}"
|
|
|
|
|
- "-p"
|
|
|
|
|
- "{{ serveur_ops_cache_collections }}"
|
|
|
|
|
creates: "{{ serveur_ops_cache_collections }}/requirements.yml"
|
|
|
|
|
delegate_to: localhost
|
|
|
|
|
become: false
|
|
|
|
|
run_once: true
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Deposer les collections sur le poste
|
2026-08-24 15:50:37 -04:00
|
|
|
register: serveur_ops_depot_collections
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
ansible.builtin.copy:
|
|
|
|
|
src: "{{ serveur_ops_cache_collections }}/"
|
|
|
|
|
dest: "{{ serveur_ops_racine }}/.collections-hors-ligne/"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0644"
|
|
|
|
|
directory_mode: "0755"
|
|
|
|
|
when: not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
# `chdir` OBLIGATOIRE : le `requirements.yml` produit par `collection download` nomme les
|
|
|
|
|
# archives en RELATIF (`community-postgresql-4.2.0.tar.gz`), et ansible-galaxy les cherche
|
|
|
|
|
# depuis le repertoire COURANT, pas depuis celui du fichier. Sans chdir : « Could not find
|
|
|
|
|
# community-postgresql-4.2.0.tar.gz » alors que l'archive est bien la, a cote.
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
# LE GARDE-FOU DOIT SUIVRE LE RESULTAT, PAS LE GESTE (2026-08-27). La condition
|
|
|
|
|
# d'installation ne regardait que le DEPOT des archives. Le jour ou l'on a corrige le
|
|
|
|
|
# CHEMIN d'installation, les archives etaient inchangees : le role a donc conclu qu'il
|
|
|
|
|
# n'y avait rien a faire, et les collections sont restees a l'ancien endroit. Le meme
|
|
|
|
|
# piege qu'en 2026-08-24, deplace d'un cran. On mesure desormais la DESTINATION.
|
|
|
|
|
- name: Voir si les collections sont deja la ou le moteur les cherche
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: >-
|
|
|
|
|
{{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur')
|
|
|
|
|
| first).dest }}/.ansible/collections/ansible_collections/community/general/MANIFEST.json
|
|
|
|
|
register: serveur_ops_collections_en_place
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
- name: Installer les collections depuis le depot local (hors ligne)
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: >-
|
|
|
|
|
{{ serveur_ops_venv }}/bin/ansible-galaxy collection install
|
collections : epingler les versions — le runner echouait la ou le poste reussissait
Premiere materialisation de VM depuis le runner du SITE :
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
Meme depot, meme playbook, meme plan que chez le mainteneur. La difference tenait
a une seule chose que le moteur ne disait pas : la VERSION de ses collections.
Le poste porte `community.general` 10.3.0. Le runner, monte un jour plus tard, a
recu la 13.3.0 — et la version 11 a RETIRE les modules Proxmox de cette collection
(ils vivent desormais dans `community.proxmox`). `requirements.yml` nommait ses
collections sans dire lesquelles : chaque machine installait donc ce qui etait
courant le jour de son montage. Une dependance non epinglee n'est pas une
dependance, c'est un pari sur l'etat d'Internet a la date du deploiement.
TROIS CORRECTIONS.
`requirements.yml` epingle les trois collections aux versions eprouvees.
`serveur_ops` installe avec `--force`. Sans lui, ansible-galaxy laisse en place une
version SUPERIEURE a celle demandee : il ne retrograde pas. Un poste peut etre en
avance, pas seulement en retard, et le depot doit faire autorite dans les deux sens.
P51 garde les deux faiblesses de ce fichier — celle du 2026-08-24, une collection
utilisee sans etre declaree (`ansible.posix`), et celle d'aujourd'hui, declaree sans
version. La preuve ne lit que les fichiers de TACHES : un `defaults/main.yml` porte
`net.ipv4.ip_forward`, qu'un motif trop large prend pour un module.
MIGRATION CONNUE, PAS FAITE : passer les quatre modules Proxmox a
`community.proxmox` permettra de suivre `community.general` au-dela de la 11.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:58:23 -04:00
|
|
|
-r requirements.yml --force
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
-p {{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur')
|
|
|
|
|
| first).dest }}/.ansible/collections
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
chdir: "{{ serveur_ops_racine }}/.collections-hors-ligne"
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
# INSTALLER LA OU LE MOTEUR REGARDE (2026-08-27). Le `Makefile` pose
|
|
|
|
|
# `export ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous `make`, Ansible ne cherche donc ses
|
|
|
|
|
# collections QUE dans `<moteur>/.ansible/collections`. Ce role les deposait dans
|
|
|
|
|
# `<racine>/.ansible/collections` — a cote, jamais lu. Le runner du SITE echouait ainsi
|
|
|
|
|
# sur `couldn't resolve module/action community.general.proxmox_pool` alors que la
|
|
|
|
|
# collection etait bel et bien installee, complete, et visible par `ansible-doc`.
|
|
|
|
|
#
|
|
|
|
|
# POURQUOI LE MAINTENEUR NE VOYAIT RIEN : sur son poste, les collections viennent du
|
|
|
|
|
# paquet systeme (`/usr/lib/python3/dist-packages/ansible_collections`), toujours dans
|
|
|
|
|
# le chemin de recherche quel que soit `ANSIBLE_HOME`. Le reglage du Makefile n'y a
|
|
|
|
|
# jamais eu de consequence. Sur un runner, ou rien n'est installe par la distribution,
|
|
|
|
|
# il decide de tout. Encore un ecart qu'on ne voit qu'en portant le moteur ailleurs.
|
|
|
|
|
#
|
collections : epingler les versions — le runner echouait la ou le poste reussissait
Premiere materialisation de VM depuis le runner du SITE :
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
Meme depot, meme playbook, meme plan que chez le mainteneur. La difference tenait
a une seule chose que le moteur ne disait pas : la VERSION de ses collections.
Le poste porte `community.general` 10.3.0. Le runner, monte un jour plus tard, a
recu la 13.3.0 — et la version 11 a RETIRE les modules Proxmox de cette collection
(ils vivent desormais dans `community.proxmox`). `requirements.yml` nommait ses
collections sans dire lesquelles : chaque machine installait donc ce qui etait
courant le jour de son montage. Une dependance non epinglee n'est pas une
dependance, c'est un pari sur l'etat d'Internet a la date du deploiement.
TROIS CORRECTIONS.
`requirements.yml` epingle les trois collections aux versions eprouvees.
`serveur_ops` installe avec `--force`. Sans lui, ansible-galaxy laisse en place une
version SUPERIEURE a celle demandee : il ne retrograde pas. Un poste peut etre en
avance, pas seulement en retard, et le depot doit faire autorite dans les deux sens.
P51 garde les deux faiblesses de ce fichier — celle du 2026-08-24, une collection
utilisee sans etre declaree (`ansible.posix`), et celle d'aujourd'hui, declaree sans
version. La preuve ne lit que les fichiers de TACHES : un `defaults/main.yml` porte
`net.ipv4.ip_forward`, qu'un motif trop large prend pour un module.
MIGRATION CONNUE, PAS FAITE : passer les quatre modules Proxmox a
`community.proxmox` permettra de suivre `community.general` au-dela de la 11.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 18:58:23 -04:00
|
|
|
# `--force` PARCE QU'UN POSTE PEUT ETRE EN AVANCE, PAS SEULEMENT EN RETARD (2026-08-27).
|
|
|
|
|
# Sans lui, `ansible-galaxy` laisse en place une collection deja installee dans une
|
|
|
|
|
# version SUPERIEURE a celle demandee : il ne retrograde pas. Le runner du SITE, monte
|
|
|
|
|
# avant que `requirements.yml` n'epingle ses versions, portait `community.general` 13.3.0
|
|
|
|
|
# — une version d'ou les modules Proxmox ont ete RETIRES. La premiere materialisation de
|
|
|
|
|
# VM depuis le runner a echoue la-dessus. Le depot doit faire autorite dans les DEUX
|
|
|
|
|
# sens : ce que `requirements.yml` declare est ce qui est installe, ni plus haut ni plus
|
|
|
|
|
# bas. Voir la preuve P51.
|
|
|
|
|
#
|
2026-08-24 15:50:37 -04:00
|
|
|
# LE GARDE-FOU SUIVAIT LA MAUVAISE CHOSE (2026-08-24). Il etait `creates: …/community/general` :
|
|
|
|
|
# une collection AJOUTEE a requirements.yml n'aurait jamais ete installee, puisque le
|
|
|
|
|
# repertoire temoin existait deja. Le runner serait reste en retard sur le moteur, en
|
|
|
|
|
# silence. On suit desormais le DEPOT des archives : si les collections deposees
|
|
|
|
|
# changent, on reinstalle.
|
|
|
|
|
changed_when: true
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
2026-08-24 15:50:37 -04:00
|
|
|
# DEUX `when` SUR UNE MEME TACHE, C'EST UN SEUL — LE DERNIER (2026-08-24). En ajoutant
|
|
|
|
|
# la condition sur le depot, j'ai laisse celle du check-mode juste en dessous : YAML
|
|
|
|
|
# garde la derniere cle et jette l'autre, en silence. `ansible-lint` l'a vu ; personne
|
|
|
|
|
# d'autre ne l'aurait vu. Les conditions vont donc dans UNE liste.
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_depot_collections is changed
|
portabilite : declarer ce dont le moteur depend — quatre defauts reveles par le runner
Premiere materialisation d'une VM de tenant depuis le runner du SITE. Elle a
echoue quatre fois d'affilee, sur quatre dependances que le moteur ne declarait
nulle part. Chacune fonctionnait chez le mainteneur pour une raison DIFFERENTE,
et aucune de ces raisons n'existe sur une autre machine.
1. VERSIONS DES COLLECTIONS. `requirements.yml` les nommait sans les epingler. Le
runner a recu `community.general` 13.3.0 quand le poste porte la 10.3.0 — et la
11 a retire les modules Proxmox de cette collection.
ERROR! couldn't resolve module/action 'community.general.proxmox_pool'
2. INSTALLATION EN AVANT. `ansible-galaxy` ne retrograde pas : declarer la 10.3.0
ne suffisait pas a defaire une 13.3.0 deja posee. `--force`.
3. EMPLACEMENT. Le `Makefile` pose `ANSIBLE_HOME ?= $(CURDIR)/.ansible` : sous
`make`, Ansible ne lit QUE `<moteur>/.ansible/collections`. Le role deposait
dans `<racine>/.ansible/collections` — a cote, jamais lu. Invisible chez le
mainteneur, ou les collections viennent du paquet systeme, toujours dans le
chemin quel que soit ANSIBLE_HOME. Et le garde-fou d'installation suivait le
DEPOT des archives : changer la destination ne le declenchait pas. Il mesure
desormais la destination — meme piege qu'en 2026-08-24, deplace d'un cran.
4. BIBLIOTHEQUES PYTHON. Le venv recevait `ansible-core` et `pyyaml`, ecrits en
dur. Les modules Proxmox tournent `delegate_to: localhost` et exigent
`proxmoxer` SUR LE CONTROLEUR.
La bibliotheque Python proxmoxer est absente
Ne d'ici : `requirements-python.txt`, avec sa frontiere ecrite — il ne declare
QUE ce qui tourne sur le controleur. `python-ldap` et `psycopg2` s'executent
sur leurs cibles ; le poste du mainteneur ne les a pas et la flotte se deploie,
ce qui prouve que la ligne est au bon endroit.
P51 garde les trois faiblesses de cette famille : une dependance utilisee sans
etre declaree (le defaut du 2026-08-24, `ansible.posix`), une dependance declaree
sans version, et `proxmoxer` absent alors que des playbooks Proxmox existent.
Quatre controles negatifs verifies.
RESULTAT : ops-01 materialisee par le runner du site. L'agent invite rapporte
`eth0 10.17.19.41/24`, Debian 13 trixie — l'adressage derive du plan, applique.
Un seul executant masque ces ecarts indefiniment ; un second les revele tous en
une soiree. Meme mecanique que le second tenant en aout.
make prouver : CONFORME, 51 OK, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-27 22:38:35 -04:00
|
|
|
or not serveur_ops_collections_en_place.stat.exists
|
2026-08-24 15:50:37 -04:00
|
|
|
- not ansible_check_mode
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
|
|
|
|
|
# LES DEUX SYMLINKS (D-80). `instance` dit QUEL tenant on pilote. Le poste en pose un par
|
|
|
|
|
# défaut ; l'exploitant le bascule comme sur le poste du mainteneur.
|
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
|
|
|
#
|
|
|
|
|
# UN RUNNER DE SITE N'EN A PAS (2026-08-26). Il ne pilote aucun écosystème — il matérialise
|
|
|
|
|
# le terrain — et son propre inventaire est DYNAMIQUE (`site_inventaire.py`). Son plan
|
|
|
|
|
# déclare donc `serveur_ops_instance: ""`, et c'est une réponse, pas un oubli.
|
|
|
|
|
#
|
|
|
|
|
# Sans la garde ci-dessous, la chaîne rendait `{{ racine }}/` + `""` : le lien `instance`
|
|
|
|
|
# pointait sur `/opt/setops/` lui-même — un répertoire qui n'est l'écosystème de personne.
|
|
|
|
|
# Mesuré le 2026-08-26 : `instance -> /opt/setops/`. Un lien qui existe et ne désigne rien
|
|
|
|
|
# est pire qu'un lien absent : tout ce qui le suit croit avoir trouvé une instance.
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
- name: Désigner l'instance pilotée par défaut
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
src: "{{ serveur_ops_racine }}/{{ serveur_ops_instance }}"
|
|
|
|
|
dest: >-
|
|
|
|
|
{{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/instance
|
|
|
|
|
state: link
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
force: true
|
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
|
|
|
when:
|
|
|
|
|
- serveur_ops_instance | default('') | trim | length > 0
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
# Et s'il n'y en a pas, le lien ne doit pas SURVIVRE d'un passage precedent : un runner
|
|
|
|
|
# qu'on reconfigure en runner de site garderait sinon un lien perime vers un tenant.
|
|
|
|
|
- name: Retirer le lien `instance` quand le poste ne pilote aucun écosystème
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: >-
|
|
|
|
|
{{ serveur_ops_racine }}/{{ (serveur_ops_depots | selectattr('role', 'eq', 'moteur') | first).dest }}/instance
|
|
|
|
|
state: absent
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_instance | default('') | trim | length == 0
|
|
|
|
|
- not ansible_check_mode
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
|
2026-08-23 12:24:28 -04:00
|
|
|
# LE SECOND SYMLINK. Sans lui, le poste sait configurer des machines qui existent ; il ne
|
|
|
|
|
# sait pas en creer, faute de savoir sur quelle fabric les poser. C'est la difference
|
|
|
|
|
# entre exploiter et engendrer.
|
|
|
|
|
- name: Désigner la fabric sur laquelle l'écosystème repose
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
src: "{{ serveur_ops_racine }}/{{ serveur_ops_underlay }}/underlay.yml"
|
la console mesure le pouvoir sur la voute, pas sur la carte
Le document des responsabilites attache chaque pouvoir a une voute : calculer n'en demande
aucune, configurer demande celle du tenant, materialiser celle du site. Le code lisait les
symlinks, c'est-a-dire les cartes. Le runner de Chezlepro-locataire montait la carte du
site sans en avoir jamais eu la voute : sa console se declarait poste et offrait 126 etapes
sur 126. Ces gestes seraient partis puis tombes sur un secret vide — un echec au milieu du
chemin, la ou un refus net aurait dit la verite avant de commencer.
contexte() derive desormais les pouvoirs des voutes presentes, et la portee decoule des
pouvoirs au lieu de les preceder. On ne prouve pas qu'une voute s'ouvre, le mot de passe se
tape a l'execution ; mais son absence est decisive et se mesure sans rien ouvrir. Lire une
carte reste permis : le pouvoir fabric suit toujours le symlink, consulter un miroir n'est
pas engendrer.
serveur_ops retire aussi le lien quand la fabric n'est plus declaree — il ne retirait rien,
et un runner gardait le pouvoir que son plan ne lui donnait plus. Il ne retire qu'un lien,
jamais un fichier : une vraie carte a cette place n'a pas ete ecrite par ce role, et il le
dit plutot que de detruire ce qui n'est pas le sien.
Valide : syntax-check et ansible-lint sur le role (profil production, 0/0), make test a 0
echec, P81 et P83 vertes. Six tests montent quatre faux disques et exigent la portee qui
leur revient, dont le defaut lui-meme : carte presente, voute absente, portee tenant.
Limite : P02 reste en echec pour la raison anterieure deja consignee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:12:18 -04:00
|
|
|
dest: "{{ serveur_ops_chemin_underlay }}"
|
2026-08-23 12:24:28 -04:00
|
|
|
state: link
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
force: true
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_underlay | length > 0
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
la console mesure le pouvoir sur la voute, pas sur la carte
Le document des responsabilites attache chaque pouvoir a une voute : calculer n'en demande
aucune, configurer demande celle du tenant, materialiser celle du site. Le code lisait les
symlinks, c'est-a-dire les cartes. Le runner de Chezlepro-locataire montait la carte du
site sans en avoir jamais eu la voute : sa console se declarait poste et offrait 126 etapes
sur 126. Ces gestes seraient partis puis tombes sur un secret vide — un echec au milieu du
chemin, la ou un refus net aurait dit la verite avant de commencer.
contexte() derive desormais les pouvoirs des voutes presentes, et la portee decoule des
pouvoirs au lieu de les preceder. On ne prouve pas qu'une voute s'ouvre, le mot de passe se
tape a l'execution ; mais son absence est decisive et se mesure sans rien ouvrir. Lire une
carte reste permis : le pouvoir fabric suit toujours le symlink, consulter un miroir n'est
pas engendrer.
serveur_ops retire aussi le lien quand la fabric n'est plus declaree — il ne retirait rien,
et un runner gardait le pouvoir que son plan ne lui donnait plus. Il ne retire qu'un lien,
jamais un fichier : une vraie carte a cette place n'a pas ete ecrite par ce role, et il le
dit plutot que de detruire ce qui n'est pas le sien.
Valide : syntax-check et ansible-lint sur le role (profil production, 0/0), make test a 0
echec, P81 et P83 vertes. Six tests montent quatre faux disques et exigent la portee qui
leur revient, dont le defaut lui-meme : carte presente, voute absente, portee tenant.
Limite : P02 reste en echec pour la raison anterieure deja consignee.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 20:12:18 -04:00
|
|
|
# LA DECLARATION VAUT DANS LES DEUX SENS (2026-09-20).
|
|
|
|
|
#
|
|
|
|
|
# `serveur_ops_underlay` vide dit « ce poste exploite sans engendrer ». Le role se
|
|
|
|
|
# contentait alors de NE PAS POSER le lien — et un runner qui l'avait deja le gardait,
|
|
|
|
|
# avec le pouvoir que son plan ne lui donnait plus. Mesure du jour : le runner de
|
|
|
|
|
# Chezlepro-LOCATAIRE montait encore la carte du site, et sa console offrait ses 126
|
|
|
|
|
# etapes, creation et destruction de machines comprises.
|
|
|
|
|
#
|
|
|
|
|
# Retirer une declaration doit retirer l'artefact, sinon la declaration ne decrit plus
|
|
|
|
|
# rien. Meme patron que la sonde de console, qui suit son exposition dans les deux sens.
|
|
|
|
|
#
|
|
|
|
|
# ON NE RETIRE QU'UN LIEN, JAMAIS UN FICHIER. Sur un runner, `underlay.yml` a la racine du
|
|
|
|
|
# moteur n'est que le symlink pose ci-dessus. Si quelqu'un y a depose une vraie carte, ce
|
|
|
|
|
# n'est plus le meme objet — on le laisse et on le dit, plutot que de detruire un contenu
|
|
|
|
|
# qu'on n'a pas ecrit.
|
|
|
|
|
- name: Voir ce que porte la place de la fabric
|
|
|
|
|
ansible.builtin.stat:
|
|
|
|
|
path: "{{ serveur_ops_chemin_underlay }}"
|
|
|
|
|
register: serveur_ops_place_fabric
|
|
|
|
|
when: serveur_ops_underlay | length == 0
|
|
|
|
|
|
|
|
|
|
- name: Retirer la fabric quand cet écosystème ne l'engendre plus
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_ops_chemin_underlay }}"
|
|
|
|
|
state: absent
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_underlay | length == 0
|
|
|
|
|
- serveur_ops_place_fabric.stat.islnk | default(false)
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Dire qu'une vraie carte occupe la place, au lieu de la détruire
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg: >-
|
|
|
|
|
{{ serveur_ops_chemin_underlay }} est un FICHIER, pas le lien que ce rôle pose.
|
|
|
|
|
Ce plan ne déclare plus de fabric ; ce fichier n'a donc pas été touché, et ce
|
|
|
|
|
runner garde de quoi lire une fabric qu'il ne déclare plus. À trancher à la main.
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_underlay | length == 0
|
|
|
|
|
- serveur_ops_place_fabric.stat.exists | default(false)
|
|
|
|
|
- not (serveur_ops_place_fabric.stat.islnk | default(false))
|
|
|
|
|
|
serveur_ops : la difference entre une archive et une matrice
Un ecosysteme pouvait DETENIR son genome sans savoir l'executer. Le poste
d'exploitation porte Ansible epingle, le genome clone depuis SA PROPRE forge, et
une cle SSH qui n'appartient qu'a lui. Il n'emporte ni la voute ni son mot de
passe : la structure se reconstruit depuis la forge, les secrets depuis la
sauvegarde. Deux sources qu'un meme incident n'atteint pas ensemble.
Une machine neuve traverse tout le moteur sans rien heriter, et revele ce qu'un
etat anterieur masquait. Deux defauts silencieux en sont sortis.
setops_plan_dir pointait le lien `instance` en dur : neuf roles lisaient donc le
plan d'une AUTRE instance. Le plancher de patient 0 portait les FQDN de
Chezlepro, et son edge publiait server_name forge.chezlepro.internal. La
variable suit desormais l'inventaire reellement charge.
Trois instances sur quatre declaraient les SAN d'exposition de leur edge ; la
quatrieme et le modele public ne les avaient pas. La forge de patient 0 etait
donc publiee derriere le certificat auto-signe de Debian, sans que rien ne le
signale -- `git clone` fut le premier a refuser, a juste titre. P42 le reclame
maintenant pour tout ecosysteme qui declare un edge.
Le harnais a ecrit la moitie de ce role : 5 echecs sur la piece neuve, aucun
n'empechait le code de tourner, tous la rendaient invisible a la carte, au
graphe et au lecteur. 42 preuves, 0 echec.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-23 11:58:10 -04:00
|
|
|
# --- LA CLÉ DU POSTE ---------------------------------------------------------
|
|
|
|
|
#
|
|
|
|
|
# Le poste se fabrique sa propre paire, distincte de celle du mainteneur. Deux
|
|
|
|
|
# exploitants, deux clés : on peut révoquer l'un sans couper l'autre — et l'accès du
|
|
|
|
|
# poste se lit dans les `authorized_keys` de la flotte, au lieu de se confondre avec
|
|
|
|
|
# celui d'un humain.
|
|
|
|
|
#
|
|
|
|
|
# `ssh-keygen` plutôt que `community.crypto.openssh_keypair` : le dépôt ne requiert que
|
|
|
|
|
# `community.postgresql` et `community.general`, et fabriquer une paire de clés ne
|
|
|
|
|
# justifie pas d'imposer une collection de plus à tout écosystème qui déploie un poste.
|
|
|
|
|
- name: Assurer le répertoire SSH du poste
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_ops_racine }}/.ssh"
|
|
|
|
|
state: directory
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0700"
|
|
|
|
|
|
|
|
|
|
- name: Fabriquer la clé SSH du poste
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
cmd: >-
|
|
|
|
|
ssh-keygen -t {{ serveur_ops_cle_type }} -N ''
|
|
|
|
|
-C "setops@{{ inventory_hostname }}.{{ domaine_interne }}"
|
|
|
|
|
-f {{ serveur_ops_racine }}/.ssh/id_{{ serveur_ops_cle_type }}
|
|
|
|
|
creates: "{{ serveur_ops_racine }}/.ssh/id_{{ serveur_ops_cle_type }}"
|
|
|
|
|
become: true
|
|
|
|
|
become_user: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_cle_generer | bool
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
register: serveur_ops_cle
|
|
|
|
|
|
|
|
|
|
- name: Relire la clé publique du poste
|
|
|
|
|
ansible.builtin.slurp:
|
|
|
|
|
src: "{{ serveur_ops_racine }}/.ssh/id_{{ serveur_ops_cle_type }}.pub"
|
|
|
|
|
register: serveur_ops_pub
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_cle_generer | bool
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
# ÉCRIRE, PUIS RELIRE (D-68) — et rendre le résultat EXPLOITABLE. Cette clé ne sert à
|
|
|
|
|
# rien tant qu'elle n'est pas autorisée sur la flotte : on l'affiche pour que l'exploitant
|
|
|
|
|
# la reprenne dans les intrants, au lieu de la laisser dormir sur le disque.
|
|
|
|
|
- name: La clé publique à autoriser sur la flotte
|
|
|
|
|
ansible.builtin.debug:
|
|
|
|
|
msg:
|
|
|
|
|
- "Clé publique du poste d'exploitation — à ajouter aux intrants SSH du plan :"
|
|
|
|
|
- "{{ serveur_ops_pub.content | b64decode | trim }}"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_cle_generer | bool
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Déposer le mode d'emploi sur le poste
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: LISEZ-MOI.md.j2
|
|
|
|
|
dest: "{{ serveur_ops_racine }}/LISEZ-MOI.md"
|
|
|
|
|
owner: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
group: "{{ serveur_ops_utilisateur }}"
|
|
|
|
|
mode: "0644"
|
sondes : quatorze, une par role, derivees en objets Icinga
boites (dovecot) · cache-apt (artefacts) · certificat (client_pki, 14/14)
collaboration (nextcloud) · collecte (prometheus) · file-courriel (postfix)
forge (forgejo) · identite (keycloak) · ingestion (loki) · moteur (icinga)
resolution (resolveur) · runner (serveur_ops) · tableaux (grafana)
voute (ops_tenant) — toutes vertes, sans une ligne ecrite dans Icinga.
DEUX PRINCIPES QUE LA PREMIERE SONDE A IMPOSES.
Une sonde doit pouvoir etre mise en defaut PAR PARAMETRE : cible et seuils
sont des variables du role, on prouve le rouge avec un port ferme ou un
seuil impossible, sans rien casser. Et la sonde vit LA OU VIT LA VERITE :
« ce noeud est-il collecte ? » appartient a prometheus, pas au client —
une seule y voit les N noeuds, et surtout elle voit le cas SILENCIEUX.
ON DEMANDE AU SERVICE CE QU IL PENSE DE LUI-MEME quand il sait le dire
(healthz, /ready, status.php, decouverte OIDC). Quand il ne sait pas, on
va chercher la verite de terrain : « moteur » ne regarde ni le service ni
le port, il demande a la base depuis combien de temps elle n a pas ete
rafraichie — la lecon des sauvegardes appliquee a la supervision.
QUATRE FOIS J AI ECRIT LA SONDE AVANT DE MESURER, QUATRE FOIS ELLE A EU
TORT. La forge : port et chemin des depots inventes, elle ecoute en 3000
derriere l edge et n a legitimement aucun depot. Loki : « panne
persistante » conclue sur deux lectures a quelques secondes d intervalle
juste apres un redemarrage — deux mesures rapprochees ne distinguent pas
un etat d un instant. Keycloak : vise en 8443, il ecoute en 8080. Le
runner : git en root refuse un depot d un autre proprietaire. A chaque
fois le remede est le meme — lire la verite du role, ne pas la supposer.
Et le meme piege Jinja qu avec client_sante : ${#tableau[@]} contient {#.
Le remede etait deja au depot ; je l ai reecrit au lieu de le chercher.
RESTE : client_smtp, client_artefacts, client_journal, icingaweb2 et
ops_site. Ce sont des chemins de report, dont la panne se voit deja par le
silence des sondes qu ils portent.
make prouver : CONFORME, 64 OK, 0 echec, 0 saute (P64 : 14 sondes).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-10 03:00:21 -04:00
|
|
|
|
|
|
|
|
# --- Sonde de supervision (docs/supervision-conception.md) --------------------------
|
|
|
|
|
# Le role qui possede la verite depose sa propre sonde ; le porteur (`client_sante`) la
|
|
|
|
|
# fait tourner et pousse le verdict, sans savoir ce qu'elle mesure.
|
|
|
|
|
- name: Assurer le repertoire des sondes de supervision
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /usr/local/lib/setops/sondes
|
|
|
|
|
state: directory
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0755"
|
|
|
|
|
|
|
|
|
|
- name: Deposer la sonde « runner »
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: sonde-runner.sh.j2
|
|
|
|
|
dest: /usr/local/lib/setops/sondes/runner.sh
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0750"
|
la console d exploitation devient un service, et elle n avait aucune serrure
Le GUI n a aucune authentification : GET / sert la page a qui la demande, jeton
ecrit dedans, et POST /api exige ce jeton que la page vient de donner. Le jeton
garde contre le CSRF, pas contre un visiteur — la seule serrure est
--hote 127.0.0.1. Ce qu il offre a qui entre : deployer, creer, raser, editer le
plan. La fabric entiere.
Le service reste donc sur la boucle locale. Ce qui est publie est un nginx local
qui authentifie d abord : oidc par defaut (oauth2-proxy, donc un groupe
d annuaire qu on revoque sans deploiement), locale en repli pour un ecosysteme
sans annuaire. L authentification est posee au niveau du server, pas d un
location.
La sonde console-ops mesure une SERRURE : une requete anonyme doit etre REFUSEE.
Un 200 y est la pire des reponses, et il ne fait echouer personne.
P54 a attrape une contrainte ratee : serveur_ops est insemine par le SITE, qui
ne detient pas la voute du locataire. Le role ne nomme donc aucune voute — il
declare un parametre, et la couche qui detient le secret le remplit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 19:27:28 -04:00
|
|
|
|
|
|
|
|
# --- LA CONSOLE D'EXPLOITATION, SERVIE PAR CE RUNNER (2026-09-14) ---------------------
|
|
|
|
|
#
|
|
|
|
|
# CE QU'ELLE ETAIT : `make inventaire-ui`, lancee a la main, ecoutant la boucle locale du
|
|
|
|
|
# POSTE. Pour la voir, il fallait etre assis devant le controleur.
|
|
|
|
|
#
|
|
|
|
|
# CE QU'ELLE DEVIENT : un service du runner, publie par l'edge de son ecosysteme sous le
|
|
|
|
|
# nom que le plan lui donne. Chaque ecosysteme a sa console, chez lui.
|
|
|
|
|
#
|
|
|
|
|
# ELLE RESTE SUR LA BOUCLE LOCALE. Voir `serveur_ops_gui_actif` dans les defauts : le GUI
|
|
|
|
|
# sert sa page a qui la demande, jeton compris — ce jeton garde contre le CSRF, pas
|
|
|
|
|
# contre un visiteur. Ce qui est publie, c'est le nginx qui authentifie devant.
|
|
|
|
|
- name: Exiger un vestibule devant la console
|
|
|
|
|
ansible.builtin.assert:
|
|
|
|
|
that:
|
|
|
|
|
- (serveur_ops_gui_auth != 'locale') or (serveur_ops_gui_admin_motdepasse | length > 0)
|
|
|
|
|
fail_msg: >-
|
|
|
|
|
`serveur_ops_gui_auth: locale` mais `serveur_ops_gui_admin_motdepasse` est vide.
|
|
|
|
|
Cette console lance des deploiements et peut raser un ecosysteme : elle ne s'ouvre
|
|
|
|
|
pas avec un mot de passe devine. Le renseigner depuis la voute de l'ECOSYSTEME QUI
|
|
|
|
|
LA PUBLIE — ce role n'en nomme aucune, il est insemine par un site qui ne la
|
|
|
|
|
detient pas — ou passer en `oidc`.
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
|
|
|
|
|
- name: Installer le vestibule de la console
|
|
|
|
|
ansible.builtin.apt:
|
|
|
|
|
name: "{{ serveur_ops_gui_paquets }}"
|
|
|
|
|
state: present
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
|
|
|
|
|
- name: Déployer le service de la console
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: setops-gui.service.j2
|
|
|
|
|
dest: /etc/systemd/system/setops-gui.service
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0644"
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
notify: Redemarrer la console Set-OPS
|
|
|
|
|
|
|
|
|
|
# LE MOT DE PASSE DU VESTIBULE, QUAND IL N'Y A PAS D'ANNUAIRE.
|
|
|
|
|
#
|
|
|
|
|
# `htpasswd -v` VERIFIE sans rien reecrire : c'est lui qui rend la pose idempotente. Une
|
|
|
|
|
# comparaison de fichiers ne le pourrait pas — bcrypt sale a chaque hachage, et deux
|
|
|
|
|
# fichiers justes different toujours.
|
|
|
|
|
- name: Le mot de passe deja pose est-il le bon ?
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv:
|
|
|
|
|
- htpasswd
|
|
|
|
|
- -vb
|
|
|
|
|
- "{{ serveur_ops_gui_htpasswd }}"
|
|
|
|
|
- "{{ serveur_ops_gui_admin_utilisateur }}"
|
|
|
|
|
- "{{ serveur_ops_gui_admin_motdepasse }}"
|
|
|
|
|
register: serveur_ops_gui_verif
|
|
|
|
|
changed_when: false
|
|
|
|
|
failed_when: false
|
|
|
|
|
no_log: true
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_gui_actif | bool
|
|
|
|
|
- serveur_ops_gui_auth == 'locale'
|
|
|
|
|
|
|
|
|
|
- name: Poser le mot de passe du vestibule
|
|
|
|
|
ansible.builtin.command:
|
|
|
|
|
argv:
|
|
|
|
|
- htpasswd
|
|
|
|
|
- -cbB
|
|
|
|
|
- "{{ serveur_ops_gui_htpasswd }}"
|
|
|
|
|
- "{{ serveur_ops_gui_admin_utilisateur }}"
|
|
|
|
|
- "{{ serveur_ops_gui_admin_motdepasse }}"
|
|
|
|
|
no_log: true
|
|
|
|
|
# Elle ne tourne que si la verification a echoue : quand elle tourne, elle change
|
|
|
|
|
# vraiment quelque chose. L'idempotence vit dans le `when`, pas dans un test de sortie.
|
|
|
|
|
changed_when: true
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_gui_actif | bool
|
|
|
|
|
- serveur_ops_gui_auth == 'locale'
|
|
|
|
|
- (serveur_ops_gui_verif.rc | default(1)) != 0
|
|
|
|
|
notify: Recharger le vestibule de la console
|
|
|
|
|
|
|
|
|
|
- name: Refermer le fichier de mots de passe sur nginx
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: "{{ serveur_ops_gui_htpasswd }}"
|
|
|
|
|
owner: root
|
|
|
|
|
group: www-data
|
|
|
|
|
mode: "0640"
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_gui_actif | bool
|
|
|
|
|
- serveur_ops_gui_auth == 'locale'
|
|
|
|
|
|
|
|
|
|
- name: Déployer le vestibule de la console
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: nginx-gui.conf.j2
|
|
|
|
|
dest: /etc/nginx/sites-available/setops-gui.conf
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0644"
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
notify: Recharger le vestibule de la console
|
|
|
|
|
|
|
|
|
|
- name: Activer le vestibule de la console
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
src: /etc/nginx/sites-available/setops-gui.conf
|
|
|
|
|
dest: /etc/nginx/sites-enabled/setops-gui.conf
|
|
|
|
|
state: link
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
notify: Recharger le vestibule de la console
|
|
|
|
|
|
|
|
|
|
# LE SITE PAR DEFAUT SERT TOUT CE QU'ON NE LUI A PAS DEMANDE. Le laisser en place
|
|
|
|
|
# donnerait une seconde porte, sans vestibule, sur le meme port.
|
|
|
|
|
- name: Retirer le site nginx par defaut
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /etc/nginx/sites-enabled/default
|
|
|
|
|
state: absent
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
notify: Recharger le vestibule de la console
|
|
|
|
|
|
|
|
|
|
- name: Activer et demarrer la console
|
|
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: setops-gui.service
|
|
|
|
|
enabled: true
|
|
|
|
|
state: started
|
|
|
|
|
daemon_reload: true
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_gui_actif | bool
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
console des runners : le code sur le disque n'est pas le code servi
Les trois consoles portaient le moteur du jour et servaient celui du 16 septembre. Le
clone du genome ne notifiait rien et la tache de service demande state: started, qui ne
fait rien quand le service tourne deja ; inventory_gui.py lit son code au demarrage, et
seulement la. Le role retient desormais si le MOTEUR a avance — designe par
serveur_ops_depot_moteur, jamais un chemin recopie — et redemarre la console dans ce seul
cas : un plan qui avance ne coupe pas les pages ouvertes.
La sonde console-ops criait au vestibule ouvert sur les deux runners de locataire, sur
des consoles fermees. Elle frappait la boucle locale, seul endroit d'ou un 200 est le
resultat attendu en mode oidc, ou nginx est reduit a 127.0.0.1 pour qu'on ne contourne
pas la passerelle SSO. Elle connait maintenant son mode : en locale le verdict ne bouge
pas, en oidc la serrure mesuree est l'adresse d'ecoute. Le refus de la passerelle reste
l'affaire de la sonde passerelle de serveur_oauth2_proxy, sur le meme hote.
Valide : --syntax-check sur playbooks/groupes/serveur_ops.yml, ansible-lint sur le role
(profil production, 0 echec, 0 avertissement, 14 fichiers), le gabarit rendu dans ses deux
modes sans reste Jinja et bash -n propre, le filtre d'ecoute eprouve sur cinq formes
d'adresses, l'expression du when sur cinq cas dont le mode check. Le role est applique aux
trois runners, failed=0, et la sonde rejouee sur chacun rend 0.
Limite : le redemarrage automatique ne s'est pas declenche, les clones etant deja au
niveau de la forge — la tache s'est correctement abstenue. La preuve du declenchement
viendra au prochain genome pousse.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-20 15:27:50 -04:00
|
|
|
# LE CODE NEUF SUR LE DISQUE N'EST PAS LE CODE SERVI (2026-09-20).
|
|
|
|
|
#
|
|
|
|
|
# `state: started` ne fait rien quand le service tourne deja, et le clone du genome ne
|
|
|
|
|
# notifiait rien. Un runner pouvait donc porter le moteur du jour et servir, depuis sa
|
|
|
|
|
# memoire, celui d'il y a quatre jours. Mesure de ce jour-la sur les trois runners :
|
|
|
|
|
# moteur 6367d85 sur le disque, console demarree le 16 septembre a 16h36. Le handler
|
|
|
|
|
# « Redemarrer la console Set-OPS » existait deja ; il n'etait branche que sur le
|
|
|
|
|
# fichier d'unite, qui lui ne changeait pas.
|
|
|
|
|
#
|
|
|
|
|
# LE PIEGE EST CONNU AILLEURS : un certificat renouvele qu'un nginx non recharge continue
|
|
|
|
|
# de servir perime. Ecrire ne suffit pas — il faut que le consommateur relise.
|
|
|
|
|
# `python3 scripts/inventory_gui.py` lit son code au demarrage, et seulement la.
|
|
|
|
|
- name: Redémarrer la console quand le moteur a avancé
|
|
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: setops-gui.service
|
|
|
|
|
state: restarted
|
|
|
|
|
daemon_reload: true
|
|
|
|
|
when:
|
|
|
|
|
- serveur_ops_gui_actif | bool
|
|
|
|
|
- serveur_ops_moteur_avance | default(false) | bool
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
la console d exploitation devient un service, et elle n avait aucune serrure
Le GUI n a aucune authentification : GET / sert la page a qui la demande, jeton
ecrit dedans, et POST /api exige ce jeton que la page vient de donner. Le jeton
garde contre le CSRF, pas contre un visiteur — la seule serrure est
--hote 127.0.0.1. Ce qu il offre a qui entre : deployer, creer, raser, editer le
plan. La fabric entiere.
Le service reste donc sur la boucle locale. Ce qui est publie est un nginx local
qui authentifie d abord : oidc par defaut (oauth2-proxy, donc un groupe
d annuaire qu on revoque sans deploiement), locale en repli pour un ecosysteme
sans annuaire. L authentification est posee au niveau du server, pas d un
location.
La sonde console-ops mesure une SERRURE : une requete anonyme doit etre REFUSEE.
Un 200 y est la pire des reponses, et il ne fait echouer personne.
P54 a attrape une contrainte ratee : serveur_ops est insemine par le SITE, qui
ne detient pas la voute du locataire. Le role ne nomme donc aucune voute — il
declare un parametre, et la couche qui detient le secret le remplit.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
2026-09-14 19:27:28 -04:00
|
|
|
# UN RUNNER QUI CESSE DE PUBLIER SA CONSOLE DOIT L'ETEINDRE. Sans ce retrait, retirer
|
|
|
|
|
# l'exposition du plan laisserait le service tourner et le vestibule ouvert — la console
|
|
|
|
|
# resterait joignable alors que plus rien ne la declare.
|
|
|
|
|
- name: Éteindre la console quand ce runner ne la publie plus
|
|
|
|
|
ansible.builtin.systemd:
|
|
|
|
|
name: setops-gui.service
|
|
|
|
|
enabled: false
|
|
|
|
|
state: stopped
|
|
|
|
|
failed_when: false
|
|
|
|
|
when:
|
|
|
|
|
- not (serveur_ops_gui_actif | bool)
|
|
|
|
|
- not ansible_check_mode
|
|
|
|
|
|
|
|
|
|
- name: Retirer le vestibule quand ce runner ne publie plus
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /etc/nginx/sites-enabled/setops-gui.conf
|
|
|
|
|
state: absent
|
|
|
|
|
when: not (serveur_ops_gui_actif | bool)
|
|
|
|
|
notify: Recharger le vestibule de la console
|
|
|
|
|
|
|
|
|
|
- name: Deposer la sonde « console-ops »
|
|
|
|
|
ansible.builtin.template:
|
|
|
|
|
src: sonde-console-ops.sh.j2
|
|
|
|
|
dest: /usr/local/lib/setops/sondes/console-ops.sh
|
|
|
|
|
owner: root
|
|
|
|
|
group: root
|
|
|
|
|
mode: "0750"
|
|
|
|
|
when: serveur_ops_gui_actif | bool
|
|
|
|
|
|
|
|
|
|
# LA SONDE SUIT LA DECLARATION, DANS LES DEUX SENS. Un runner qui cesse de publier sa
|
|
|
|
|
# console garderait sinon une sonde orpheline : `client_sante` continuerait de la jouer
|
|
|
|
|
# et de pousser un resultat pour un service qu'Icinga ne definit plus.
|
|
|
|
|
- name: Retirer la sonde « console-ops » quand ce runner ne publie plus
|
|
|
|
|
ansible.builtin.file:
|
|
|
|
|
path: /usr/local/lib/setops/sondes/console-ops.sh
|
|
|
|
|
state: absent
|
|
|
|
|
when: not (serveur_ops_gui_actif | bool)
|