trois reconstructions : trouver, verifier, prouver — 37 min 24 au tour 3
Tour 1 8 passages ~2 h 30 16 defauts Tour 2 3 passages 53 min 2 defauts Tour 3 2 passages 37 min 0 LE TOUR 2 EST CELUI QUI COMPTE LE PLUS. J avais annonce deux passages ; il en fallait trois, parce que les deux blocages connus sont EN SERIE : on ne peut pas amorcer une forge qui ne tourne pas, et elle ne tourne qu apres la convergence de la cle. Un runbook ecrit d apres un recit de depannage contenait une erreur qu aucune relecture n aurait montree — il fallait le SUIVRE pour la voir. DEUX MURS DE PLUS, invisibles au tour 1. site-creer rend la main avant que les machines repondent (3 min 20 mesurees). Et les cles d hote changent a chaque reconstruction : accept-new couvre la premiere rencontre, jamais un changement. LE CYCLE DE LA CLE EST DENOUE. Aucun ordre de couches n y pouvait rien — les certificats doivent venir tot. C est la propriete du geste qui a change de main : serveur_forgejo revendique la cle au moment ou il cree le groupe. client_pki garde la sienne et la repose a chaque passage, parce que step reecrit la cle a chaque renouvellement. Gain : un passage entier et 300 secondes d attente perdue. forge-amorcer purge desormais l entree perimee de l hote que GIT va contacter, lu dans git remote get-url — pas celle de l API, qui peut etre un tunnel. Et il rend les quatre premieres lignes de stderr : n afficher que la derniere m a coute un aller-retour, git terminant toujours par un message generique. Le runbook porte les six etapes du tour 3 avec leurs durees reelles et les dix-huit murs. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Crgis8CxCWkAGFA1ecBz3q
This commit is contained in:
parent
bef0655112
commit
b9b0d87db2
4 changed files with 193 additions and 17 deletions
63
CHANGELOG.md
63
CHANGELOG.md
|
|
@ -1,5 +1,68 @@
|
||||||
# CHANGELOG — Set-OPS
|
# CHANGELOG — Set-OPS
|
||||||
|
|
||||||
|
## 2026-09-12 (3) — Trois reconstructions : trouver, verifier, prouver
|
||||||
|
|
||||||
|
La premiere reconstruction avait trouve seize defauts. Deux tours de plus ont ete faits, et
|
||||||
|
c'est le second qui compte le plus : **un runbook ecrit d'apres un recit de depannage
|
||||||
|
contenait une erreur qu'aucune relecture n'aurait montree.**
|
||||||
|
|
||||||
|
Tour 1 8 passages ~2 h 30 16 defauts trouves
|
||||||
|
Tour 2 3 passages 53 min 2 defauts trouves
|
||||||
|
Tour 3 2 passages 37 min 24 0
|
||||||
|
|
||||||
|
### Ce que le tour 2 a corrige dans le runbook
|
||||||
|
|
||||||
|
J'avais annonce DEUX passages. Il en fallait TROIS, et pour une raison instructive : les
|
||||||
|
deux blocages connus — la cle de Forgejo et la forge vide — sont **en serie**. On ne peut
|
||||||
|
pas amorcer une forge qui ne tourne pas, et elle ne tourne qu'apres la convergence de la
|
||||||
|
cle. Je les croyais franchissables dans le meme passage.
|
||||||
|
|
||||||
|
Il fallait SUIVRE le runbook pour le voir.
|
||||||
|
|
||||||
|
### Deux murs de plus, invisibles au tour 1
|
||||||
|
|
||||||
|
**#17 — `site-creer` rend la main avant que les machines repondent.** Mesure : 3 min 20 au
|
||||||
|
tour 3. Le chemin locataire a `_attendre-flotte` ; le site n'a pas d'equivalent. Un
|
||||||
|
enchainement automatique echouerait sur `UNREACHABLE` qui n'est qu'une impatience.
|
||||||
|
|
||||||
|
**#18 — les cles d'hote changent a chaque reconstruction.** `known_hosts` porte l'ancienne,
|
||||||
|
`git` refuse — a juste titre — et sans terminal il ne peut rien demander. Le message qu'il
|
||||||
|
rend parle de droits d'acces et de depot inexistant.
|
||||||
|
|
||||||
|
`accept-new` accepte une PREMIERE rencontre, jamais un CHANGEMENT : il ne suffit donc pas
|
||||||
|
pour une machine reconstruite sur une adresse deja connue. `forge-amorcer` purge desormais
|
||||||
|
l'entree perimee — celle de l'hote que **git** va contacter, lu dans `git remote get-url`,
|
||||||
|
et non celle de l'API : quand l'amorcage passe par un tunnel, les deux different et purger
|
||||||
|
la mauvaise ne se verrait qu'au push.
|
||||||
|
|
||||||
|
### Le cycle de la cle, denoue
|
||||||
|
|
||||||
|
`client_pki` posait les droits de la cle d'hote pour le groupe `git`, que le paquet de
|
||||||
|
Forgejo cree dans une couche POSTERIEURE. Aucun ordre de couches ne denoue ce cycle : les
|
||||||
|
certificats doivent venir tot, tout le monde en depend.
|
||||||
|
|
||||||
|
C'est la PROPRIETE DU GESTE qui a change de main. `serveur_forgejo` revendique la cle au
|
||||||
|
moment ou il cree le groupe ; `client_pki` garde la sienne et la repose a chaque passage,
|
||||||
|
parce que `step` reecrit la cle a chaque renouvellement. Les deux se recouvrent, et c'est
|
||||||
|
voulu : l'une amorce, l'autre entretient.
|
||||||
|
|
||||||
|
Gain mesure : un passage entier, et 300 secondes d'attente perdue.
|
||||||
|
|
||||||
|
### Ce que mon propre outil masquait
|
||||||
|
|
||||||
|
`forge-amorcer` n'affichait que la DERNIERE ligne de `stderr` — or `git` termine toujours
|
||||||
|
par « assurez-vous que le depot existe », quelle que soit la cause. Un aller-retour de
|
||||||
|
diagnostic pour une cle d'hote. Il rend maintenant les quatre premieres lignes.
|
||||||
|
|
||||||
|
C'est le meme defaut que ceux trouves dans le moteur, commis par moi : **un message qui
|
||||||
|
pointe ailleurs que sa cause**.
|
||||||
|
|
||||||
|
### La sequence est desormais mesuree, pas reconstituee
|
||||||
|
|
||||||
|
`docs/runbooks-exploitation.md` §9 porte les six etapes du tour 3, avec leurs durees
|
||||||
|
reelles et les dix-huit murs. Le troisieme tour n'a rien trouve de nouveau : c'est le
|
||||||
|
premier ou reconstruire le site est une PROCEDURE et non une enquete.
|
||||||
|
|
||||||
## 2026-09-12 (2) — Le site est reconstruit depuis zero, et il a fallu seize corrections
|
## 2026-09-12 (2) — Le site est reconstruit depuis zero, et il a fallu seize corrections
|
||||||
|
|
||||||
`SITE-Chezlepro` n'avait jamais ete rase. Il avait ete monte par ajouts successifs, sur des
|
`SITE-Chezlepro` n'avait jamais ete rase. Il avait ete monte par ajouts successifs, sur des
|
||||||
|
|
|
||||||
|
|
@ -327,15 +327,27 @@ laissé le premier renommage, jusqu'au passage de `client_pki`.
|
||||||
> la connexion. Les zones du SITE ne sont pas routées depuis le plan d'administration du
|
> la connexion. Les zones du SITE ne sont pas routées depuis le plan d'administration du
|
||||||
> locataire — le mur est la frontière, pas le vhost.
|
> locataire — le mur est la frontière, pas le vhost.
|
||||||
|
|
||||||
## 9. Le premier jour d'un site — la séquence, et les seize murs
|
## 9. Le premier jour d'un site — la séquence, et les dix-huit murs
|
||||||
|
|
||||||
> **Écrit le 2026-09-12**, au sortir de la première reconstruction d'un site depuis zéro.
|
> **Écrit le 2026-09-12**, au sortir de la première reconstruction d'un site depuis zéro.
|
||||||
> Avant elle, `SITE-Chezlepro` n'avait jamais été rasé : il avait été monté par ajouts
|
> Avant elle, `SITE-Chezlepro` n'avait jamais été rasé : il avait été monté par ajouts
|
||||||
> successifs, sur des semaines, avec un service déjà debout à chaque étape.
|
> successifs, sur des semaines, avec un service déjà debout à chaque étape.
|
||||||
>
|
>
|
||||||
> La limite qu'on répétait — « l'infrastructure d'accueil n'a jamais été reconstruite
|
> La limite qu'on répétait — « l'infrastructure d'accueil n'a jamais été reconstruite
|
||||||
> depuis zéro » — se lisait comme de la prudence. C'était **seize défauts** que rien
|
> depuis zéro » — se lisait comme de la prudence. C'était **dix-huit défauts** que rien
|
||||||
> d'autre n'aurait pu révéler.
|
> d'autre n'aurait pu révéler.
|
||||||
|
>
|
||||||
|
> **Trois reconstructions complètes** ont été nécessaires : la première pour les trouver,
|
||||||
|
> la deuxième pour vérifier les correctifs — elle en a révélé deux de plus, invisibles
|
||||||
|
> tant que l'état n'était pas assez neuf — et la troisième pour prouver la séquence.
|
||||||
|
>
|
||||||
|
> | | Passages | Durée | Défauts trouvés |
|
||||||
|
> |---|---|---|---|
|
||||||
|
> | Tour 1 | 8 | ~2 h 30 | 16 |
|
||||||
|
> | Tour 2 | 3 | 53 min | 2 |
|
||||||
|
> | Tour 3 | **2** | **37 min 24** | **0** |
|
||||||
|
>
|
||||||
|
> La séquence ci-dessous est celle du troisième tour. Elle est mesurée, pas reconstituée.
|
||||||
|
|
||||||
### Ce qui rend un site différent d'un locataire
|
### Ce qui rend un site différent d'un locataire
|
||||||
|
|
||||||
|
|
@ -352,31 +364,48 @@ C'est de là que viennent onze des quinze murs.
|
||||||
# 0. AVANT TOUT — l'état sort du bâtiment
|
# 0. AVANT TOUT — l'état sort du bâtiment
|
||||||
make depot-hors-site VERS=<support hors site>
|
make depot-hors-site VERS=<support hors site>
|
||||||
|
|
||||||
# 1. Les VM, depuis l'underlay
|
# 1. Le résolveur d'amorçage, DÉCLARÉ dans le plan du site AVANT de créer quoi que ce soit
|
||||||
make site-creer CONFIRMER=true # ~12 min pour 7 machines
|
|
||||||
|
|
||||||
# 2. Le résolveur d'amorçage, DÉCLARÉ dans le plan du site
|
|
||||||
# plan/10-intrants.yml : dns_amorcage: <patte de la frontière>
|
# plan/10-intrants.yml : dns_amorcage: <patte de la frontière>
|
||||||
# Sans lui, les machines pointent sur le DNS du site — qui est l'une d'elles.
|
# Sans lui, les machines pointent sur le DNS du site — qui est l'une d'elles.
|
||||||
|
# Et `nftables_admin_ssh: [<plan d'administration>]`, sans quoi l'exploitant ne
|
||||||
|
# pourra pas atteindre ce qu'il vient de construire.
|
||||||
|
|
||||||
# 3. Le déploiement, DEUX FOIS au minimum
|
# 2. Les VM, depuis l'underlay ~9 min
|
||||||
ansible-playbook -i scripts/site_inventaire.py playbooks/site.yml \
|
make site-creer CONFIRMER=true
|
||||||
-e "@$(dirname "$(readlink -f underlay.yml)")/underlay.vault.yml"
|
|
||||||
|
|
||||||
# 4. Entre les deux passages : amorcer la forge
|
# 3. ATTENDRE QU'ELLES REPONDENT — `site-creer` ne le fait pas ~3 min
|
||||||
|
until ansible -i scripts/site_inventaire.py all,'!<hyperviseurs>' -m ping >/dev/null 2>&1
|
||||||
|
do sleep 10; done
|
||||||
|
|
||||||
|
# 4. Passage 1 — va jusqu'à `serveur_ops`, s'arrête sur la forge vide ~20 min
|
||||||
|
V="$(dirname "$(readlink -f underlay.yml)")/underlay.vault.yml"
|
||||||
|
ansible-playbook -i scripts/site_inventaire.py playbooks/site.yml -e "@$V"
|
||||||
|
|
||||||
|
# 5. Amorcer la forge — elle tourne, elle est vide ~45 s
|
||||||
export SETOPS_FORGE_MDP=… # jamais en argument de ligne de commande
|
export SETOPS_FORGE_MDP=… # jamais en argument de ligne de commande
|
||||||
make forge-amorcer CONFIRMER=true
|
make forge-amorcer CONFIRMER=true
|
||||||
|
|
||||||
# 5. Rejouer jusqu'à 0 échec
|
# 6. Passage 2 — doit finir à 0 échec ~4 min
|
||||||
|
ansible-playbook -i scripts/site_inventaire.py playbooks/site.yml -e "@$V"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Total mesuré : 37 min 24** pour sept machines, de rien du tout à un site complet.
|
||||||
|
|
||||||
**La voûte se passe en `-e @`** — `make site-appliquer` la dérive du symlink `underlay.yml`,
|
**La voûte se passe en `-e @`** — `make site-appliquer` la dérive du symlink `underlay.yml`,
|
||||||
mais il n'existe aucune cible qui déploie le site EN ENTIER. Un `ansible-playbook` direct
|
mais il n'existe aucune cible qui déploie le site EN ENTIER. Un `ansible-playbook` direct
|
||||||
l'oublie, et l'échec parle d'une assertion, jamais d'un fichier manquant.
|
l'oublie, et l'échec parle d'une assertion, jamais d'un fichier manquant.
|
||||||
|
|
||||||
**Deux passages, au minimum, et ce n'est pas un contournement.** `client_pki` pose les
|
**Deux passages, et le second ne sert qu'à la forge.** Le premier va jusqu'à la couche 45
|
||||||
droits d'une clé pour un consommateur qu'une couche ultérieure crée : au premier passage le
|
sur 46 ; seul `serveur_ops` manque, faute de génome dans une forge neuve.
|
||||||
groupe `git` n'existe pas, la clé reste fermée, et Forgejo ne démarre qu'au second.
|
|
||||||
|
> **Il en fallait TROIS avant le 2026-09-12.** `client_pki` posait les droits de la clé
|
||||||
|
> d'hôte pour le groupe `git`, que le paquet de Forgejo crée dans une couche postérieure :
|
||||||
|
> au premier passage la clé restait fermée, Forgejo ne démarrait pas — après **300 secondes
|
||||||
|
> d'attente perdue** — et il fallait un passage entier pour qu'il démarre, un autre pour la
|
||||||
|
> forge. Aucun ordre de couches ne dénoue ce cycle ; c'est la **propriété du geste** qui a
|
||||||
|
> changé de main : `serveur_forgejo` revendique désormais la clé au moment où il crée le
|
||||||
|
> groupe. `client_pki` garde la sienne et la repose à chaque passage, parce que `step`
|
||||||
|
> réécrit la clé à chaque renouvellement.
|
||||||
|
|
||||||
### Les quinze murs, et ce que chacun enseigne
|
### Les quinze murs, et ce que chacun enseigne
|
||||||
|
|
||||||
|
|
@ -398,10 +427,12 @@ groupe `git` n'existe pas, la clé reste fermée, et Forgejo ne démarre qu'au s
|
||||||
| 14 | Un flux à deux paires n'obtient qu'une branche | la chaîne de `elif` rangeait `[flotte, admin]` dans un seul cas |
|
| 14 | Un flux à deux paires n'obtient qu'une branche | la chaîne de `elif` rangeait `[flotte, admin]` dans un seul cas |
|
||||||
| 15 | Dépôts créés privés, runner anonyme | `could not read Username` — un message qui pointe ailleurs que sa cause |
|
| 15 | Dépôts créés privés, runner anonyme | `could not read Username` — un message qui pointe ailleurs que sa cause |
|
||||||
| 16 | Le wiki n'existe pas sur une forge neuve | Forgejo ne crée `<dépôt>.wiki.git` qu'à la **première page**, posée à la main dans l'interface |
|
| 16 | Le wiki n'existe pas sur une forge neuve | Forgejo ne crée `<dépôt>.wiki.git` qu'à la **première page**, posée à la main dans l'interface |
|
||||||
|
| 17 | `site-creer` rend la main avant que les machines répondent | mesuré à **3 min 20** — un enchaînement automatique échouerait sur `UNREACHABLE` |
|
||||||
|
| 18 | Les clés d'hôte changent à chaque reconstruction | `accept-new` couvre la première rencontre, **jamais un changement** : `forge-amorcer` purge l'entrée périmée de l'hôte que `git` va contacter |
|
||||||
|
|
||||||
### Le motif
|
### Le motif
|
||||||
|
|
||||||
Onze des seize sont **du code juste en régime établi**, faux le premier jour : un résolveur
|
Douze des dix-huit sont **du code juste en régime établi**, faux le premier jour : un résolveur
|
||||||
qui se pointe sur lui-même, une clé dont le consommateur n'existe pas encore, un répertoire
|
qui se pointe sur lui-même, une clé dont le consommateur n'existe pas encore, un répertoire
|
||||||
créé par une couche ultérieure, une forge vide qu'on croit remplie.
|
créé par une couche ultérieure, une forge vide qu'on croit remplie.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -85,6 +85,34 @@
|
||||||
# read operation timed out ». Un deploiement de flotte echouait parce qu'un serveur tiers
|
# read operation timed out ». Un deploiement de flotte echouait parce qu'un serveur tiers
|
||||||
# etait lent — sur une plateforme qui se veut souveraine, c'est une dependance de trop
|
# etait lent — sur une plateforme qui se veut souveraine, c'est une dependance de trop
|
||||||
# sur le chemin critique.
|
# sur le chemin critique.
|
||||||
|
# LE ROLE QUI CREE LE GROUPE REVENDIQUE LA CLE (2026-09-12).
|
||||||
|
#
|
||||||
|
# `client_pki` (couche `pki_client`) pose les droits de la cle d'hote pour le groupe qui
|
||||||
|
# doit la lire — ici `git`. Mais ce groupe est cree JUSTE AU-DESSUS, dans une couche
|
||||||
|
# POSTERIEURE. Au premier jour d'un site, l'ordre se mord la queue :
|
||||||
|
#
|
||||||
|
# client_pki a besoin du groupe git <- cree par ce role
|
||||||
|
# forgejo a besoin de la cle lisible <- posee par client_pki
|
||||||
|
#
|
||||||
|
# Mesure sur deux reconstructions completes : le passage 1 laisse la cle en `root:root`,
|
||||||
|
# Forgejo ne demarre pas, et l'attente du premier demarrage coute 300 SECONDES pour rien.
|
||||||
|
# Il fallait un passage entier de plus.
|
||||||
|
#
|
||||||
|
# AUCUN ORDRE DE COUCHES NE DENOUE CE CYCLE — les certificats doivent venir tot, tout le
|
||||||
|
# monde en depend. Ce qui se deplace, c'est la PROPRIETE DU GESTE : le role qui cree le
|
||||||
|
# groupe est celui qui peut, a cet instant, donner l'acces.
|
||||||
|
#
|
||||||
|
# `client_pki` garde la sienne et la REPOSE a chaque passage — `step` reecrit la cle a
|
||||||
|
# chaque renouvellement, et ne regler les droits qu'ici les perdrait des le premier.
|
||||||
|
# Les deux taches se recouvrent, et c'est voulu : l'une amorce, l'autre entretient.
|
||||||
|
- name: Donner acces a la cle d'hote au groupe git (amorcage)
|
||||||
|
ansible.builtin.file:
|
||||||
|
path: "{{ client_pki_cle | default('/etc/step/certs/' ~ (ansible_fqdn | default(ansible_hostname)) ~ '.key') }}"
|
||||||
|
owner: root
|
||||||
|
group: "{{ serveur_forgejo_utilisateur }}"
|
||||||
|
mode: "{{ client_pki_cle_mode | default('0640') }}"
|
||||||
|
failed_when: false # pas encore de certificat : `client_pki` passera apres
|
||||||
|
|
||||||
- name: Le binaire de cette version est-il deja pose ?
|
- name: Le binaire de cette version est-il deja pose ?
|
||||||
ansible.builtin.stat:
|
ansible.builtin.stat:
|
||||||
path: "/usr/local/bin/forgejo-{{ serveur_forgejo_version }}"
|
path: "/usr/local/bin/forgejo-{{ serveur_forgejo_version }}"
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,7 @@ import subprocess
|
||||||
import sys
|
import sys
|
||||||
import urllib.error
|
import urllib.error
|
||||||
import urllib.request
|
import urllib.request
|
||||||
|
from urllib.parse import urlsplit
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|
||||||
RACINE = Path(__file__).resolve().parent.parent
|
RACINE = Path(__file__).resolve().parent.parent
|
||||||
|
|
@ -132,6 +133,35 @@ def main() -> int:
|
||||||
f"Relancer avec : make forge-amorcer CONFIRMER=true")
|
f"Relancer avec : make forge-amorcer CONFIRMER=true")
|
||||||
return 0
|
return 0
|
||||||
|
|
||||||
|
# PURGER L'ENTREE PERIMEE DE L'HOTE QU'ON S'APPRETE A CONTACTER (2026-09-12).
|
||||||
|
#
|
||||||
|
# `accept-new` accepte une PREMIERE rencontre, jamais un CHANGEMENT — et c'est bien
|
||||||
|
# ainsi. Mais une forge reconstruite porte une cle d'hote inedite sur une adresse
|
||||||
|
# DEJA CONNUE : ssh refuse, `git` ne peut rien demander sans terminal, et le message
|
||||||
|
# qu'il rend parle de droits d'acces et de depot inexistant. Mesure au tour 2 : un
|
||||||
|
# aller-retour de diagnostic complet pour une cle d'hote.
|
||||||
|
#
|
||||||
|
# CE QUE CA N'AFFAIBLIT PAS. La purge ne vaut que pour l'hote de CETTE forge, au
|
||||||
|
# moment ou l'exploitant amorce une machine qu'il vient de creer lui-meme par une API
|
||||||
|
# authentifiee. On ne touche a aucune autre entree, et `accept-new` reprend ensuite
|
||||||
|
# son role : la cle notee ici sera defendue comme les autres au prochain contact.
|
||||||
|
# LA CIBLE EST L'HOTE QUE `git` VA CONTACTER, pas celui de l'API : quand l'amorcage
|
||||||
|
# passe par un tunnel, `--forge` vaut `127.0.0.1` alors que le push part vers le nom
|
||||||
|
# du remote. Purger le mauvais ne servirait a rien, et on ne s'en apercevrait qu'au
|
||||||
|
# push.
|
||||||
|
hotes_git = set()
|
||||||
|
for _nom, local, _br in prets:
|
||||||
|
url = subprocess.run(["git", "-C", str(local), "remote", "get-url", "origin"],
|
||||||
|
capture_output=True, text=True, timeout=20).stdout.strip()
|
||||||
|
h = urlsplit(url).hostname if "://" in url else (
|
||||||
|
url.split("@", 1)[-1].split(":", 1)[0] if "@" in url else "")
|
||||||
|
if h and h not in ("127.0.0.1", "localhost"):
|
||||||
|
hotes_git.add(h)
|
||||||
|
for cible in sorted(hotes_git):
|
||||||
|
r = subprocess.run(["ssh-keygen", "-R", cible], capture_output=True, text=True, timeout=20)
|
||||||
|
if r.returncode == 0 and "not found" not in (r.stderr or "").lower():
|
||||||
|
print(f" known_hosts : entree perimee de {cible} retiree")
|
||||||
|
|
||||||
mdp = os.environ.get("SETOPS_FORGE_MDP", "")
|
mdp = os.environ.get("SETOPS_FORGE_MDP", "")
|
||||||
if not mdp:
|
if not mdp:
|
||||||
print("\nREFUS : mot de passe de l'administrateur de la forge absent.\n"
|
print("\nREFUS : mot de passe de l'administrateur de la forge absent.\n"
|
||||||
|
|
@ -196,12 +226,36 @@ def main() -> int:
|
||||||
continue
|
continue
|
||||||
# `--force` JAMAIS : si le depot porte deja quelque chose, on veut le savoir,
|
# `--force` JAMAIS : si le depot porte deja quelque chose, on veut le savoir,
|
||||||
# pas l'ecraser. Un amorcage n'a de sens que sur une forge vide.
|
# pas l'ecraser. Un amorcage n'a de sens que sur une forge vide.
|
||||||
|
# LA FORGE EST NEUVE, SA CLE D'HOTE AUSSI (2026-09-12). Une reconstruction donne
|
||||||
|
# a chaque machine une cle d'hote inedite : `known_hosts` porte l'ancienne, `git`
|
||||||
|
# refuse — a juste titre — et sans terminal il ne peut rien demander :
|
||||||
|
#
|
||||||
|
# Host key verification failed.
|
||||||
|
# ssh_askpass: exec(/usr/bin/ssh-askpass): No such file or directory
|
||||||
|
#
|
||||||
|
# `accept-new` ACCEPTE UNE PREMIERE RENCONTRE, JAMAIS UN CHANGEMENT : si une cle
|
||||||
|
# connue differe, ssh refuse toujours. C'est la posture juste ici — l'exploitant
|
||||||
|
# vient de creer cette machine lui-meme, il y a quelques minutes, par une API
|
||||||
|
# authentifiee. Exiger qu'il ait deja vu la cle d'un hote qui n'existait pas
|
||||||
|
# rendrait l'amorcage impossible.
|
||||||
|
#
|
||||||
|
# NE PAS confondre avec `StrictHostKeyChecking=no`, qui avalerait aussi un
|
||||||
|
# changement — c'est-a-dire exactement l'attaque contre laquelle il protege.
|
||||||
|
env = dict(os.environ, GIT_SSH_COMMAND="ssh -o StrictHostKeyChecking=accept-new")
|
||||||
r = subprocess.run(["git", "-C", str(local), "push", "origin", branche],
|
r = subprocess.run(["git", "-C", str(local), "push", "origin", branche],
|
||||||
capture_output=True, text=True, timeout=180)
|
capture_output=True, text=True, timeout=180, env=env)
|
||||||
if r.returncode == 0:
|
if r.returncode == 0:
|
||||||
print(f" {nom:22} pousse ({branche})")
|
print(f" {nom:22} pousse ({branche})")
|
||||||
else:
|
else:
|
||||||
print(f" {nom:22} ECHEC push : {(r.stderr or '').strip().splitlines()[-1][:90]}")
|
# LA DERNIERE LIGNE DE `stderr` EST LA PLUS GENERIQUE (2026-09-12). `git`
|
||||||
|
# termine par « assurez-vous que le depot existe » quelle que soit la cause :
|
||||||
|
# cle d'hote changee, droits, depot absent. N'afficher qu'elle a coute un
|
||||||
|
# aller-retour de diagnostic — on rend les premieres lignes, qui portent le
|
||||||
|
# motif reel.
|
||||||
|
_err = [l for l in (r.stderr or "").strip().splitlines() if l.strip()]
|
||||||
|
print(f" {nom:22} ECHEC push :")
|
||||||
|
for _l in _err[:4]:
|
||||||
|
print(f" {_l[:110]}")
|
||||||
echecs += 1
|
echecs += 1
|
||||||
|
|
||||||
print(f"\n{len(prets) - echecs}/{len(prets)} depot(s) amorces.")
|
print(f"\n{len(prets) - echecs}/{len(prets)} depot(s) amorces.")
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue