[IMP] script todo: FS invité robuste (heartbeat+console), temps/version, i18n versions
Trois améliorations QEMU :
- Extension du FS invité robuste : résolution d'IP avec BATTEMENT
(_qemu_resolve_ips, parallèle, boot émulé lent) au lieu d'un timeout
court ; en cas d'absence d'IP ou d'échec SSH, repli sur la CONSOLE
SÉRIE avec la commande growpart/resize prête à coller (login
erplibre/erplibre). Commande factorisée dans _GROW_FS_REMOTE.
- Temps estimés PAR VERSION : nouveau mon.avg_by_version(distro, version)
et _qemu_stat_avg("version", v, distro) -> suffixe « · ~46s moy (1) »
dans les listes de versions (prompt simple + granulaire).
- i18n : « Versions for » n'avait AUCUNE traduction (restait en anglais).
Ajout FR « Versions des » (+ « Version des ») et capitalisation de la
distro -> « Versions des Ubuntu : ».
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a0c560b15f
commit
3f20497912
3 changed files with 99 additions and 33 deletions
|
|
@ -265,6 +265,16 @@ def avg_by_distro(distro, stats=None):
|
|||
return (sum(secs) / len(secs), len(secs)) if secs else (None, 0)
|
||||
|
||||
|
||||
def avg_by_version(distro, version, stats=None):
|
||||
"""(moyenne_secondes, nb_runs) pour cette (distro, version), ou (None, 0)."""
|
||||
secs = [
|
||||
r["seconds"]
|
||||
for r in _runs(stats)
|
||||
if r.get("distro") == distro and r.get("version") == version
|
||||
]
|
||||
return (sum(secs) / len(secs), len(secs)) if secs else (None, 0)
|
||||
|
||||
|
||||
def last_run(stats=None):
|
||||
"""Dernier run enregistré (dict) ou None."""
|
||||
runs = _runs(stats)
|
||||
|
|
|
|||
|
|
@ -911,10 +911,11 @@ class TODO:
|
|||
def _qemu_prompt_version(self, distro):
|
||||
"""Demande la version pour la distro (défaut = version par défaut)."""
|
||||
versions, default = self._QEMU_DISTROS.get(distro, ([], ""))
|
||||
print(f"\n{t('Version for')} {distro} :")
|
||||
print(f"\n{t('Version for')} {distro.capitalize()} :")
|
||||
for i, v in enumerate(versions, 1):
|
||||
suffix = " *" if v == default else ""
|
||||
print(f" [{i}] {v}{suffix}")
|
||||
stat = self._qemu_stat_avg("version", v, distro)
|
||||
print(f" [{i}] {v}{suffix}{stat}")
|
||||
sel = input(
|
||||
f"{t('Choice (number or version, blank = default):')} "
|
||||
).strip()
|
||||
|
|
@ -978,14 +979,19 @@ class TODO:
|
|||
pass
|
||||
return ""
|
||||
|
||||
def _qemu_stat_avg(self, field, value):
|
||||
def _qemu_stat_avg(self, field, value, distro=None):
|
||||
"""Suffixe « · ~5m moy (3) » : durée d'install MOYENNE historique pour
|
||||
cette archi/distro (fichier .venv.erplibre), ou '' si aucune donnée."""
|
||||
cette archi/distro/version (fichier .venv.erplibre), ou '' si aucune
|
||||
donnée. Pour field='version', `distro` est requis."""
|
||||
try:
|
||||
from script.todo import qemu_install_monitor as mon
|
||||
|
||||
fn = mon.avg_by_arch if field == "arch" else mon.avg_by_distro
|
||||
secs, n = fn(value)
|
||||
if field == "arch":
|
||||
secs, n = mon.avg_by_arch(value)
|
||||
elif field == "version":
|
||||
secs, n = mon.avg_by_version(distro, value)
|
||||
else:
|
||||
secs, n = mon.avg_by_distro(value)
|
||||
if secs:
|
||||
return f" · ~{mon._fmt_secs(secs)} {t('avg')} ({n})"
|
||||
except Exception:
|
||||
|
|
@ -1582,32 +1588,61 @@ class TODO:
|
|||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(cmd, source_erplibre=False)
|
||||
|
||||
# Commande d'extension du FS racine (partition + FS) réutilisée par SSH
|
||||
# et par le repli console série.
|
||||
_GROW_FS_REMOTE = (
|
||||
"set -e; "
|
||||
"root=$(findmnt -no SOURCE /); "
|
||||
"dev=$(lsblk -no PKNAME \"$root\" | head -1); "
|
||||
"part=$(echo \"$root\" | grep -oE '[0-9]+$'); "
|
||||
"sudo growpart /dev/$dev $part || true; "
|
||||
"fstype=$(findmnt -no FSTYPE /); "
|
||||
'case "$fstype" in '
|
||||
"ext*) sudo resize2fs \"$root\";; "
|
||||
"xfs) sudo xfs_growfs /;; "
|
||||
"btrfs) sudo btrfs filesystem resize max /;; "
|
||||
'esac; '
|
||||
"df -h /"
|
||||
)
|
||||
|
||||
def _qemu_grow_guest_fs(self, name):
|
||||
"""Étend la partition racine + le FS invité via SSH (growpart +
|
||||
resize2fs/xfs_growfs/btrfs). Détecte le device et le type de FS."""
|
||||
ip = self._qemu_vm_ip(name, timeout=120)
|
||||
if not ip:
|
||||
print(t("No IP; grow the guest FS manually once booted."))
|
||||
"""Étend la partition racine + le FS invité. Essaie SSH (IP résolue
|
||||
avec BATTEMENT, le boot émulé étant lent) ; en cas d'absence d'IP ou
|
||||
d'échec SSH, propose le repli par CONSOLE SÉRIE (commande à coller)."""
|
||||
remote = self._GROW_FS_REMOTE
|
||||
real = self._qemu_domname(name)
|
||||
# Résolution d'IP robuste (parallèle + battement toutes les 30 s)
|
||||
# plutôt qu'un simple timeout court qui abandonnait trop tôt.
|
||||
ip = self._qemu_resolve_ips([real], timeout=300).get(real)
|
||||
if ip:
|
||||
opts = (
|
||||
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
||||
"-o ConnectTimeout=15"
|
||||
)
|
||||
cmd = f"ssh {opts} erplibre@{ip} {shlex.quote(remote)}"
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
if self.execute.exec_command_live(cmd, source_erplibre=False) == 0:
|
||||
return
|
||||
print(f"⚠ {t('SSH grow failed; falling back to serial console.')}")
|
||||
else:
|
||||
print(t("No IP; falling back to serial console."))
|
||||
self._qemu_grow_via_console(real, remote)
|
||||
|
||||
def _qemu_grow_via_console(self, name, remote):
|
||||
"""Repli console série : affiche la commande prête à coller puis ouvre
|
||||
la console (login interactif erplibre/erplibre — pas d'automatisation
|
||||
fiable de la saisie)."""
|
||||
print(f"\n{t('Serial console fallback. Log in, then paste:')}")
|
||||
print(f"\n {remote}\n")
|
||||
print(f"💡 {t('To leave the console, press Ctrl+] (then Enter).')}")
|
||||
print(
|
||||
f"👤 {t('Default login (if set at deploy): erplibre / erplibre')}"
|
||||
)
|
||||
if not self._is_yes(
|
||||
input(t("Open the serial console now? (y/N): "))
|
||||
):
|
||||
return
|
||||
remote = (
|
||||
"set -e; "
|
||||
"root=$(findmnt -no SOURCE /); "
|
||||
"dev=$(lsblk -no PKNAME \"$root\" | head -1); "
|
||||
"part=$(echo \"$root\" | grep -oE '[0-9]+$'); "
|
||||
"sudo growpart /dev/$dev $part || true; "
|
||||
"fstype=$(findmnt -no FSTYPE /); "
|
||||
'case "$fstype" in '
|
||||
"ext*) sudo resize2fs \"$root\";; "
|
||||
"xfs) sudo xfs_growfs /;; "
|
||||
"btrfs) sudo btrfs filesystem resize max /;; "
|
||||
'esac; '
|
||||
"df -h /"
|
||||
)
|
||||
opts = (
|
||||
"-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null "
|
||||
"-o ConnectTimeout=15"
|
||||
)
|
||||
cmd = f"ssh {opts} erplibre@{ip} {shlex.quote(remote)}"
|
||||
cmd = f"sudo virsh console {shlex.quote(name)}"
|
||||
print(f"{t('Will execute:')} {cmd}")
|
||||
self.execute.exec_command_live(cmd, source_erplibre=False)
|
||||
|
||||
|
|
@ -2804,10 +2839,11 @@ class TODO:
|
|||
if catalog_all:
|
||||
chosen = vlist
|
||||
else:
|
||||
print(f"\n{t('Versions for')} {d} :")
|
||||
print(f"\n{t('Versions for')} {d.capitalize()} :")
|
||||
for i, v in enumerate(vlist, 1):
|
||||
_c, _o, ram, disk = versions_map[v]
|
||||
print(f" [{i}] {v} (RAM≥{ram}Mo, {disk})")
|
||||
stat = self._qemu_stat_avg("version", v, d)
|
||||
print(f" [{i}] {v} (RAM≥{ram}Mo, {disk}){stat}")
|
||||
print(f" [all] {t('select all')}")
|
||||
r = input(
|
||||
t("Selection (numbers, or 'all', default: all): ")
|
||||
|
|
|
|||
|
|
@ -1171,9 +1171,13 @@ TRANSLATIONS = {
|
|||
"en": "Invalid selection, using ubuntu",
|
||||
},
|
||||
"Version for": {
|
||||
"fr": "Version pour",
|
||||
"fr": "Version des",
|
||||
"en": "Version for",
|
||||
},
|
||||
"Versions for": {
|
||||
"fr": "Versions des",
|
||||
"en": "Versions of",
|
||||
},
|
||||
"Architecture:": {
|
||||
"fr": "Architecture :",
|
||||
"en": "Architecture:",
|
||||
|
|
@ -1420,6 +1424,22 @@ TRANSLATIONS = {
|
|||
"fr": "Impossible de lire la taille actuelle du disque ; abandon.",
|
||||
"en": "Could not read current disk size; aborting.",
|
||||
},
|
||||
"SSH grow failed; falling back to serial console.": {
|
||||
"fr": "Échec de l'extension via SSH ; repli sur la console série.",
|
||||
"en": "SSH grow failed; falling back to serial console.",
|
||||
},
|
||||
"No IP; falling back to serial console.": {
|
||||
"fr": "Pas d'IP ; repli sur la console série.",
|
||||
"en": "No IP; falling back to serial console.",
|
||||
},
|
||||
"Serial console fallback. Log in, then paste:": {
|
||||
"fr": "Repli console série. Connectez-vous, puis collez :",
|
||||
"en": "Serial console fallback. Log in, then paste:",
|
||||
},
|
||||
"Open the serial console now? (y/N): ": {
|
||||
"fr": "Ouvrir la console série maintenant ? (o/N) : ",
|
||||
"en": "Open the serial console now? (y/N): ",
|
||||
},
|
||||
"The VM must be off. Shut it down and retry? (y/N): ": {
|
||||
"fr": "La VM doit être éteinte. L'éteindre et réessayer ? (o/N) : ",
|
||||
"en": "The VM must be off. Shut it down and retry? (y/N): ",
|
||||
|
|
|
|||
Loading…
Reference in a new issue