From 0930f30cd39a8803ec36cacdd85e80bf69561a5e Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Tue, 28 Jul 2026 08:15:46 +0000 Subject: [PATCH] [IMP] script todo: suffixe d'arch dans le nom + log d'install non vide MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1) Nommage : _qemu_infra_name ajoute un suffixe d'architecture quand elle diffère de la native de l'hôte -> déployer un s390x sur un hôte amd64 donne « erplibre-ubuntu-2604-s390x » (au lieu de « erplibre-ubuntu-2604 »). Évite les collisions de noms entre archis et rend l'archi visible. Passé dans les 3 appelants (VM unique + plan + jobs de l'infra). 2) Log d'installation vide : sur une archi ÉMULÉE (s390x/arm64 sur x86) le boot prend plusieurs minutes ; pendant ce temps l'attente n'écrivait rien -> le log restait VIDE et paraissait « bloqué ». _launch_one écrit désormais un en-tête d'attente immédiat + un battement toutes les ~30 s, et l'attente sshd/cloud-init passe de ~12 à ~20 min (boot émulé lent). _qemu_wait_ssh (chemin streamé) : timeout porté à 20 min également. Validé : nommage (amd64/natif sans suffixe, s390x/arm64 suffixés) ; le log se remplit dès le départ (en-tête + « ... 30s »). Co-Authored-By: Claude Opus 4.8 (1M context) --- script/todo/qemu_install_monitor.py | 30 ++++++++++++++++++++++++----- script/todo/todo.py | 22 +++++++++++++-------- script/todo/todo_i18n.py | 12 ++++++++++++ 3 files changed, 51 insertions(+), 13 deletions(-) diff --git a/script/todo/qemu_install_monitor.py b/script/todo/qemu_install_monitor.py index 5fc18a2..ad0370b 100644 --- a/script/todo/qemu_install_monitor.py +++ b/script/todo/qemu_install_monitor.py @@ -20,6 +20,14 @@ import subprocess import time from pathlib import Path +try: + from script.todo.todo_i18n import t +except Exception: # pragma: no cover - repli si i18n indisponible + + def t(key: str) -> str: + return key + + EXIT_MARKER = "__ERPLIBRE_EXIT__" SSH_OPTS = ( "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null " @@ -38,7 +46,8 @@ def _launch_one(ip: str, remote_cmd: str, log_path: str) -> None: """Lance une install SSH DÉTACHÉE : attend le sshd, exécute, journalise la sortie puis écrit le marqueur de fin avec le code de sortie.""" # Sonde de disponibilité : on attend que sshd réponde ET que cloud-init - # soit TERMINÉ, via des connexions COURTES successives (jusqu'à ~12 min). + # soit TERMINÉ, via des connexions COURTES successives (jusqu'à ~20 min : + # une architecture ÉMULÉE, s390x/arm64 sur hôte x86, boote lentement). # Au 1er boot, cloud-init régénère les clés d'hôte et REDÉMARRE sshd : une # session SSH longue (ex. « cloud-init status --wait ») serait alors tuée # (« Connection closed by remote host », exit 255 — cas Fedora). Chaque @@ -49,16 +58,27 @@ def _launch_one(ip: str, remote_cmd: str, log_path: str) -> None: "if command -v cloud-init >/dev/null 2>&1; then " "cloud-init status 2>/dev/null || true; else echo nocloudinit; fi" ) + log_q = shlex.quote(log_path) + # On écrit un message d'attente + un battement toutes les ~30 s : sinon le + # log reste VIDE pendant tout le boot émulé et paraît « bloqué ». + msg_wait = t("Waiting for the VM to start (boot + cloud-init)") + msg_slow = t("(an emulated architecture can be slow; this is normal)") + msg_ready = t("VM ready - starting the ERPLibre install") wrapper = ( - f"for i in $(seq 1 144); do " + f"echo {shlex.quote('== ' + msg_wait + ' ==')} >> {log_q}; " + f"echo {shlex.quote(' ' + msg_slow)} >> {log_q}; " + f"for i in $(seq 1 240); do " f"st=$(ssh {SSH_OPTS} -o BatchMode=yes erplibre@{ip} " f"{shlex.quote(ci_probe)} 2>/dev/null); " f'case "$st" in ' f"*done*|*disabled*|*error*|*degraded*|*nocloudinit*) break;; " - f"esac; sleep 5; done; " + f"esac; " + f'if [ $((i % 6)) -eq 0 ]; then echo " ... $((i*5))s" >> {log_q}; fi; ' + f"sleep 5; done; " + f"echo {shlex.quote('== ' + msg_ready + ' ==')} >> {log_q}; " f"ssh {SSH_OPTS} erplibre@{ip} {shlex.quote(remote_cmd)} " - f"> {shlex.quote(log_path)} 2>&1; " - f'echo "{EXIT_MARKER} $?" >> {shlex.quote(log_path)}' + f">> {log_q} 2>&1; " + f'echo "{EXIT_MARKER} $?" >> {log_q}' ) # setsid -f : le process survit à la fermeture du menu / du dashboard. subprocess.Popen( diff --git a/script/todo/todo.py b/script/todo/todo.py index 36a6f11..3fe30c7 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -1041,7 +1041,7 @@ class TODO: distro = self._qemu_prompt_distro() version = self._qemu_prompt_version(distro) arch = self._qemu_prompt_arch(distro) - default_name = self._qemu_infra_name(distro, version) + default_name = self._qemu_infra_name(distro, version, arch) name = ( input(f"{t('VM name (default: ')}{default_name}): ").strip() or default_name @@ -1608,10 +1608,16 @@ class TODO: spec.loader.exec_module(mod) return mod - @staticmethod - def _qemu_infra_name(distro, version): - """Nom de VM stable pour le parc, ex. erplibre-ubuntu-2404.""" - return f"erplibre-{distro}-{version.replace('.', '')}" + @classmethod + def _qemu_infra_name(cls, distro, version, arch=None): + """Nom de VM stable pour le parc, ex. erplibre-ubuntu-2404. Ajoute un + suffixe d'architecture quand elle diffère de la native de l'hôte (ex. + erplibre-ubuntu-2604-s390x sur un hôte amd64) pour éviter les collisions + de noms entre archis et rendre l'archi visible.""" + base = f"erplibre-{distro}-{version.replace('.', '')}" + if arch and arch != cls._native_arch(): + base += f"-{arch}" + return base @staticmethod def _parse_disk_gb(size): @@ -1743,7 +1749,7 @@ class TODO: ERPLIBRE_EXTRA_DISK_GB = 5 @staticmethod - def _qemu_wait_ssh(ip, user="erplibre", timeout=720): + def _qemu_wait_ssh(ip, user="erplibre", timeout=1200): """Attend que sshd réponde ET que cloud-init soit TERMINÉ, via des connexions COURTES successives. Au 1er boot, cloud-init régénère les clés d'hôte et REDÉMARRE sshd : attendre la fin de cloud-init AVANT de @@ -2049,7 +2055,7 @@ class TODO: free_ram = self._host_free_ram_mb() print(f"\n{t('Deployment plan')} ({len(selected)} VM) :") for d, v, ram, disk in selected: - name = self._qemu_infra_name(d, v) + name = self._qemu_infra_name(d, v, arch) print(f" - {name:<26} {d} {v:<7} RAM {ram}Mo {t('disk')} {disk}") print(f"\n {t('Total RAM (all running):')} {total_ram} Mo") print(f" {t('Total virtual disk (thin qcow2):')} ~{total_disk} G") @@ -2108,7 +2114,7 @@ class TODO: deployed = [] jobs = [] # (name, parts) des VM à créer for d, v, _ram, disk in selected: - name = self._qemu_infra_name(d, v) + name = self._qemu_infra_name(d, v, arch) if self._qemu_domain_exists(name): print(f"⏭ {name}: {t('already exists, skipped.')}") deployed.append(name) diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 1bd71f8..c6ae2e0 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1197,6 +1197,18 @@ TRANSLATIONS = { "fr": "images pour cette architecture disponibles seulement pour :", "en": "images for this arch only exist for:", }, + "Waiting for the VM to start (boot + cloud-init)": { + "fr": "En attente du démarrage de la VM (boot + cloud-init)", + "en": "Waiting for the VM to start (boot + cloud-init)", + }, + "(an emulated architecture can be slow; this is normal)": { + "fr": "(une architecture émulée peut être lente ; c'est normal)", + "en": "(an emulated architecture can be slow; this is normal)", + }, + "VM ready - starting the ERPLibre install": { + "fr": "VM prête — installation ERPLibre en cours", + "en": "VM ready - starting the ERPLibre install", + }, "Choice (number or name, blank = native):": { "fr": "Choix (numéro ou nom, vide = native) :", "en": "Choice (number or name, blank = native):",