From d83d244805006fb15d8c07eca11d357ff6c4a67b Mon Sep 17 00:00:00 2001 From: Mathieu Benoit Date: Tue, 28 Jul 2026 02:26:07 +0000 Subject: [PATCH] [FIX] script todo/qemu: safer VM deploy prompt (size unit, name, errors) Three papercuts hit when creating a VM from the menu: 1. Disk size "30" was passed verbatim to qemu-img resize, which read it as 30 BYTES and failed ("use --shrink"). The prompt now shows units (e.g. 30G, 1T) and a bare number is normalised to GB ("30" -> "30G"), in the menu and in deploy_qemu.py itself. 2. The VM name is no longer required: leaving it blank uses the default erplibre-- (e.g. erplibre-ubuntu-2604). Distro and version are now asked first so the default can be offered. 3. The menu ignored the deploy exit code and carried on to "wait for the VM IP" even after a failure, hanging forever with the error scrolled off. It now checks the return code and stops with a clear message. Co-Authored-By: Claude Opus 4.8 (1M context) --- script/qemu/deploy_qemu.py | 4 ++++ script/todo/todo.py | 31 +++++++++++++++++++++++++------ script/todo/todo_i18n.py | 14 +++++++++++--- 3 files changed, 40 insertions(+), 9 deletions(-) diff --git a/script/qemu/deploy_qemu.py b/script/qemu/deploy_qemu.py index 8c0d2d3..92e7dc0 100755 --- a/script/qemu/deploy_qemu.py +++ b/script/qemu/deploy_qemu.py @@ -1315,6 +1315,10 @@ def main() -> None: args.memory = min_ram if args.disk_size is None: args.disk_size = min_disk + # Un nombre nu (« 30 ») serait pris pour des OCTETS par qemu-img et ferait + # échouer le resize : on suppose des Go par défaut (« 30 » -> « 30G »). + if re.fullmatch(r"\d+", str(args.disk_size)): + args.disk_size = f"{args.disk_size}G" urls = image_candidates( args.distro, code, args.arch, args.version, args.dry_run ) diff --git a/script/todo/todo.py b/script/todo/todo.py index 2e6ad9f..ac3d78c 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -930,19 +930,32 @@ class TODO: if cmd_no_found: print(t("Command not found !")) + @staticmethod + def _normalize_disk_size(size): + """« 30 » -> « 30G » (unité par défaut Go). Laisse « 30G », « 1T »… ou + vide tels quels. Sans unité, qemu-img interpréterait des OCTETS.""" + size = size.strip() + if re.fullmatch(r"\d+", size): + return size + "G" + return size + def _qemu_deploy_vm(self, dry_run=False): script_path = self._qemu_script_path() - name = input(t("VM name (required): ")).strip() - if not name: - print(t("VM name is required!")) - return + # Distro + version d'abord : permet de proposer un nom par défaut. distro = self._qemu_prompt_distro() version = self._qemu_prompt_version(distro) + default_name = self._qemu_infra_name(distro, version) + name = ( + input(f"{t('VM name (default: ')}{default_name}): ").strip() + or default_name + ) # Laisser vide => le script applique le minimum requis par la version # choisie (libosinfo). On n'envoie alors pas le flag. memory = input(t("RAM in MB (blank = version minimum): ")).strip() vcpus = input(t("vCPUs (default: 2): ")).strip() - disk_size = input(t("Disk size (blank = version minimum): ")).strip() + disk_size = self._normalize_disk_size( + input(t("Disk size (blank = version min; e.g. 30G, 1T): ")).strip() + ) default_key = self._qemu_default_ssh_key() key_hint = default_key or t("none") @@ -995,7 +1008,13 @@ class TODO: print(f"{t('Will execute:')} {cmd}") if password: print(f"🔑 {t('Console/SSH login:')} erplibre / {password}") - self.execute.exec_command_live(cmd, source_erplibre=False) + status = self.execute.exec_command_live(cmd, source_erplibre=False) + + # Arrêt net sur erreur : sinon on enchaînait sur l'attente d'IP d'une + # VM jamais créée (blocage infini), l'utilisateur ne voyant rien. + if status != 0: + print(f"\n❌ {t('Deployment failed (see the error above).')}") + return # Proposer d'enregistrer la VM dans ~/.ssh/config (après le 1er boot). if not dry_run: diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 62977b4..d9c8a20 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1359,9 +1359,17 @@ TRANSLATIONS = { "fr": "vCPU (défaut : 2) : ", "en": "vCPUs (default: 2): ", }, - "Disk size (blank = version minimum): ": { - "fr": "Taille du disque (vide = minimum de la version) : ", - "en": "Disk size (blank = version minimum): ", + "Disk size (blank = version min; e.g. 30G, 1T): ": { + "fr": "Taille du disque (vide = min. version ; ex. 30G, 1T) : ", + "en": "Disk size (blank = version min; e.g. 30G, 1T): ", + }, + "VM name (default: ": { + "fr": "Nom de la VM (défaut : ", + "en": "VM name (default: ", + }, + "Deployment failed (see the error above).": { + "fr": "Échec du déploiement (voir l'erreur ci-dessus).", + "en": "Deployment failed (see the error above).", }, "SSH public key path": { "fr": "Chemin de la clé publique SSH",