[FIX] script todo: reliable curl/git/make install + copyable logs

Root cause of "git/make: command not found": the bootstrap ran
"apt-get update -qq && apt-get install -y $PKGS". Inside an && list,
set -e does NOT abort on failure, so when apt-get update failed (flaky
VM network) the install was silently skipped and the script marched on
without git/make. Now: "apt-get update || true; apt-get install -y
$PKGS" (update best-effort, install mandatory), followed by an explicit
"command -v curl git make" check that exits with a clear message
("Outil manquant ... (reseau de la VM ?)") instead of a cryptic failure
later. Shared by the streamed and the monitored install paths.

Dashboard: add "c" to copy the selected VM's full log to the clipboard
(OSC 52, works over SSH) with a notification; the ssh bar documents
Shift+drag for native terminal selection; on close, print a ready-to-
copy "tail -n +1 <logdir>/*.log" to share the logs.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Mathieu Benoit 2026-07-28 04:40:18 +00:00
parent d4d3d801c3
commit 0830c895eb
3 changed files with 39 additions and 3 deletions

View file

@ -133,6 +133,7 @@ def run_monitor(manifest_path: str) -> None:
("q", "quit", "Quitter (détaché)"),
("s", "ssh", "SSH"),
("f", "follow", "Suivre"),
("c", "copy_log", "Copier log"),
]
def __init__(self):
@ -171,7 +172,10 @@ def run_monitor(manifest_path: str) -> None:
vm = self._vm_by_name(self._selected)
bar = self.query_one("#sshbar", Static)
if vm:
bar.update(f" {vm['ssh']} (s = ouvrir SSH)")
bar.update(
f" {vm['ssh']} (s = SSH · c = copier le log · "
"Maj+glisser = sélectionner)"
)
def _load_selected_log(self, reset=False):
vm = self._vm_by_name(self._selected)
@ -229,4 +233,20 @@ def run_monitor(manifest_path: str) -> None:
with self.suspend():
os.system(f"ssh {SSH_OPTS} erplibre@{vm['ip']} || true")
def action_copy_log(self) -> None:
"""Copie le log complet de la VM sélectionnée dans le
presse-papiers (OSC 52 ; marche aussi à travers SSH)."""
vm = self._vm_by_name(self._selected)
if not vm:
return
try:
text = Path(vm["log"]).read_text(errors="replace")
except OSError:
return
self.copy_to_clipboard(text)
self.notify(
f"Log de {vm['name']} copié ({len(text)} car.)",
title="Presse-papiers",
)
Monitor().run()

View file

@ -1677,13 +1677,22 @@ class TODO:
# (Fedora). apt-get update car cloud-init n'a pas rafraîchi apt.
"PKGS='curl git make'; "
"if command -v apt-get >/dev/null 2>&1; then "
"sudo apt-get update -qq && sudo apt-get install -y $PKGS; "
# update best-effort (|| true) puis install OBLIGATOIRE : sans le
# « || true », un « apt-get update » en échec (réseau) sautait
# l'install sans erreur (liste &&) -> git/make absents ensuite.
"sudo apt-get update -qq || true; "
"sudo apt-get install -y $PKGS; "
"elif command -v dnf >/dev/null 2>&1; then "
"sudo dnf install -y $PKGS; "
"elif command -v yum >/dev/null 2>&1; then "
"sudo yum install -y $PKGS; "
"else echo 'Aucun gestionnaire de paquets (apt/dnf/yum)'; "
"exit 1; fi; "
# Vérifie explicitement que tout est là : erreur nette plutôt
# qu'un « command not found » cryptique plus loin.
"for t in curl git make; do command -v $t >/dev/null 2>&1 || "
'{ echo "Outil manquant apres installation: $t '
'(reseau de la VM ?)"; exit 1; }; done; '
"mkdir -p ~/git; "
"if [ ! -d ~/git/erplibre/.git ]; then "
f"git clone --branch {shlex.quote(branch)} "
@ -1725,7 +1734,10 @@ class TODO:
print(
f"\n{t('Monitor closed. Installs keep running in the background.')}"
)
print(f" {t('Logs:')} {os.path.dirname(manifest)}")
logdir = os.path.dirname(manifest)
print(f" {t('Logs:')} {logdir}")
# Commande prête à copier pour relire/partager tous les logs.
print(f" {t('Read the logs:')} tail -n +1 {logdir}/*.log")
def _qemu_install_erplibre_vm(self, name, ssh_key, branch):
"""Clone ERPLibre (branche donnée) dans ~/git/erplibre de la VM puis

View file

@ -1443,6 +1443,10 @@ TRANSLATIONS = {
"fr": "Logs :",
"en": "Logs:",
},
"Read the logs:": {
"fr": "Lire les logs :",
"en": "Read the logs:",
},
"Install textual for the dashboard (pip).": {
"fr": "Installez textual pour le dashboard (pip).",
"en": "Install textual for the dashboard (pip).",