[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
|
# © 2026 TechnoLibre (http://www.technolibre.ca)
|
|
|
|
|
# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl)
|
|
|
|
|
"""Jusqu'à quel étage un Proxmox dans un Proxmox tient-il ?
|
|
|
|
|
|
|
|
|
|
Ce n'est pas un test unitaire : il crée de vraies machines et prend des
|
|
|
|
|
HEURES. Il vit donc hors de `test/`, que le lanceur unitaire balaie.
|
|
|
|
|
|
|
|
|
|
Ce qu'il établit, et pourquoi cela valait un script : la profondeur
|
|
|
|
|
d'imbrication praticable ne se déduit pas, elle se mesure. Une mesure à la
|
|
|
|
|
main a montré, au quatrième étage, un invité 36 fois plus lent que le temps
|
|
|
|
|
réel — 583 secondes d'horloge pour 16 secondes de temps invité — puis un noyau
|
|
|
|
|
gelé au MÊME octet quelles que soient les ressources. Un chiffre obtenu une
|
|
|
|
|
fois, sur une machine, n'est pas un chiffre : ce script le refait à la demande
|
|
|
|
|
et dit exactement OÙ ça casse.
|
|
|
|
|
|
|
|
|
|
La descente est UNIFORME. Chaque étage, le premier compris, passe par les
|
|
|
|
|
mêmes six étapes : créer, attendre le ssh, installer Proxmox, redémarrer et
|
|
|
|
|
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage. Seule la
|
|
|
|
|
création diffère — libvirt en local, « qm » ensuite.
|
|
|
|
|
|
|
|
|
|
Il envoie NOTRE install_proxmox.sh par scp au lieu de laisser la VM cloner le
|
|
|
|
|
dépôt : c'est notre code qu'on veut éprouver, et le dépôt distant est souvent
|
|
|
|
|
en retard sur le checkout — un correctif absent du distant a fait « revenir »
|
|
|
|
|
le même défaut sur trois VM de suite.
|
|
|
|
|
|
|
|
|
|
./LongTest/deep_proxmox.py --depth 10 --dry-run
|
|
|
|
|
./LongTest/deep_proxmox.py --depth 10
|
|
|
|
|
./LongTest/deep_proxmox.py --detruire # défait ce que la descente a posé
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import argparse
|
|
|
|
|
import json
|
|
|
|
|
import os
|
|
|
|
|
import re
|
|
|
|
|
import shlex
|
|
|
|
|
import subprocess
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
RACINE = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
|
|
|
|
sys.path.insert(0, RACINE)
|
|
|
|
|
|
|
|
|
|
from script.proxmox import nesting # noqa: E402
|
|
|
|
|
from script.proxmox import proxmox_deploy as pve # noqa: E402
|
|
|
|
|
|
|
|
|
|
# L'image des étages imbriqués. Debian parce que install_proxmox.sh s'installe
|
|
|
|
|
# SUR une Debian — Proxmox ne publie pas d'image cloud.
|
|
|
|
|
DISTRO = "proxmox"
|
|
|
|
|
NOM_BASE = "deep-pve"
|
|
|
|
|
|
|
|
|
|
# Une étape bloquée ne doit pas bloquer le test : chaque appel est borné, et le
|
|
|
|
|
# journal dit lequel a expiré. Généreux, parce que chaque étage est plus lent
|
|
|
|
|
# que le précédent — c'est précisément ce qu'on mesure.
|
|
|
|
|
DELAIS = {
|
|
|
|
|
"creation": 1200,
|
|
|
|
|
"ssh": 2400,
|
|
|
|
|
"install": 7200,
|
|
|
|
|
"reboot": 2400,
|
|
|
|
|
"reparation": 600,
|
|
|
|
|
"controle": 180,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def dire(msg, journal=None):
|
|
|
|
|
ligne = f"[{time.strftime('%H:%M:%S')}] {msg}"
|
|
|
|
|
print(ligne, flush=True)
|
|
|
|
|
if journal:
|
|
|
|
|
with open(journal, "a", encoding="utf-8") as fh:
|
|
|
|
|
fh.write(ligne + "\n")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def capacite_hote():
|
|
|
|
|
"""(cœurs, RAM disponible en Mo, disque libre en Go) de la machine réelle.
|
|
|
|
|
|
|
|
|
|
« available » et non « free » : c'est ce que le noyau promet de rendre sans
|
|
|
|
|
mettre la machine à genoux.
|
|
|
|
|
"""
|
|
|
|
|
coeurs = os.cpu_count() or 2
|
|
|
|
|
ram = 0
|
|
|
|
|
try:
|
|
|
|
|
with open("/proc/meminfo", encoding="utf-8") as fh:
|
|
|
|
|
for ligne in fh:
|
|
|
|
|
if ligne.startswith("MemAvailable:"):
|
|
|
|
|
ram = int(ligne.split()[1]) // 1024
|
|
|
|
|
break
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
disque = 0
|
|
|
|
|
try:
|
|
|
|
|
st = os.statvfs("/var/lib/libvirt/images")
|
|
|
|
|
disque = (st.f_bavail * st.f_frsize) // (1024**3)
|
|
|
|
|
except OSError:
|
|
|
|
|
pass
|
|
|
|
|
return coeurs, ram, disque
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def module_qemu():
|
|
|
|
|
"""deploy_qemu.py chargé comme module : il porte le catalogue d'images."""
|
|
|
|
|
import importlib.util
|
|
|
|
|
|
|
|
|
|
chemin = os.path.join(RACINE, "script/qemu/deploy_qemu.py")
|
|
|
|
|
spec = importlib.util.spec_from_file_location("deploy_qemu", chemin)
|
|
|
|
|
mod = importlib.util.module_from_spec(spec)
|
|
|
|
|
spec.loader.exec_module(mod)
|
|
|
|
|
return mod
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def cle_publique():
|
|
|
|
|
for nom in ("id_ed25519.pub", "id_rsa.pub"):
|
|
|
|
|
chemin = os.path.expanduser(f"~/.ssh/{nom}")
|
|
|
|
|
if os.path.exists(chemin):
|
|
|
|
|
return chemin
|
|
|
|
|
return ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def nom_etage(niveau):
|
|
|
|
|
return f"{NOM_BASE}-{niveau}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def alias_etage(niveau, parent_alias):
|
|
|
|
|
"""« parent+enfant », la convention du dépôt : elle dit où la machine vit
|
|
|
|
|
et ne peut rien voler à un homonyme."""
|
|
|
|
|
if not parent_alias:
|
|
|
|
|
return nom_etage(niveau)
|
|
|
|
|
court = re.sub(r"[^A-Za-z0-9._-]", "-", parent_alias)
|
|
|
|
|
return f"{court}+{nom_etage(niveau)}"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Descente:
|
|
|
|
|
"""Un étage après l'autre, et ce qu'on en sait."""
|
|
|
|
|
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
def __init__(self, plan, journal, dry_run=False, chemin_json=None):
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
self.plan = plan
|
|
|
|
|
self.journal = journal
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
self.chemin_json = chemin_json
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
self.dry_run = dry_run
|
|
|
|
|
self.etages = []
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
self.interrompu = False
|
|
|
|
|
self.niveau_courant = 1
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
|
|
|
|
def dire(self, msg):
|
|
|
|
|
dire(msg, self.journal)
|
|
|
|
|
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
def delai(self, etape):
|
|
|
|
|
"""Le délai de cette étape, à l'étage courant.
|
|
|
|
|
|
|
|
|
|
Constant, il contredisait la raison d'être du script : au quatrième
|
|
|
|
|
étage un invité tournait 36 fois moins vite. Une installation de dix
|
|
|
|
|
minutes au premier étage en demande des heures au quatrième, et le
|
|
|
|
|
plafond fixe la déclarait échouée — en concluant à un mur
|
|
|
|
|
d'imbrication là où il n'y avait qu'un délai trop court.
|
|
|
|
|
|
|
|
|
|
Le facteur est CARRÉ et borné : chaque étage ajoute une couche
|
|
|
|
|
d'hyperviseur à traverser, mais un facteur illimité rendrait un
|
|
|
|
|
échec réel indiscernable d'une attente sans fin.
|
|
|
|
|
"""
|
|
|
|
|
facteur = min(max(1, self.niveau_courant), 5) ** 2
|
|
|
|
|
return DELAIS[etape] * facteur
|
|
|
|
|
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
# ---------------------------------------------------------------- #
|
|
|
|
|
# Parler aux machines
|
|
|
|
|
# ---------------------------------------------------------------- #
|
|
|
|
|
def executer(self, hote, remote, delai, etiquette, montrer=False):
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
argv = pve.ssh_argv(
|
|
|
|
|
hote, pve.wrap_privilege(remote, hote.get("sudo") or "")
|
|
|
|
|
)
|
|
|
|
|
print(" " + " ".join(shlex.quote(a) for a in argv)[:200])
|
|
|
|
|
return 0, ""
|
|
|
|
|
debut = time.time()
|
|
|
|
|
code, sortie = pve.run(hote, remote, delai)
|
|
|
|
|
if code or montrer:
|
|
|
|
|
self.dire(
|
|
|
|
|
f" {etiquette} : code {code}"
|
|
|
|
|
f" en {int(time.time() - debut)} s"
|
|
|
|
|
)
|
|
|
|
|
if code:
|
|
|
|
|
for ligne in pve.strip_ssh_noise(sortie).strip().splitlines()[-5:]:
|
|
|
|
|
self.dire(f" {ligne}")
|
|
|
|
|
return code, sortie
|
|
|
|
|
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
def attendre_ssh(self, hote, delai, parent=None):
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
"""Attend que la machine réponde. Rend les secondes, ou None.
|
|
|
|
|
|
|
|
|
|
Des connexions COURTES successives : cloud-init régénère les clés
|
|
|
|
|
d'hôte et redémarre sshd au premier démarrage, ce qui tuerait une
|
|
|
|
|
session longue.
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
|
|
|
|
|
`parent` : si l'hôte qui HÉBERGE la machine attendue cesse de
|
|
|
|
|
répondre, on abandonne tout de suite. Constaté : l'étage 1 a redémarré
|
|
|
|
|
pendant l'installation de l'étage 4, ce qui a éteint les étages 2, 3 et
|
|
|
|
|
4 d'un coup ; la descente a attendu son délai entier — quarante
|
|
|
|
|
minutes — un ssh qui ne pouvait plus aboutir, puis a rendu « jamais
|
|
|
|
|
joignable en ssh ». Le diagnostic était faux : la machine n'était pas
|
|
|
|
|
lente, sa MAISON n'existait plus.
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
"""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
return 0
|
|
|
|
|
debut = time.time()
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# SANS privilège : wrap_privilege transformerait « true » en
|
|
|
|
|
# « sudo sh -c true », et un sudo qui réclame un mot de passe — le
|
|
|
|
|
# temps que cloud-init écrive /etc/sudoers.d — se lisait « jamais
|
|
|
|
|
# joignable en ssh ». Le transport marchait ; c'est le diagnostic qui
|
|
|
|
|
# était faux.
|
|
|
|
|
sonde = dict(hote, sudo="")
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
sonde_parent = dict(parent, sudo="") if parent else None
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
while time.time() - debut < delai:
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
code, _o = pve.run(sonde, "true", 60)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
if code == 0:
|
|
|
|
|
return int(time.time() - debut)
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
if sonde_parent is not None:
|
|
|
|
|
code_parent, _p = pve.run(sonde_parent, "true", 60)
|
|
|
|
|
if code_parent != 0:
|
|
|
|
|
self.dire(
|
|
|
|
|
f" ✗ l'hôte {sonde_parent['target']} ne répond"
|
|
|
|
|
" plus : l'attente n'aboutira pas"
|
|
|
|
|
)
|
|
|
|
|
return None
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
time.sleep(15)
|
|
|
|
|
return None
|
|
|
|
|
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
def sudo_pret(self, hote):
|
|
|
|
|
"""sudo répond-il sans mot de passe ? Nommé à part de ssh."""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
return True
|
|
|
|
|
code, _o = pve.run(hote, "true", 60)
|
|
|
|
|
return code == 0
|
|
|
|
|
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
# ---------------------------------------------------------------- #
|
|
|
|
|
# Les six étapes, les mêmes à chaque étage
|
|
|
|
|
# ---------------------------------------------------------------- #
|
|
|
|
|
def installer_proxmox(self, hote):
|
|
|
|
|
"""Envoie NOTRE script et l'exécute. Rend True si Proxmox est posé."""
|
|
|
|
|
local = os.path.join(RACINE, "script/proxmox/install_proxmox.sh")
|
|
|
|
|
distant = "/tmp/install_proxmox.sh"
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(f" scp {local} <hôte>:{distant}")
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
print(f" bash {distant}")
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
return True
|
|
|
|
|
argv = pve.ssh_argv(hote, "")[:-1] # les options, sans la commande
|
|
|
|
|
cible = argv[-1]
|
|
|
|
|
options = argv[1:-1]
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
["scp", "-q"] + options + [local, f"{cible}:{distant}"],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
timeout=300,
|
|
|
|
|
)
|
|
|
|
|
if res.returncode:
|
|
|
|
|
self.dire(f" ✗ scp : {res.stderr.strip()[:200]}")
|
|
|
|
|
return False
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# « bash » et non « sh » : le script porte « set -euo pipefail » et un
|
|
|
|
|
# shebang bash. Sur Debian /bin/sh est dash, qui répond « set: Illegal
|
|
|
|
|
# option -o pipefail » et sort à la PREMIÈRE ligne — vérifié. Chaque
|
|
|
|
|
# étage aurait échoué sur l'installation, à tous les coups.
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
code, _o = self.executer(
|
|
|
|
|
dict(hote, sudo=""),
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
f"bash {distant}",
|
|
|
|
|
self.delai("install"),
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
"install_proxmox.sh",
|
|
|
|
|
montrer=True,
|
|
|
|
|
)
|
|
|
|
|
return code == 0
|
|
|
|
|
|
|
|
|
|
def redemarrer_et_verifier(self, hote):
|
|
|
|
|
"""Redémarre, attend le retour, exige le noyau Proxmox.
|
|
|
|
|
|
|
|
|
|
Le script pose le noyau sans redémarrer — lancé par ssh, un reboot
|
|
|
|
|
couperait sa session et ferait passer l'installation pour un échec.
|
|
|
|
|
Sans ce redémarrage, la machine reste sur le noyau cloud de Debian,
|
|
|
|
|
dépouillé de tout netfilter : ni pont NAT, ni invité.
|
|
|
|
|
"""
|
|
|
|
|
if self.dry_run:
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
print(" reboot, puis btime changé ET *-pve dans uname -r")
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
return True
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# L'instant de démarrage AVANT : le noyau seul ne prouve rien. Rejoué
|
|
|
|
|
# sur un étage déjà installé, le script est idempotent et ne redémarre
|
|
|
|
|
# pas ; vingt secondes après l'ordre, sshd répond encore et la machine
|
|
|
|
|
# tourne DÉJÀ sur -pve. On validait donc un redémarrage qui n'avait pas
|
|
|
|
|
# eu lieu, et l'étape suivante tombait sur une machine en train de
|
|
|
|
|
# s'éteindre — avec un diagnostic sans rapport. Même piège que celui
|
|
|
|
|
# corrigé dans le suivi d'installation, refait ici.
|
|
|
|
|
_c, out = pve.run(dict(hote, sudo=""), "stat -c %Y /proc/1", 60)
|
|
|
|
|
avant = pve.strip_ssh_noise(out).strip()
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
pve.run(hote, "systemctl reboot", 60)
|
|
|
|
|
debut = time.time()
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
while time.time() - debut < self.delai("reboot"):
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
time.sleep(20)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
code, out = pve.run(
|
|
|
|
|
dict(hote, sudo=""), "uname -r; stat -c %Y /proc/1", 60
|
|
|
|
|
)
|
|
|
|
|
lignes = pve.strip_ssh_noise(out).strip().splitlines()
|
|
|
|
|
if code or len(lignes) < 2:
|
|
|
|
|
continue
|
|
|
|
|
noyau, apres = lignes[0].strip(), lignes[-1].strip()
|
|
|
|
|
if "-pve" not in noyau:
|
|
|
|
|
continue
|
|
|
|
|
if avant and apres == avant:
|
|
|
|
|
continue # elle n'a pas encore redémarré
|
|
|
|
|
self.dire(
|
|
|
|
|
f" noyau {noyau} après {int(time.time() - debut)} s"
|
|
|
|
|
)
|
|
|
|
|
return True
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
self.dire(" ✗ pas revenue sur un noyau -pve")
|
|
|
|
|
return False
|
|
|
|
|
|
|
|
|
|
def reparer_pmxcfs(self, hote):
|
|
|
|
|
"""Gel de cloud-init, /etc/hosts, unités, constat du montage."""
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(" gel cloud-init + /etc/hosts + unités + montage")
|
|
|
|
|
return True
|
|
|
|
|
_c, out = pve.run(
|
|
|
|
|
dict(hote, sudo=""), 'printf %s "$SSH_CONNECTION"', 30
|
|
|
|
|
)
|
|
|
|
|
ip = pve.ssh_server_ip(out)
|
|
|
|
|
if not ip:
|
|
|
|
|
self.dire(" ✗ adresse d'accès inconnue")
|
|
|
|
|
return False
|
|
|
|
|
for cmd, etiquette in (
|
|
|
|
|
(pve.cloud_hosts_freeze_cmd(), "gel cloud-init"),
|
|
|
|
|
(pve.hosts_repair_cmd(ip), "/etc/hosts"),
|
|
|
|
|
):
|
|
|
|
|
code, sortie = self.executer(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
hote, cmd, self.delai("reparation"), etiquette
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
if code or "-KO" in pve.strip_ssh_noise(sortie):
|
|
|
|
|
self.dire(f" ✗ {etiquette}")
|
|
|
|
|
return False
|
|
|
|
|
for unite in pve.PVE_UNITS:
|
|
|
|
|
self.executer(
|
|
|
|
|
hote, pve.pve_unit_cmd(unite, remonte=True), 300, unite
|
|
|
|
|
)
|
|
|
|
|
_c, out = self.executer(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
hote, pve.mount_wait_cmd(), self.delai("reparation"), "montage"
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
vu = pve.parse_mount_wait(out)
|
|
|
|
|
self.dire(f" /etc/pve : {vu['verdict']}")
|
|
|
|
|
return vu["verdict"] == "MONTE"
|
|
|
|
|
|
|
|
|
|
def preparer_parent(self, parent):
|
|
|
|
|
"""Stockage, pont et réseau interne du parent, ou None."""
|
|
|
|
|
_c, out = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
"pvesm status --content images",
|
|
|
|
|
DELAIS["controle"],
|
|
|
|
|
"pvesm",
|
|
|
|
|
)
|
|
|
|
|
stockage = pve.pick_storage(pve.parse_storages(out))
|
|
|
|
|
if not stockage and not self.dry_run:
|
|
|
|
|
self.dire(" ✗ aucun stockage sur le parent")
|
|
|
|
|
return None
|
|
|
|
|
_c, out = self.executer(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
parent,
|
|
|
|
|
"ip -o link show type bridge",
|
|
|
|
|
self.delai("controle"),
|
|
|
|
|
"ponts",
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
ponts = pve.parse_bridges(out)
|
|
|
|
|
if not ponts:
|
|
|
|
|
_c, nets = self.executer(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
parent, pve.USED_NETS_CMD, self.delai("controle"), "réseaux"
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
cidr = pve.pick_internal_cidr(nets) or pve.INTERNAL_CIDR
|
|
|
|
|
_c, rt = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
"ip -o -4 route show default",
|
|
|
|
|
DELAIS["controle"],
|
|
|
|
|
"uplink",
|
|
|
|
|
)
|
|
|
|
|
trouve = re.search(r"dev\s+(\S+)", rt or "")
|
|
|
|
|
uplink = trouve.group(1) if trouve else ""
|
|
|
|
|
self.dire(f" pont {cidr}, NAT par {uplink or '—'}")
|
|
|
|
|
for cmd in pve.bridge_setup_cmds(cidr=cidr, uplink=uplink):
|
|
|
|
|
code, _o = self.executer(
|
|
|
|
|
parent, cmd, DELAIS["reparation"], "pont"
|
|
|
|
|
)
|
|
|
|
|
if code and not self.dry_run:
|
|
|
|
|
return None
|
|
|
|
|
ponts = [pve.INTERNAL_BRIDGE]
|
|
|
|
|
_c, cfg = self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
"cat /etc/network/interfaces",
|
|
|
|
|
DELAIS["controle"],
|
|
|
|
|
"interfaces",
|
|
|
|
|
)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# Le DNS de l'hôte. « --ipconfig0 » ne le porte PAS : une VM en
|
|
|
|
|
# adresse fixe route mais ne résout rien, et install_proxmox.sh meurt
|
|
|
|
|
# sur « apt update » sans que rien ne l'explique. Le rapport imputerait
|
|
|
|
|
# à l'installation ce qui est un défaut de résolveur.
|
|
|
|
|
_c, resolv = self.executer(
|
|
|
|
|
parent, pve.RESOLV_CMD, self.delai("controle"), "resolv"
|
|
|
|
|
)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
return (
|
|
|
|
|
stockage or "local",
|
|
|
|
|
ponts[0],
|
|
|
|
|
pve.parse_bridge_config(cfg).get(ponts[0], {}),
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
pve.parse_nameservers(resolv),
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
|
|
|
|
|
def creer_etage1(self, res):
|
|
|
|
|
"""Une VM locale, par la CLI QEMU/KVM."""
|
|
|
|
|
nom = nom_etage(1)
|
|
|
|
|
argv = [
|
|
|
|
|
os.path.join(RACINE, ".venv.erplibre/bin/python"),
|
|
|
|
|
os.path.join(RACINE, "script/qemu/deploy_qemu.py"),
|
|
|
|
|
"--distro",
|
|
|
|
|
DISTRO,
|
|
|
|
|
"--name",
|
|
|
|
|
nom,
|
|
|
|
|
"--vcpus",
|
|
|
|
|
str(res["vcpu"]),
|
|
|
|
|
"--memory",
|
|
|
|
|
str(res["ram"]),
|
|
|
|
|
"--disk-size",
|
|
|
|
|
f"{res['disque']}G",
|
|
|
|
|
]
|
|
|
|
|
pub = cle_publique()
|
|
|
|
|
if pub:
|
|
|
|
|
argv += ["--ssh-key", pub]
|
|
|
|
|
if self.dry_run:
|
|
|
|
|
print(" " + " ".join(shlex.quote(a) for a in argv))
|
|
|
|
|
return nom
|
|
|
|
|
res_proc = subprocess.run(argv, timeout=DELAIS["creation"] * 3)
|
|
|
|
|
if res_proc.returncode:
|
|
|
|
|
self.dire(" ✗ la CLI QEMU/KVM a échoué")
|
|
|
|
|
return None
|
[FIX] proxmox : apt-daily tient le verrou au démarrage
Trois pannes trouvées en LANÇANT la descente, aucune vue en la relisant — ni
par moi, ni par l'attaque adversariale.
Le premier « apt update » d'une image cloud échoue sur un verrou qui n'est pas
celui qu'on croit. Mesuré une seconde après le premier ssh :
E: Could not get lock /var/lib/apt/lists/lock.
It is held by process 1026 (apt-get)
Ce n'est pas cloud-init — « status --wait » avait rendu la main. C'est
apt-daily, le minuteur de Debian, qui se déclenche au démarrage. Et le verrou
des LISTES n'est pas couvert par « DPkg::Lock::Timeout », que l'installeur
réglait pourtant déjà à 600 s : cette attente ne vaut que pour dpkg. On arrête
donc les minuteurs, puis on RÉESSAIE — arrêter une unité n'interrompt pas
l'apt-get déjà en vol. Le défaut touchait tout déploiement Proxmox, pas
seulement ce test.
Le premier étage n'avait pas d'alias ssh. « deploy_qemu.py » en ligne de
commande n'écrit pas d'entrée ~/.ssh/config — le menu le fait, la CLI non. La
descente aurait attendu son plein délai avant de conclure « jamais joignable »
sur une VM qui répondait à son adresse. Elle l'écrit maintenant elle-même,
depuis l'adresse résolue, et refuse d'avancer si la VM n'en a pas.
Et la réserve de l'hôte est proportionnelle. Quatre gigaoctets sur une machine
de soixante, c'était 6 % laissés au système : le jour où les invités touchent
vraiment leur mémoire, c'est l'hôte qui part en swap — et la mesure serait
celle du swap, pas de l'imbrication. Un huitième, avec le plancher d'avant
pour les petites machines.
Ce que la descente a établi en trois étages : 392 s, 644 s, 1120 s, soit 1,7
fois par étage. Puis la poignée de main ssh passe de 77 à 1664 secondes au
quatrième — vingt fois d'un seul cran. Le coude est là.
Et le mur que j'avais pris pour une limite d'imbrication n'en était pas une.
La VM qui gelait au quatrième étage avait douze vCPU ; celle-ci en a deux et
elle passe, en écrivant. C'était une limite de parallélisme SOUS imbrication —
exactement ce que l'algorithme borne, vérifié pour la première fois plutôt que
supposé.
--- EN ---
Three faults found by RUNNING the descent, none seen by reading it — neither
by me nor by the adversarial attack.
A cloud image's first "apt update" fails on a lock that is not the one you
expect. Measured one second after the first ssh:
E: Could not get lock /var/lib/apt/lists/lock.
It is held by process 1026 (apt-get)
It is not cloud-init — "status --wait" had returned. It is apt-daily, Debian's
timer, firing at boot. And the LISTS lock is not covered by
"DPkg::Lock::Timeout", which the installer already set to 600 s: that wait
only applies to dpkg. So we stop the timers, then RETRY — stopping a unit does
not interrupt the apt-get already in flight. The defect affected every Proxmox
deployment, not just this test.
The first level had no ssh alias. "deploy_qemu.py" on the command line does not
write a ~/.ssh/config entry — the menu does, the CLI does not. The descent
would have waited its full timeout before concluding "never reachable" about a
VM answering at its address. It now writes the entry itself, from the resolved
address, and refuses to proceed if the VM has none.
And the host's reserve is proportional. Four gigabytes on a sixty-gigabyte
machine left 6 % to the system: the day the guests really touch their memory,
the host swaps — and the measurement would be of swap, not of nesting. One
eighth now, keeping the old floor for small machines.
What the descent established over three levels: 392 s, 644 s, 1120 s — 1.7x per
level. Then the ssh handshake goes from 77 to 1664 seconds at the fourth:
twenty times in one step. That is the elbow.
And the wall I had taken for a nesting limit was not one. The VM that froze at
the fourth level had twelve vCPU; this one has two and it gets through,
writing. It was a limit of parallelism UNDER nesting — exactly what the
algorithm caps, verified for the first time rather than assumed.
Assisted-by: Claude Opus 5
(cherry picked from commit 7f86562cbd8f10017dcb88fe4272efc162cbccbc)
2026-08-27 04:27:50 -04:00
|
|
|
# L'entrée ~/.ssh/config, que la CLI n'écrit PAS. Sans elle,
|
|
|
|
|
# « ssh deep-pve-1 » rend « Name or service not known » et la descente
|
|
|
|
|
# attendait son plein délai avant de conclure « jamais joignable » —
|
|
|
|
|
# sur une VM qui répondait parfaitement à son adresse. Vécu au premier
|
|
|
|
|
# lancement réel.
|
|
|
|
|
from script.todo.todo import TODO
|
|
|
|
|
|
|
|
|
|
todo = TODO.__new__(TODO)
|
|
|
|
|
ip = todo._qemu_vm_ip_now(nom)
|
|
|
|
|
if not ip:
|
|
|
|
|
self.dire(f" ✗ {nom} créée mais sans adresse")
|
|
|
|
|
return None
|
|
|
|
|
self.dire(f" {nom} : {ip}")
|
|
|
|
|
prive = cle_publique()[:-4] if cle_publique() else None
|
|
|
|
|
todo._write_ssh_config_entry(
|
|
|
|
|
[nom], "erplibre", ip, identity_file=prive
|
|
|
|
|
)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
return nom
|
|
|
|
|
|
|
|
|
|
def creer_enfant(self, parent, niveau, res, prepare):
|
|
|
|
|
"""« qm create » sur le parent. Rend (vmid, adresse) ou (None, None)."""
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
stockage, pont, info_pont, dns = prepare
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
mod = module_qemu()
|
|
|
|
|
version = mod.DISTROS[DISTRO][1]
|
|
|
|
|
code_img = mod.DISTROS[DISTRO][0][version][0]
|
|
|
|
|
url = mod.image_url(DISTRO, code_img, "amd64", version)
|
|
|
|
|
image = mod.default_image_name(DISTRO, code_img, "amd64", version)
|
|
|
|
|
_c, out = self.executer(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
parent, "qm list", self.delai("controle"), "qm list"
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
vmid = pve.next_vmid(pve.parse_qm_list(out))
|
|
|
|
|
ipconfig = pve.ipconfig_for(info_pont, vmid)
|
|
|
|
|
adresse = pve.ip_from_ipconfig(ipconfig)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# AVANT de télécharger l'image et de démarrer quoi que ce soit : sur un
|
|
|
|
|
# pont relié au LAN, ipconfig_for rend « ip=dhcp » et l'adresse est
|
|
|
|
|
# vide. Le contrôle venait après la création : on laissait une VM
|
|
|
|
|
# allumée, un disque alloué, et une machine que --detruire ne
|
|
|
|
|
# connaissait pas.
|
|
|
|
|
if not adresse and self.dry_run:
|
|
|
|
|
# En essai à blanc on n'a rien lu du parent : conclure « pas de
|
|
|
|
|
# pont interne » serait une affirmation tirée d'une mesure qui
|
|
|
|
|
# n'a pas eu lieu. On prend une adresse plausible pour dérouler
|
|
|
|
|
# le plan jusqu'au bout.
|
|
|
|
|
adresse = "10.10.10.150"
|
|
|
|
|
ipconfig = f"ip={adresse}/24,gw=10.10.10.1"
|
|
|
|
|
if not adresse:
|
|
|
|
|
self.dire(" ✗ pas d'adresse fixe : le parent n'a pas de")
|
|
|
|
|
self.dire(" pont interne, et l'enfant serait injoignable")
|
|
|
|
|
return None, None
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
spec = {
|
|
|
|
|
"name": nom_etage(niveau),
|
|
|
|
|
"storage": stockage,
|
|
|
|
|
"image": image,
|
|
|
|
|
"memory": res["ram"],
|
|
|
|
|
"vcpus": res["vcpu"],
|
|
|
|
|
"bridge": pont,
|
|
|
|
|
"disk": f"{res['disque']}G",
|
|
|
|
|
"user": "erplibre",
|
|
|
|
|
"ipconfig": ipconfig,
|
|
|
|
|
"sshkey_path": "/root/.ssh/longtest.pub",
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
"nameservers": dns,
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
"start": True,
|
|
|
|
|
}
|
|
|
|
|
# La clé publique doit être un FICHIER sur le parent : « --sshkeys »
|
|
|
|
|
# n'accepte pas la clé en ligne.
|
|
|
|
|
pub = cle_publique()
|
|
|
|
|
if pub and not self.dry_run:
|
|
|
|
|
with open(pub, encoding="utf-8") as fh:
|
|
|
|
|
contenu = fh.read().strip()
|
|
|
|
|
self.executer(
|
|
|
|
|
parent,
|
|
|
|
|
f"mkdir -p /root/.ssh && printf '%s\\n'"
|
|
|
|
|
f" {shlex.quote(contenu)} > /root/.ssh/longtest.pub",
|
|
|
|
|
DELAIS["controle"],
|
|
|
|
|
"clé",
|
|
|
|
|
)
|
|
|
|
|
for cmd in [pve.image_fetch_cmd(url, image)] + pve.create_cmds(
|
|
|
|
|
vmid, spec
|
|
|
|
|
):
|
|
|
|
|
code, _o = self.executer(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
parent, cmd, self.delai("creation"), "qm create"
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
if code and not self.dry_run:
|
|
|
|
|
return None, None
|
|
|
|
|
return vmid, adresse
|
|
|
|
|
|
|
|
|
|
# ---------------------------------------------------------------- #
|
|
|
|
|
# La descente
|
|
|
|
|
# ---------------------------------------------------------------- #
|
|
|
|
|
def parcourir(self):
|
|
|
|
|
parent = None
|
|
|
|
|
parent_alias = ""
|
|
|
|
|
for res in self.plan["niveaux"]:
|
|
|
|
|
niveau = res["niveau"]
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
self.niveau_courant = niveau
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
debut = time.time()
|
|
|
|
|
etage = {
|
|
|
|
|
"niveau": niveau,
|
|
|
|
|
"ressources": res,
|
|
|
|
|
"etape": "creation",
|
|
|
|
|
"ok": False,
|
|
|
|
|
}
|
|
|
|
|
self.dire(
|
|
|
|
|
f" ── étage {niveau} : {res['vcpu']} vCPU,"
|
|
|
|
|
f" {res['ram']} Mo, {res['disque']} Go"
|
|
|
|
|
)
|
|
|
|
|
if niveau == 1:
|
|
|
|
|
nom = self.creer_etage1(res)
|
|
|
|
|
if not nom:
|
|
|
|
|
self.etages.append(etage)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
self.interrompu = True
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
break
|
|
|
|
|
alias = nom
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
# Le domaine libvirt existe : le rapport doit exister aussi.
|
|
|
|
|
self._sauver(etage)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
else:
|
|
|
|
|
prepare = self.preparer_parent(parent)
|
|
|
|
|
if not prepare:
|
|
|
|
|
etage["etape"] = "parent"
|
|
|
|
|
self.etages.append(etage)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
self.interrompu = True
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
break
|
|
|
|
|
vmid, adresse = self.creer_enfant(parent, niveau, res, prepare)
|
|
|
|
|
if vmid is None:
|
|
|
|
|
self.etages.append(etage)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
self.interrompu = True
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
break
|
|
|
|
|
etage["vmid"] = vmid
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# Le parent est noté AVANT tout autre contrôle : c'est le seul
|
|
|
|
|
# enregistrement de ce qu'on vient de créer, et --detruire s'en
|
|
|
|
|
# sert. Sans lui, une VM abandonnée juste après « qm create »
|
|
|
|
|
# n'était nommée nulle part.
|
|
|
|
|
etage["parent_alias"] = parent_alias
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
self._sauver(etage)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
alias = alias_etage(niveau, parent_alias)
|
|
|
|
|
if not self.dry_run:
|
|
|
|
|
self.ecrire_alias(alias, adresse, parent_alias)
|
|
|
|
|
cible = {"target": alias, "sudo": "sudo ", "jump": ""}
|
|
|
|
|
etage["alias"] = alias
|
|
|
|
|
|
|
|
|
|
etage["etape"] = "ssh"
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
attente = self.attendre_ssh(cible, self.delai("ssh"), parent)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
if attente is None:
|
|
|
|
|
self.dire(" ✗ jamais joignable en ssh")
|
|
|
|
|
self.etages.append(etage)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
self.interrompu = True
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
break
|
|
|
|
|
etage["ssh_secondes"] = attente
|
|
|
|
|
self.dire(f" ssh après {attente} s")
|
|
|
|
|
|
|
|
|
|
for etape, action in (
|
|
|
|
|
("install", lambda: self.installer_proxmox(cible)),
|
|
|
|
|
("reboot", lambda: self.redemarrer_et_verifier(cible)),
|
|
|
|
|
("pmxcfs", lambda: self.reparer_pmxcfs(cible)),
|
|
|
|
|
):
|
|
|
|
|
etage["etape"] = etape
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
self._sauver(etage)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
if not action():
|
|
|
|
|
self.etages.append(etage)
|
|
|
|
|
return self.rapport(interrompu=True)
|
|
|
|
|
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# En dry-run, aucune étape n'a été mesurée : les marquer
|
|
|
|
|
# « atteintes » produisait un rapport indiscernable d'une vraie
|
|
|
|
|
# réussite, JSON compris, et un code de sortie 0.
|
|
|
|
|
etage["etape"] = "plan" if self.dry_run else "termine"
|
|
|
|
|
etage["ok"] = not self.dry_run
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
etage["secondes"] = int(time.time() - debut)
|
|
|
|
|
self.etages.append(etage)
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
self._sauver()
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
self.dire(f" ✓ étage {niveau} en {etage['secondes']} s")
|
|
|
|
|
parent, parent_alias = cible, alias
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
return self.rapport(interrompu=self.interrompu)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
|
|
|
|
def ecrire_alias(self, alias, adresse, parent_alias):
|
|
|
|
|
"""Une entrée ~/.ssh/config pour joindre l'enfant à travers le parent."""
|
|
|
|
|
from script.todo.todo import TODO
|
|
|
|
|
|
|
|
|
|
todo = TODO.__new__(TODO)
|
|
|
|
|
prive = cle_publique()[:-4] if cle_publique() else None
|
|
|
|
|
todo._write_ssh_config_entry(
|
|
|
|
|
[alias],
|
|
|
|
|
"erplibre",
|
|
|
|
|
adresse,
|
|
|
|
|
proxy_jump=parent_alias or None,
|
|
|
|
|
identity_file=prive,
|
|
|
|
|
)
|
|
|
|
|
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
def _etat(self, interrompu, en_cours=None):
|
|
|
|
|
"""Le rapport, à cet instant. `en_cours` : l'étage pas encore rangé."""
|
|
|
|
|
etages = list(self.etages)
|
|
|
|
|
if en_cours is not None and en_cours not in etages:
|
|
|
|
|
etages.append(en_cours)
|
|
|
|
|
return {
|
|
|
|
|
"demandee": self.plan["demandee"],
|
|
|
|
|
"atteignable": self.plan["atteignable"],
|
|
|
|
|
"atteinte": sum(1 for e in etages if e.get("ok")),
|
|
|
|
|
"interrompu": interrompu,
|
|
|
|
|
# Sans ce champ, un rapport d'essai à blanc se lisait comme une
|
|
|
|
|
# descente réussie — et « --detruire » s'en servait.
|
|
|
|
|
"dry_run": self.dry_run,
|
|
|
|
|
"etages": etages,
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
def _sauver(self, en_cours=None):
|
|
|
|
|
"""Écrit le rapport PARTIEL, dès qu'une VM existe.
|
|
|
|
|
|
|
|
|
|
Il ne s'écrivait qu'à la fin. Une descente tuée au quatrième étage —
|
|
|
|
|
c'est arrivé — laissait quatre machines réelles et « --detruire »
|
|
|
|
|
répondait « aucun rapport : rien à défaire » : le seul enregistrement
|
|
|
|
|
du couple (alias du parent, VMID) mourait avec le processus. Il fallait
|
|
|
|
|
alors les retrouver et les détruire à la main, c'est-à-dire par leur
|
|
|
|
|
nom, ce que tout le reste de ce fichier s'applique à ne pas faire.
|
|
|
|
|
|
|
|
|
|
Marqué « interrompu » jusqu'au bout : un rapport partiel ne doit jamais
|
|
|
|
|
se lire comme une descente terminée.
|
|
|
|
|
"""
|
|
|
|
|
if self.dry_run or not self.chemin_json:
|
|
|
|
|
return
|
|
|
|
|
temporaire = self.chemin_json + ".tmp"
|
|
|
|
|
try:
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
etat = self._etat(interrompu=True, en_cours=en_cours)
|
|
|
|
|
# Le PID de la descente qui écrit : c'est ce qui distingue un
|
|
|
|
|
# rapport ABANDONNÉ d'un rapport en cours d'écriture. Le rapport
|
|
|
|
|
# final, lui, n'en porte pas — la descente est finie.
|
|
|
|
|
etat["pid"] = os.getpid()
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
with open(temporaire, "w", encoding="utf-8") as fh:
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
json.dump(etat, fh, indent=2)
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
os.replace(temporaire, self.chemin_json)
|
|
|
|
|
except OSError as err:
|
|
|
|
|
self.dire(f" ⚠ rapport non écrit : {err}")
|
|
|
|
|
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
def rapport(self, interrompu=False):
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
etat = self._etat(interrompu)
|
|
|
|
|
atteint = etat["atteinte"]
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
print("")
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
if self.dry_run:
|
|
|
|
|
self.dire(
|
|
|
|
|
f" plan annoncé sur {len(self.etages)} étage(s) —"
|
|
|
|
|
" rien n'a été créé"
|
|
|
|
|
)
|
|
|
|
|
else:
|
|
|
|
|
self.dire(
|
|
|
|
|
f" profondeur atteinte : {atteint}"
|
|
|
|
|
f" / {self.plan['demandee']}"
|
|
|
|
|
)
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
# Deux causes très différentes rendaient le même « 5 / 10 » : la
|
|
|
|
|
# machine trop petite pour dix, ou un étage tombé en route. La
|
|
|
|
|
# première n'est pas un défaut du code, la seconde si.
|
|
|
|
|
if atteint == self.plan["atteignable"] < self.plan["demandee"]:
|
|
|
|
|
self.dire(
|
|
|
|
|
f" (plan borné à {self.plan['atteignable']} par le"
|
|
|
|
|
f" {self.plan['arret']} : tout le plan a tenu)"
|
|
|
|
|
)
|
|
|
|
|
elif atteint < self.plan["atteignable"]:
|
|
|
|
|
self.dire(
|
|
|
|
|
f" (le plan annonçait {self.plan['atteignable']} :"
|
|
|
|
|
" un étage est tombé, voir plus haut)"
|
|
|
|
|
)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
for e in self.etages:
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
if self.dry_run:
|
|
|
|
|
marque, detail = "·", "plan"
|
|
|
|
|
else:
|
|
|
|
|
marque = "✓" if e["ok"] else "✗"
|
|
|
|
|
detail = (
|
|
|
|
|
f"{e.get('secondes', '—')} s" if e["ok"] else e["etape"]
|
|
|
|
|
)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
self.dire(f" {marque} étage {e['niveau']:2d} {detail}")
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
return etat
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
|
|
|
|
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
def _lance_ce_script(pid):
|
|
|
|
|
"""`pid` exécute-t-il CE script — et non pas seulement le nomme-t-il ?
|
|
|
|
|
|
|
|
|
|
Par ARGUMENT, jamais par sous-chaîne. Constaté sur cette machine : un
|
|
|
|
|
« pgrep -f deep_proxmox.py » posé dans une boucle de surveillance donne un
|
|
|
|
|
shell dont la ligne de commande contient le motif, et le contrôle comptait
|
|
|
|
|
ce shell comme une descente — deux faux positifs sur trois. Un argument
|
|
|
|
|
qui SE TERMINE par le nom du fichier, lui, ne peut venir que d'un
|
|
|
|
|
interpréteur qu'on a lancé dessus.
|
|
|
|
|
"""
|
|
|
|
|
try:
|
|
|
|
|
with open(f"/proc/{int(pid)}/cmdline", "rb") as fh:
|
|
|
|
|
arguments = fh.read().split(b"\0")
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
return False
|
|
|
|
|
return any(a.endswith(b"deep_proxmox.py") for a in arguments)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def descente_vivante(pid):
|
|
|
|
|
"""Le processus `pid` est-il une descente EN COURS ?
|
|
|
|
|
|
|
|
|
|
Le PID seul ne suffirait pas : les numéros se réutilisent, et rien ne dit
|
|
|
|
|
qu'un rapport vieux d'une semaine ne porte pas le PID d'un shell
|
|
|
|
|
d'aujourd'hui. La ligne de commande est donc lue aussi.
|
|
|
|
|
"""
|
|
|
|
|
return bool(pid) and _lance_ce_script(pid)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def autre_deep_proxmox():
|
|
|
|
|
"""Les PID des AUTRES deep_proxmox.py vivants. Le sien est exclu.
|
|
|
|
|
|
|
|
|
|
Le garde-fou du rapport — un PID dans le fichier — ne protège que les
|
|
|
|
|
descentes lancées APRÈS son écriture : celle qui tournait déjà avait
|
|
|
|
|
chargé l'ancien module en mémoire et n'écrira jamais de PID. Constaté sur
|
|
|
|
|
une descente réelle de dix étages, à l'étage 4. Ce contrôle-ci ne dépend
|
|
|
|
|
d'aucun rapport : détruire pendant qu'une descente tourne n'est jamais
|
|
|
|
|
juste, quel que soit le rapport choisi.
|
|
|
|
|
|
|
|
|
|
/proc plutôt que pgrep : « pgrep -f deep_proxmox » attrape le shell qui
|
|
|
|
|
l'invoque, et on croit alors voir survivre un processus qui n'existe pas.
|
|
|
|
|
"""
|
|
|
|
|
moi = os.getpid()
|
|
|
|
|
vivants = []
|
|
|
|
|
try:
|
|
|
|
|
entrees = os.listdir("/proc")
|
|
|
|
|
except OSError:
|
|
|
|
|
return vivants
|
|
|
|
|
for entree in entrees:
|
|
|
|
|
if not entree.isdigit() or int(entree) == moi:
|
|
|
|
|
continue
|
|
|
|
|
if _lance_ce_script(entree):
|
|
|
|
|
vivants.append(int(entree))
|
|
|
|
|
return vivants
|
|
|
|
|
|
|
|
|
|
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
def dernier_rapport():
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
"""Le rapport le plus récent qui NOMME quelque chose à défaire, ou {}.
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
C'est le SEUL enregistrement de ce que la descente a créé : un couple
|
|
|
|
|
(alias du parent, VMID) par étage. Détruire d'après lui, et non d'après
|
|
|
|
|
les noms, est toute la différence entre défaire son propre travail et
|
|
|
|
|
effacer une machine qui se trouve porter un nom voisin.
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
|
|
|
|
|
Deux rapports sont ÉCARTÉS, et chacun l'est pour un accident précis :
|
|
|
|
|
|
|
|
|
|
* celui d'une descente VIVANTE. Depuis que le rapport s'écrit VM par VM,
|
|
|
|
|
la descente en cours en a un sur le disque, et c'est le plus récent :
|
|
|
|
|
« --detruire » aurait détruit l'arbre sous le processus qui installait
|
|
|
|
|
encore, emportant des heures de mesure. Avant, la descente en cours
|
|
|
|
|
n'avait aucun rapport et la question ne se posait pas — le correctif a
|
|
|
|
|
créé le danger.
|
|
|
|
|
|
|
|
|
|
* celui qui n'a RIEN créé. Un second lancement qui meurt à l'étage 1 —
|
|
|
|
|
« le disque existe déjà » — écrit un rapport vide sous un horodatage
|
|
|
|
|
plus tardif. Il masquait le partiel qui nommait les VM réelles :
|
|
|
|
|
« 0 VM imbriquée(s) », puis « virsh undefine --remove-all-storage » sur
|
|
|
|
|
l'étage 1, dont le disque contient les étages 2 et suivants — jamais
|
|
|
|
|
arrêtés, jamais nommés.
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
"""
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
dossier = os.path.expanduser("~/.erplibre/longtest")
|
|
|
|
|
try:
|
|
|
|
|
fichiers = sorted(
|
|
|
|
|
f for f in os.listdir(dossier) if f.endswith(".json")
|
|
|
|
|
)
|
|
|
|
|
except OSError:
|
|
|
|
|
return {}
|
|
|
|
|
for nom in reversed(fichiers):
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
chemin = os.path.join(dossier, nom)
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
try:
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
with open(chemin, encoding="utf-8") as fh:
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
rapport = json.load(fh)
|
|
|
|
|
except (OSError, ValueError):
|
|
|
|
|
continue
|
|
|
|
|
if rapport.get("dry_run"):
|
|
|
|
|
continue # un plan n'a rien créé
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
if descente_vivante(rapport.get("pid")):
|
|
|
|
|
dire(f" ⏳ descente EN COURS ({rapport['pid']}) : {nom} ignoré")
|
|
|
|
|
continue
|
|
|
|
|
if not (rapport.get("etages") or []):
|
|
|
|
|
continue # rien créé : ne pas masquer un rapport qui nomme des VM
|
|
|
|
|
rapport["fichier"] = chemin
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
return rapport
|
|
|
|
|
return {}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def a_defaire(rapport):
|
|
|
|
|
"""[(niveau, parent_alias, vmid, nom)] du plus PROFOND au plus haut.
|
|
|
|
|
|
|
|
|
|
Trié sur le niveau LU dans le rapport, pas déduit du nom. La version
|
|
|
|
|
d'avant comptait les « + » de l'alias — or `alias_etage` remplace le « + »
|
|
|
|
|
du parent par un « - », donc chaque alias en portait exactement UN et le
|
|
|
|
|
tri ne triait rien. La destruction partait du plus HAUT : « qm destroy
|
|
|
|
|
--purge » sur l'étage 2 emportait le disque contenant les étages 3 et
|
|
|
|
|
suivants, sans les avoir arrêtés ni nommés.
|
|
|
|
|
"""
|
|
|
|
|
etages = [
|
|
|
|
|
e
|
|
|
|
|
for e in (rapport.get("etages") or [])
|
|
|
|
|
if e.get("vmid") and e.get("parent_alias")
|
|
|
|
|
]
|
|
|
|
|
etages.sort(key=lambda e: -int(e["niveau"]))
|
|
|
|
|
return [
|
|
|
|
|
(
|
|
|
|
|
int(e["niveau"]),
|
|
|
|
|
e["parent_alias"],
|
|
|
|
|
int(e["vmid"]),
|
|
|
|
|
nom_etage(int(e["niveau"])),
|
|
|
|
|
)
|
|
|
|
|
for e in etages
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detruire_une(parent_alias, vmid, nom, journal):
|
|
|
|
|
"""Arrête puis détruit UNE VM, par son VMID. Rend True si elle a disparu.
|
|
|
|
|
|
|
|
|
|
Par le VMID et par égalité stricte du nom : un filtre par sous-chaîne
|
|
|
|
|
aurait pris une « deep-pve-lab » de production, et « --purge » emporte les
|
|
|
|
|
disques ET les entrées de sauvegarde.
|
|
|
|
|
|
|
|
|
|
L'arrêt est CONSTATÉ avant la destruction : « qm stop » rend la main dès
|
|
|
|
|
que la tâche est lancée, et sur un hyperviseur imbriqué mesuré 36 fois
|
|
|
|
|
plus lent, « qm destroy » arrivait alors que la VM tournait encore et
|
|
|
|
|
refusait avec « VM is running ».
|
|
|
|
|
"""
|
|
|
|
|
parent = {"target": parent_alias, "sudo": "sudo ", "jump": ""}
|
|
|
|
|
code, out = pve.run(parent, "qm list", 180)
|
|
|
|
|
if code:
|
|
|
|
|
dire(f" ✗ {parent_alias} injoignable : rien touché", journal)
|
|
|
|
|
return False
|
|
|
|
|
presentes = {
|
|
|
|
|
int(v["vmid"]): (v.get("name") or "") for v in pve.parse_qm_list(out)
|
|
|
|
|
}
|
|
|
|
|
if vmid not in presentes:
|
|
|
|
|
dire(f" — {vmid} déjà absente de {parent_alias}", journal)
|
|
|
|
|
return True
|
|
|
|
|
if presentes[vmid] != nom:
|
|
|
|
|
dire(
|
|
|
|
|
f" ✗ {vmid} sur {parent_alias} s'appelle"
|
|
|
|
|
f" « {presentes[vmid]} », pas « {nom} » : rien touché",
|
|
|
|
|
journal,
|
|
|
|
|
)
|
|
|
|
|
return False
|
|
|
|
|
pve.run(parent, f"qm stop {vmid} --skiplock 1 || true", 300)
|
|
|
|
|
for _ in range(20):
|
|
|
|
|
_c, etat = pve.run(parent, f"qm status {vmid}", 120)
|
|
|
|
|
if "stopped" in pve.strip_ssh_noise(etat):
|
|
|
|
|
break
|
|
|
|
|
time.sleep(6)
|
|
|
|
|
code, out = pve.run(parent, f"qm destroy {vmid} --purge 1", 600)
|
|
|
|
|
if code:
|
|
|
|
|
dire(f" ✗ qm destroy {vmid} : code {code}", journal)
|
|
|
|
|
for ligne in pve.strip_ssh_noise(out).strip().splitlines()[-3:]:
|
|
|
|
|
dire(f" {ligne}", journal)
|
|
|
|
|
return False
|
|
|
|
|
dire(f" ✓ {nom} ({vmid}) sur {parent_alias}", journal)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detruire_etage1(journal, dry_run=False):
|
|
|
|
|
"""Le domaine libvirt du premier étage — le SEUL qui en soit un.
|
|
|
|
|
|
|
|
|
|
La boucle d'avant tournait sur trente niveaux avec une condition morte, et
|
|
|
|
|
sa branche « niveau == 1 » était vraie même quand la descente n'avait
|
|
|
|
|
jamais rien créé : « virsh undefine --remove-all-storage » partait alors
|
|
|
|
|
sur un domaine qui pouvait être n'importe quoi, sortie capturée, sans un
|
|
|
|
|
mot.
|
|
|
|
|
"""
|
|
|
|
|
nom = nom_etage(1)
|
|
|
|
|
existe = subprocess.run(
|
|
|
|
|
["sudo", "-n", "virsh", "dominfo", nom],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
)
|
|
|
|
|
if existe.returncode:
|
|
|
|
|
dire(f" — {nom} : aucun domaine libvirt", journal)
|
|
|
|
|
return True
|
|
|
|
|
if dry_run:
|
|
|
|
|
dire(
|
|
|
|
|
f" [à blanc] virsh undefine {nom} --remove-all-storage", journal
|
|
|
|
|
)
|
|
|
|
|
return True
|
|
|
|
|
subprocess.run(
|
|
|
|
|
["sudo", "virsh", "destroy", nom], capture_output=True, text=True
|
|
|
|
|
)
|
|
|
|
|
res = subprocess.run(
|
|
|
|
|
[
|
|
|
|
|
"sudo",
|
|
|
|
|
"virsh",
|
|
|
|
|
"undefine",
|
|
|
|
|
nom,
|
|
|
|
|
"--nvram",
|
|
|
|
|
"--remove-all-storage",
|
|
|
|
|
],
|
|
|
|
|
capture_output=True,
|
|
|
|
|
text=True,
|
|
|
|
|
)
|
|
|
|
|
if res.returncode:
|
|
|
|
|
dire(
|
|
|
|
|
f" ✗ virsh undefine {nom} : {res.stderr.strip()[:160]}", journal
|
|
|
|
|
)
|
|
|
|
|
return False
|
|
|
|
|
dire(f" ✓ {nom} (libvirt)", journal)
|
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def detruire(journal=None, dry_run=False):
|
|
|
|
|
"""Défait ce que le DERNIER rapport dit avoir créé, du plus profond.
|
|
|
|
|
|
|
|
|
|
Rien d'autre. La version d'avant prenait toute entrée ~/.ssh/config dont
|
|
|
|
|
le nom contenait « deep-pve », puis sur son rebond détruisait toute VM
|
|
|
|
|
dont le nom contenait « deep-pve » — une machine de labo appelée
|
|
|
|
|
« deep-pve-lab » sur un hyperviseur de production tombait dedans.
|
|
|
|
|
"""
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
# Avant tout : refuser tant qu'une descente tourne. Elle installe encore
|
|
|
|
|
# sur les machines qu'on s'apprête à détruire, et son rapport peut être
|
|
|
|
|
# celui qu'on vient de choisir.
|
|
|
|
|
autres = autre_deep_proxmox()
|
|
|
|
|
if autres:
|
|
|
|
|
dire(
|
|
|
|
|
f" ⛔ une descente tourne ({', '.join(map(str, autres))}) :"
|
|
|
|
|
" rien ne sera détruit.",
|
|
|
|
|
journal,
|
|
|
|
|
)
|
|
|
|
|
dire(" Attendre qu'elle finisse, ou l'arrêter d'abord.", journal)
|
|
|
|
|
return 1
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
rapport = dernier_rapport()
|
|
|
|
|
if not rapport:
|
|
|
|
|
dire(" aucun rapport de descente : rien à défaire.", journal)
|
|
|
|
|
dire(
|
|
|
|
|
" (les entrées ~/.ssh/config orphelines : menu de nettoyage)",
|
|
|
|
|
journal,
|
|
|
|
|
)
|
|
|
|
|
return 0
|
|
|
|
|
liste = a_defaire(rapport)
|
|
|
|
|
dire(f" rapport : {rapport.get('fichier')}", journal)
|
|
|
|
|
dire(f" {len(liste)} VM imbriquée(s) + l'étage 1 :", journal)
|
|
|
|
|
for niveau, parent_alias, vmid, nom in liste:
|
|
|
|
|
dire(
|
|
|
|
|
f" étage {niveau:2d} {nom} ({vmid}) sur {parent_alias}",
|
|
|
|
|
journal,
|
|
|
|
|
)
|
|
|
|
|
dire(f" étage 1 {nom_etage(1)} (libvirt)", journal)
|
|
|
|
|
if dry_run:
|
|
|
|
|
dire("\n --dry-run : rien ne sera détruit.", journal)
|
|
|
|
|
return 0
|
|
|
|
|
# Une confirmation, parce que « --purge » emporte les disques et que le
|
|
|
|
|
# menu lançait cette option d'une seule touche.
|
|
|
|
|
reponse = input("\n Détruire tout cela ? (tapez OUI) : ").strip()
|
|
|
|
|
if reponse != "OUI":
|
|
|
|
|
dire(" annulé.", journal)
|
|
|
|
|
return 1
|
|
|
|
|
faits = sum(
|
|
|
|
|
1
|
|
|
|
|
for niveau, parent_alias, vmid, nom in liste
|
|
|
|
|
if detruire_une(parent_alias, vmid, nom, journal)
|
|
|
|
|
)
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
# « if not … : faits -= 1 » : un succès de l'étage 1 n'ajoutait RIEN,
|
|
|
|
|
# alors que le total est len(liste) + 1. Le décompte était décalé de un
|
|
|
|
|
# dans TOUS les cas — une destruction complète annonçait « il reste des
|
|
|
|
|
# machines » et sortait 1, si bien que le seul avertissement censé
|
|
|
|
|
# prévenir qu'un disque de plusieurs dizaines de Go reste alloué
|
|
|
|
|
# s'affichait toujours, et qu'on apprenait à ne plus le lire.
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
racine = detruire_etage1(journal)
|
|
|
|
|
if racine:
|
[FIX] LongTest : ne pas détruire sous une descente vivante ; blocs ssh
Relecture adversaire du commit précédent : il avait CRÉÉ un danger. Le rapport
s'écrivant maintenant VM par VM, celui de la descente EN COURS est le plus
récent, et « --detruire » l'aurait choisi — qm destroy --purge sur l'arbre que
le processus installait encore. Deux garde-fous : un PID dans le rapport, et
un refus net tant qu'un autre deep_proxmox.py tourne. Le second est nécessaire
car une descente déjà lancée a l'ancien module en mémoire.
Reconnu par ARGUMENT, pas par sous-chaîne : mon propre « pgrep -f
deep_proxmox.py » de surveillance donnait deux faux positifs sur trois.
Trois autres, mêmes preuves :
- un rapport vide plus récent masquait celui qui nommait les VM réelles ;
- detruire() ne créditait jamais l'étage 1 : le décompte était décalé de un
dans tous les cas, donc l'avertissement sortait toujours ;
- _ssh_config_drop_hosts prenait l'indentation pour de la syntaxe. Sur un bloc
au corps non indenté, seule la ligne Host partait et ssh rattachait
« StrictHostKeyChecking no » au bloc précédent — un serveur de production.
Et un bloc partagé (« Host prod-db vm-a ») partait en entier.
Les cinq correctifs meurent sous mutation.
--- EN ---
Adversarial review of the previous commit: it had CREATED a hazard. With the
report now written VM by VM, the RUNNING descent's is the most recent, and
"--detruire" would have picked it — qm destroy --purge on the tree the process
was still installing. Two guards: a PID in the report, and a flat refusal
while another deep_proxmox.py runs. The second is needed because an
already-running descent holds the old module in memory.
Matched by ARGUMENT, not substring: my own monitoring "pgrep -f
deep_proxmox.py" produced two false positives out of three.
Three more, same evidence:
- a newer empty report masked the one naming the real VMs;
- detruire() never credited level 1: the count was off by one in every case,
so the warning always fired;
- _ssh_config_drop_hosts took indentation for syntax. On a block with an
unindented body only the Host line went, and ssh attached
"StrictHostKeyChecking no" to the preceding block — a production server.
And a shared block ("Host prod-db vm-a") went entirely.
All five fixes die under mutation.
Assisted-by: claude-opus-5
(cherry picked from commit 7d348d976f96e420b4fec4d879911b658a730ffa)
2026-08-27 06:56:35 -04:00
|
|
|
faits += 1
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
retirer_alias(rapport, journal)
|
|
|
|
|
if racine:
|
|
|
|
|
# L'étage 1 est un DISQUE, et tout le reste vit dedans. « virsh
|
|
|
|
|
# undefine --remove-all-storage » l'a effacé : les étages injoignables
|
|
|
|
|
# — leur parent était éteint — ont disparu avec, qu'on ait pu leur
|
|
|
|
|
# parler ou non. Annoncer « il reste des machines » dans ce cas était
|
|
|
|
|
# faux dans l'autre sens, et un avertissement faux ne se lit plus.
|
|
|
|
|
reste = len(liste) + 1 - faits
|
|
|
|
|
dire(
|
|
|
|
|
f"\n {len(liste) + 1} / {len(liste) + 1} défait(s)."
|
|
|
|
|
+ (
|
|
|
|
|
f" ({reste} injoignable(s), emporté(s) avec le disque de"
|
|
|
|
|
" l'étage 1.)"
|
|
|
|
|
if reste
|
|
|
|
|
else ""
|
|
|
|
|
),
|
|
|
|
|
journal,
|
|
|
|
|
)
|
|
|
|
|
return 0
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
dire(
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
f"\n {faits} / {len(liste) + 1} défait(s)."
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
" ⚠ l'étage 1 est DEBOUT : ce qu'il contient vit encore.",
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
journal,
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
return 1
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def retirer_alias(rapport, journal=None):
|
|
|
|
|
"""Retire de ~/.ssh/config les entrées de la descente défaite.
|
|
|
|
|
|
|
|
|
|
Sans cela, elles survivaient aux machines : des entrées mortes dont le
|
|
|
|
|
ProxyJump désigne un hôte qui n'existe plus, et qu'on retrouve plus tard
|
|
|
|
|
sans savoir à quoi elles servaient.
|
|
|
|
|
"""
|
|
|
|
|
alias = []
|
|
|
|
|
for etage in rapport.get("etages") or []:
|
|
|
|
|
nom = etage.get("alias")
|
|
|
|
|
if not nom:
|
|
|
|
|
# L'étage abandonné avant l'écriture de son alias : le calculer,
|
|
|
|
|
# il est déterminé par (niveau, alias du parent).
|
|
|
|
|
parent = etage.get("parent_alias")
|
|
|
|
|
if parent:
|
|
|
|
|
nom = alias_etage(int(etage["niveau"]), parent)
|
|
|
|
|
if nom and nom not in alias:
|
|
|
|
|
alias.append(nom)
|
|
|
|
|
if not alias:
|
|
|
|
|
return
|
|
|
|
|
try:
|
|
|
|
|
from script.todo.todo import TODO
|
|
|
|
|
|
|
|
|
|
TODO.__new__(TODO)._write_ssh_config_entry(
|
|
|
|
|
[], "erplibre", "", also_drop=tuple(alias)
|
|
|
|
|
)
|
|
|
|
|
except Exception as err: # noqa: BLE001 - jamais bloquer la destruction
|
|
|
|
|
dire(f" ⚠ entrées ~/.ssh/config non retirées : {err}", journal)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
def principal(argv=None):
|
|
|
|
|
parseur = argparse.ArgumentParser(
|
|
|
|
|
description="Jusqu'à quel étage un Proxmox dans un Proxmox tient-il ?"
|
|
|
|
|
)
|
|
|
|
|
parseur.add_argument("--depth", type=int, default=10)
|
|
|
|
|
parseur.add_argument("--dry-run", action="store_true")
|
|
|
|
|
parseur.add_argument("--detruire", action="store_true")
|
|
|
|
|
args = parseur.parse_args(argv)
|
|
|
|
|
|
|
|
|
|
journal = os.path.expanduser(
|
|
|
|
|
f"~/.erplibre/longtest/deep-pve-{time.strftime('%Y%m%d-%H%M%S')}.log"
|
|
|
|
|
)
|
|
|
|
|
os.makedirs(os.path.dirname(journal), exist_ok=True)
|
|
|
|
|
if args.detruire:
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# « --dry-run » était ignoré ici : la prudence naturelle avant une
|
|
|
|
|
# destruction détruisait pour de vrai.
|
|
|
|
|
return detruire(journal, dry_run=args.dry_run)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
|
|
|
|
coeurs, ram, disque = capacite_hote()
|
|
|
|
|
print(
|
|
|
|
|
f"\n machine : {coeurs} cœurs, {ram} Mo disponibles,"
|
|
|
|
|
f" {disque} Go de disque"
|
|
|
|
|
)
|
|
|
|
|
plan = nesting.nesting_plan(args.depth, coeurs, ram, disque)
|
|
|
|
|
print(f"\n {'étage':>5} {'vCPU':>4} {'RAM':>9} {'disque':>8}")
|
|
|
|
|
for n in plan["niveaux"]:
|
|
|
|
|
print(
|
|
|
|
|
f" {n['niveau']:>5} {n['vcpu']:>4} {n['ram']:>6} Mo"
|
|
|
|
|
f" {n['disque']:>5} Go"
|
|
|
|
|
)
|
|
|
|
|
if plan["arret"]:
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
# Les TROIS plafonds, pas seulement celui qui borne : sans eux on
|
|
|
|
|
# ajoute la ressource nommée sans savoir de combien, ni laquelle
|
|
|
|
|
# bornera ensuite.
|
|
|
|
|
plafonds = " · ".join(
|
|
|
|
|
f"{nom} {valeur}" for nom, valeur in plan["plafonds"].items()
|
|
|
|
|
)
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
print(
|
|
|
|
|
f"\n ⚠ demandée {plan['demandee']}, atteignable"
|
[FIX] imbrication : la ressource qui borne, l'attente, le décompte
Incident sur une descente réelle : un agent de relecture, chargé de vérifier
ce que redemarrer_et_verifier PROUVE, l'a appelé sur l'étage 1 vivant. Le
reboot a éteint les étages 2, 3 et 4 d'un coup. La descente a alors attendu
son délai entier — quarante minutes — un ssh qui ne pouvait plus aboutir, puis
a conclu « jamais joignable ». L'attente surveille désormais la MAISON.
Le décompte de la destruction mentait dans l'autre sens : les étages
injoignables étaient annoncés « il reste des machines » alors que le disque de
l'étage 1, effacé, les contenait. Les entrées ~/.ssh/config sont retirées
aussi, sinon leur ProxyJump désigne un hôte disparu.
Et « arret » nommait la RAM quand le vCPU bornait : la chaîne était figée en
ram > disque > vcpu et évaluée à la profondeur demandée. Il nomme maintenant
le plus bas des trois plafonds, et les trois sont affichés.
--- EN ---
Incident on a real descent: a review agent, tasked with checking what
redemarrer_et_verifier PROVES, called it on the living level 1. The reboot
took levels 2, 3 and 4 down at once. The descent then waited its whole
timeout — forty minutes — for an ssh that could no longer land, and concluded
"never reachable". The wait now watches the HOUSE.
The destroy count lied the other way: unreachable levels were reported as "il
reste des machines" when level 1's erased disk contained them. The
~/.ssh/config entries are removed too, else their ProxyJump names a host that
is gone.
And "arret" named RAM when vCPU was the bound: the chain was frozen as
ram > disque > vcpu and evaluated at the requested depth. It now names the
lowest of the three ceilings, and all three are shown.
Assisted-by: claude-opus-5
(cherry picked from commit 2d84b62bdd9907e9a30383774015bcb1ae379de1)
2026-08-27 07:32:38 -04:00
|
|
|
f" {plan['atteignable']} — c'est le {plan['arret']} qui borne"
|
|
|
|
|
f"\n profondeur permise par chaque ressource : {plafonds}"
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
)
|
|
|
|
|
if not plan["niveaux"]:
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
if args.depth < 1:
|
|
|
|
|
print(f"\n profondeur demandée : {args.depth} — rien à faire.\n")
|
|
|
|
|
return 0
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
print("\n ✗ pas même un étage ne tient sur cette machine.\n")
|
|
|
|
|
return 1
|
|
|
|
|
print(f"\n journal : {journal}")
|
|
|
|
|
if args.dry_run:
|
|
|
|
|
print(" --dry-run : rien ne sera créé.\n")
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
chemin = journal[:-4] + ("-dryrun.json" if args.dry_run else ".json")
|
[FIX] LongTest : rapport écrit VM par VM, retrait ssh sans bloc nu
Descente de dix étages arrêtée pendant l'installation du quatrième : quatre
machines réelles restaient, et « --detruire » répondait « aucun rapport : rien
à défaire ». Le rapport ne s'écrivait qu'à la fin, donc le seul enregistrement
du couple (alias du parent, VMID) mourait avec le processus. Il fallait les
retrouver par leur NOM, ce que tout ce fichier s'applique à éviter.
En nettoyant à la main, second défaut : appelé sans nom à écrire — un retrait
pur, légitime, les machines n'existant plus — _write_ssh_config_entry écrivait
« Host » NU suivi d'un « HostName » vide dans le ~/.ssh/config réel, puis
mourait sur IndexError en annonçant l'ajout. Constaté, puis retiré du fichier.
Les deux correctifs meurent sous mutation : 3 tests et 2 tests.
--- EN ---
A ten-level descent stopped during the fourth level's install: four real
machines were left, and "--detruire" answered "no report: nothing to undo".
The report was only written at the end, so the sole record of the (parent
alias, VMID) pair died with the process. They had to be found by NAME, which
this whole file works to avoid.
Cleaning up by hand surfaced a second defect: called with no name to write — a
pure removal, legitimate since the machines are gone — _write_ssh_config_entry
wrote a BARE "Host" followed by an empty "HostName" into the real ~/.ssh/config,
then died on IndexError while announcing the addition. Observed, then removed.
Both fixes die under mutation: 3 tests and 2 tests.
Assisted-by: claude-opus-5
(cherry picked from commit e7c8b540c630b33c6eb1b1a2f51c01497e0b3ca0)
2026-08-27 05:39:08 -04:00
|
|
|
descente = Descente(plan, journal, args.dry_run, chemin)
|
|
|
|
|
rapport = descente.parcourir()
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
with open(chemin, "w", encoding="utf-8") as fh:
|
|
|
|
|
json.dump(rapport, fh, indent=2)
|
|
|
|
|
print(f"\n rapport : {chemin}\n")
|
[FIX] LongTest : sh au lieu de bash, et --detruire trop large
Attaqué par trois lentilles sur le code écrit, avant de le lancer pour de
vrai. Deux fautes valaient à elles seules l'exercice.
Il n'aurait JAMAIS fonctionné. L'installeur était lancé par « sh », or il
porte « set -euo pipefail » et un shebang bash : sur Debian /bin/sh est dash,
qui répond « set: Illegal option -o pipefail » et sort à la PREMIÈRE ligne.
Chaque étage aurait échoué sur l'installation, à tous les coups.
Et « --detruire » pouvait emporter une machine étrangère. Il prenait toute
entrée ssh dont le nom CONTENAIT « deep-pve », puis sur son rebond détruisait
toute VM dont le nom contenait « deep-pve » — une « deep-pve-lab » de
production tombait dedans, et « --purge » emporte les disques. Son tri « du
plus profond au plus haut » comptait les « + » de l'alias, or alias_etage
remplace le « + » du parent par un « - » : chaque alias en portait exactement
UN, le tri ne triait rien, et la destruction partait du plus HAUT — le disque
du parent emportait ses enfants sans qu'on les ait nommés. Il ignorait
« --dry-run », ne lisait aucun code de retour, concluait « ✓ défait », et le
menu le lançait d'une touche.
Il ne détruit plus que ce que le RAPPORT nomme : un couple (parent, VMID) par
étage, du plus profond d'après le niveau lu, égalité stricte du nom, arrêt
CONSTATÉ avant destruction, codes de retour lus, et une confirmation par
« OUI » après la liste.
Six autres constats, tous réels. Le redémarrage se prouve par btime et non par
le seul noyau — rejoué sur un étage déjà installé, on validait un redémarrage
qui n'avait pas eu lieu, exactement le piège corrigé la semaine dernière dans
le suivi. La sonde de disponibilité ne demande plus sudo, sinon un sudo lent
se lisait « jamais joignable en ssh ». Les délais suivent la profondeur : le
script existe pour mesurer un ralentissement de 36x, et un plafond fixe
déclarait échouée une installation qui avançait. L'adresse fixe est contrôlée
AVANT de télécharger une image et de démarrer une VM. Le DNS de l'hôte suit la
spec, sinon apt meurt sans rien expliquer. Et l'essai à blanc ne prétend plus
avoir atteint quoi que ce soit — son rapport était indiscernable d'une
réussite, JSON compris.
L'algorithme aussi : profondeur 0 rendait un plan d'UN étage, donc
« --depth 0 » créait une VM ; et sur un hôte de quatre cœurs le premier étage
recevait UN vCPU quand son invité en recevait deux — un parent plus étroit que
son enfant.
Les tests mordent, prouvé par mutation : remplacer le calcul du premier étage
par la valeur imbriquée les laissait verts.
--- EN ---
Attacked by three lenses on the written code, before running it for real. Two
faults alone justified the exercise.
It would NEVER have worked. The installer was run by "sh", yet it carries "set
-euo pipefail" and a bash shebang: on Debian /bin/sh is dash, which answers
"set: Illegal option -o pipefail" and exits on the FIRST line. Every level
would have failed at install, every time.
And "--detruire" could take a stranger's machine. It took every ssh entry
whose name CONTAINED "deep-pve", then on its jump host destroyed every VM
whose name contained "deep-pve" — a production "deep-pve-lab" fell in, and
"--purge" takes the disks. Its "deepest first" sort counted the "+" in the
alias, yet alias_etage replaces the parent's "+" with a "-": every alias had
exactly ONE, the sort sorted nothing, and destruction started from the TOP —
the parent's disk took its children with it, unnamed. It ignored "--dry-run",
read no return code, concluded "✓ done", and the menu fired it on one key.
It now destroys only what the REPORT names: a (parent, VMID) pair per level,
deepest first by the recorded level, strict name equality, shutdown VERIFIED
before destruction, return codes read, and a "OUI" confirmation after the
list.
Six more findings, all real. The reboot is proven by btime, not by the kernel
alone — replayed on an already-installed level, we validated a reboot that
never happened, exactly the trap fixed last week in the monitor. The liveness
probe no longer asks for sudo, or a slow sudo read as "never reachable by
ssh". Timeouts follow the depth: the script exists to measure a 36x slowdown,
and a fixed ceiling declared failed an install that was progressing. The
static address is checked BEFORE downloading an image and starting a VM. The
host's DNS follows the spec, or apt dies explaining nothing. And the dry run
no longer claims to have reached anything — its report was indistinguishable
from a success, JSON included.
The algorithm too: depth 0 returned a ONE-level plan, so "--depth 0" created a
VM; and on a four-core host the first level got ONE vCPU while its guest got
two — a parent narrower than its child.
The tests bite, proven by mutation: replacing the first level's computation
with the nested value left them green.
Assisted-by: Claude Opus 5
(cherry picked from commit 64b8e5063bd7f420cdeb27b88f94043190b5ecd4)
2026-08-26 07:00:30 -04:00
|
|
|
# En essai à blanc, c'est le PLAN qui est complet ou non — aucune
|
|
|
|
|
# profondeur n'a été atteinte. Hors essai, « non nul » ne suffisait pas :
|
|
|
|
|
# une descente morte au deuxième étage sur dix rendait 0.
|
|
|
|
|
if args.dry_run:
|
|
|
|
|
return 0 if rapport["atteignable"] == rapport["demandee"] else 1
|
|
|
|
|
return 0 if rapport["atteinte"] == rapport["demandee"] else 1
|
[ADD] LongTest : jusqu'à quel étage un Proxmox imbriqué tient-il
La profondeur d'imbrication praticable ne se déduit pas, elle se mesure. Une
mesure à la main a trouvé, au quatrième étage, un invité 36 fois plus lent que
le temps réel — 583 secondes d'horloge pour 16 secondes de temps invité,
chaque ligne d'ACPI prenant une seconde — puis un noyau gelé au MÊME octet
quelles que soient les ressources. Un chiffre obtenu une fois, sur une
machine, n'est pas un chiffre.
D'où trois choses.
L'algorithme, en fonctions pures. Deux ressources s'épuisent en descendant :
la mémoire, chaque étage gardant de quoi faire tourner ses propres démons, et
le disque, celui de l'enfant vivant DANS celui du parent. Une troisième se
dégrade, et elle borne le vCPU à deux au-delà du premier étage : douze ont
gelé le noyau invité, les mêmes deux avançaient. La mémoire n'est PAS bornée —
la même VM gelait au même octet avec 9 Go et avec 2 Go, donc la rogner ne
gagnerait rien et priverait l'étage du dessous. Le plan est annoncé avant
toute création, et jamais au-delà de ce qui tient.
Le garde-fou dans l'écran. Il lisait la capacité de l'HÔTE et l'offrait en
entier : sur un troisième étage à 14 cœurs, il a proposé 12 vCPU à une VM qui
n'a jamais démarré. Le nombre n'était pas absurde pour la machine ; il l'était
pour sa profondeur, que l'écran ignorait. Elle se compte maintenant sur la
chaîne de ProxyJump — un rebond par étage, et c'est nous qui écrivons ces
entrées.
Le test long, dans LongTest/ et non dans test/ : le lanceur unitaire doit
rester lançable en quelques secondes, partout, y compris sans virtualisation.
La descente est uniforme — créer, attendre le ssh, installer, redémarrer et
vérifier le noyau, remettre pmxcfs debout, contrôler le stockage — et s'arrête
au premier étage qui échoue en NOMMANT l'étape. Il envoie notre
install_proxmox.sh par scp plutôt que de laisser la VM cloner le dépôt : c'est
notre code qu'on éprouve, et un correctif absent du distant a fait revenir le
même défaut sur trois VM.
--- EN ---
The practicable nesting depth cannot be deduced, only measured. A manual
measurement found, at the fourth level, a guest 36 times slower than real time
— 583 seconds of wall clock for 16 seconds of guest time, each ACPI line
taking a second — then a kernel frozen at the SAME byte whatever the
resources. A number obtained once, on one machine, is not a number.
Hence three things.
The algorithm, in pure functions. Two resources run out going down: memory,
each level keeping what its own daemons need, and disk, the child's living
INSIDE the parent's. A third degrades, and it caps the vCPU at two beyond the
first level: twelve froze the guest kernel, the same two progressed. Memory is
NOT capped — the same VM froze at the same byte with 9 GB and with 2 GB, so
trimming it would gain nothing and starve the level below. The plan is
announced before anything is created, and never beyond what fits.
The guard in the screen. It read the HOST's capacity and offered all of it: on
a third level with 14 cores it proposed 12 vCPU to a VM that never booted. The
number was not absurd for the machine; it was for its depth, which the screen
did not know. It is now counted on the ProxyJump chain — one hop per level,
and we are the ones writing those entries.
The long test, in LongTest/ and not test/: the unit runner must stay runnable
in seconds, anywhere, including without virtualisation. The descent is uniform
— create, wait for ssh, install, reboot and check the kernel, bring pmxcfs
back, check the storage — and stops at the first level that fails, NAMING the
step. It sends our install_proxmox.sh over scp instead of letting the VM clone
the repository: it is our code being exercised, and a fix absent from the
remote made the same defect return on three VMs.
Assisted-by: Claude Opus 5
(cherry picked from commit 4f70c461330cac6f46783a60e0f33052a979fa23)
2026-08-26 06:20:52 -04:00
|
|
|
|
|
|
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
|
sys.exit(principal())
|