diff --git a/script/todo/qemu_install_monitor.py b/script/todo/qemu_install_monitor.py index d25ee20..2558bf0 100644 --- a/script/todo/qemu_install_monitor.py +++ b/script/todo/qemu_install_monitor.py @@ -98,6 +98,15 @@ def _launch_one( 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") + msg_giveup = t( + "cloud-init still running after 20 min - install starts anyway" + " (it waits for cloud-init first)" + ) + msg_novirsh = t( + "WARNING libvirt unreachable: the IP will not be refreshed" + " (libvirt group? re-login required)" + ) + msg_moved = t("DHCP lease moved:") # L'IP est RÉSOLUE À CHAQUE TOUR, jamais figée. Au 1er boot la VM prend un # bail sous le nom par défaut de l'image, puis cloud-init pose le vrai nom # d'hôte et le client DHCP en redemande un AUTRE. L'adresse connue au @@ -130,17 +139,38 @@ def _launch_one( 'vsh() { virsh --connect qemu:///system "$@" 2>/dev/null ' '|| sudo -n virsh --connect qemu:///system "$@" 2>/dev/null; }; ' ) + # Deux trous rendaient cette ré-résolution incapable de rattraper une IP + # qui bouge — le cas exact où elle sert : + # + # - « vsh » est muet des deux côtés. Quand libvirt est injoignable (hors du + # groupe libvirt, et « sudo -n » refusé faute de tty dans ce processus + # détaché), la ré-résolution ne renvoie JAMAIS rien : l'IP de départ est + # gardée jusqu'au bout sans qu'une seule ligne du log ne le dise. + # - le repli sur les baux exigeait une réponse sur le port 22. Or dnsmasq + # ne garde qu'un bail par MAC : quand cloud-init pose le vrai nom d'hôte + # et que le client DHCP redemande, le bail DÉPLACE l'adresse. L'ancienne + # n'appartient plus à cette VM, mais on l'attendait quand même — et sshd + # n'y répondra jamais. refresh = ( ( + f"raw=$(vsh domifaddr {name_q} --source lease); vrc=$?; " + 'if [ $vrc -ne 0 ] && [ -z "$vwarn" ]; then vwarn=1; ' + f"echo {shlex.quote(' ' + msg_novirsh)} >> {log_q}; fi; " + "cands=$(echo \"$raw\" | grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}' " + "| grep -v '^127\\.'); " f"n=$(vsh domifaddr {name_q} --source agent " "| grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}' " "| grep -v '^127\\.' | head -1); " 'if [ -z "$n" ]; then ' - f"for c in $(vsh domifaddr {name_q} --source lease " - "| grep -oE '([0-9]{1,3}\\.){3}[0-9]{1,3}' " - "| grep -v '^127\\.'); do " + "for c in $cands; do " 'timeout 2 bash -c "echo > /dev/tcp/$c/22" 2>/dev/null ' '&& n="$c"; done; fi; ' + # Le bail ne mentionne plus l'adresse courante : elle est périmée, + # on suit le bail sans attendre que sshd réponde. + 'if [ -z "$n" ] && [ -n "$cands" ] && ' + '! echo "$cands" | grep -Fqx "$ip"; then ' + 'n=$(echo "$cands" | tail -1); ' + f'echo " {msg_moved} $ip -> $n" >> {log_q}; fi; ' '[ -n "$n" ] && ip="$n"; ' ) if name @@ -151,17 +181,24 @@ def _launch_one( f"{vsh if name else ''}" f"echo {shlex.quote('== ' + msg_wait + ' ==')} >> {log_q}; " f"echo {shlex.quote(' ' + msg_slow)} >> {log_q}; " + f"seen=0; " f"for i in $(seq 1 240); do " f"{refresh}" 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"*done*|*disabled*|*error*|*degraded*|*nocloudinit*) seen=1; break;; " f"esac; " f"if [ $((i % 6)) -eq 0 ]; then " f'echo " ... $((i*5))s ($ip)" >> {log_q}; fi; ' f"sleep 5; done; " + # La boucle peut s'ÉPUISER au lieu de rompre : sous émulation, + # cloud-init dépasse volontiers 20 min. Annoncer « VM prête » dans les + # deux cas donnait un message faux juste avant le plus long silence du + # log — c'est l'installation qui attend alors la fin de cloud-init. + f'if [ "$seen" = 1 ]; then ' f"echo {shlex.quote('== ' + msg_ready + ' ==')} >> {log_q}; " + f"else echo {shlex.quote('== ' + msg_giveup + ' ==')} >> {log_q}; fi; " f'echo " → $ip" >> {log_q}; ' f'ssh {SSH_OPTS} "erplibre@$ip" {shlex.quote(remote_cmd)} ' f">> {log_q} 2>&1; " diff --git a/script/todo/todo.py b/script/todo/todo.py index 5a9b238..6b83475 100755 --- a/script/todo/todo.py +++ b/script/todo/todo.py @@ -4341,9 +4341,21 @@ class TODO: # tient le verrou apt/dnf/pacman -> sinon « unable to lock # database » (Arch) / « Could not get lock » (apt). timeout pour ne # pas bloquer indéfiniment si cloud-init traîne. - "command -v cloud-init >/dev/null 2>&1 && " + # + # Cette attente dure jusqu'à 15 min et n'écrivait RIEN : sur une + # architecture émulée, le log restait muet d'un quart d'heure juste + # après avoir annoncé le début de l'installation, ce qui se lit + # comme un blocage. On encadre donc l'attente de deux lignes, et le + # « status » final dit si elle a abouti ou expiré. + "if command -v cloud-init >/dev/null 2>&1; then " + + 'echo "== ' + + t("Waiting for cloud-init to finish (up to 15 min)") + + ' =="; ' "sudo timeout 900 cloud-init status --wait >/dev/null 2>&1 " "|| true; " + + f'echo " {t("cloud-init:")} $(cloud-init status 2>/dev/null ' + '| head -1)"; ' + "fi; " # Coupé AVANT les apt-get ci-dessous : sinon apt-daily peut reprendre # le verrou entre l'attente cloud-init et l'installation. + no_auto_upgrade + diff --git a/script/todo/todo_i18n.py b/script/todo/todo_i18n.py index 498841c..5fc4a74 100644 --- a/script/todo/todo_i18n.py +++ b/script/todo/todo_i18n.py @@ -1973,6 +1973,32 @@ TRANSLATIONS = { "fr": "RAM (Mo)", "en": "RAM (MB)", }, + "cloud-init still running after 20 min - install starts anyway" + " (it waits for cloud-init first)": { + "fr": "cloud-init tourne encore apres 20 min - l'installation demarre" + " quand meme (elle attend d'abord cloud-init)", + "en": "cloud-init still running after 20 min - install starts anyway" + " (it waits for cloud-init first)", + }, + "Waiting for cloud-init to finish (up to 15 min)": { + "fr": "Attente de la fin de cloud-init (jusqu'a 15 min)", + "en": "Waiting for cloud-init to finish (up to 15 min)", + }, + "cloud-init:": { + "fr": "cloud-init :", + "en": "cloud-init:", + }, + "WARNING libvirt unreachable: the IP will not be refreshed" + " (libvirt group? re-login required)": { + "fr": "ATTENTION libvirt injoignable : l'IP ne sera pas rafraichie" + " (groupe libvirt ? reconnexion necessaire)", + "en": "WARNING libvirt unreachable: the IP will not be refreshed" + " (libvirt group? re-login required)", + }, + "DHCP lease moved:": { + "fr": "bail DHCP deplace :", + "en": "DHCP lease moved:", + }, "An install is still running:": { "fr": "Une installation est encore en cours :", "en": "An install is still running:",