From 35446538df588752c7d30d22efb2573d322b81f7 Mon Sep 17 00:00:00 2001 From: sjat Date: Thu, 18 Jun 2026 16:35:15 +0200 Subject: [PATCH] fix(integration-vm): apt-ready VMs + sudo-read serial console diagnostics cloud-init package_update:true + block on 'cloud-init status --wait' in up() so apply sees populated apt lists (fresh genericcloud images ship empty lists); dump_diagnostics()/console() read the root:0600 serial log via sudo instead of shutil.copy, which raised PermissionError mid-diagnostics. Co-Authored-By: Claude Opus 4.8 (1M context) --- scripts/integration-vm.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/scripts/integration-vm.py b/scripts/integration-vm.py index cb9f542..3cce0d3 100644 --- a/scripts/integration-vm.py +++ b/scripts/integration-vm.py @@ -78,7 +78,7 @@ def render_user_data(ssh_pubkey, ansible_user): " ssh_authorized_keys:\n" f" - {ssh_pubkey}\n" "ssh_pwauth: false\n" - "package_update: false\n" + "package_update: true\n" ) @@ -213,6 +213,9 @@ def up(host, name=None, mem_mib=DEFAULT_MEM_MIB, vcpus=DEFAULT_VCPUS): "--noautoconsole"]) ip = wait_for_ip(name) wait_for_ssh(ip, "ansible") + # Block until cloud-init finishes (incl. apt-get update) so apply sees a ready system. + sh(["ssh", "-o", "StrictHostKeyChecking=no", "-o", "UserKnownHostsFile=/dev/null", + f"ansible@{ip}", "sudo cloud-init status --wait"], check=False) (RUN_DIR / "current").write_text(f"{name}\n{ip}\n{host}\n") print(f"VM {name} up at {ip}") return name, ip @@ -341,7 +344,10 @@ def dump_diagnostics(name, ip): (d / f"{label}.txt").write_text((r.stdout or "") + (r.stderr or "")) console = CACHE_DIR / f"{name}-console.log" if console.exists(): - shutil.copy(console, d / "console.log") + # The serial log is root:0600 (libvirt-created); read it via sudo (ADR-015: the + # claude worker has sudo) and write a worker-owned copy into the bundle. + r = sh(["sudo", "cat", str(console)], check=False, capture=True) + (d / "console.log").write_text(r.stdout or "") print(f"diagnostics written to {d}", file=sys.stderr) @@ -377,7 +383,10 @@ def prune(): def console(): name = (RUN_DIR / "current").read_text().splitlines()[0] log = CACHE_DIR / f"{name}-console.log" - print(log.read_text() if log.exists() else f"no console log at {log}") + if log.exists(): + print(sh(["sudo", "cat", str(log)], check=False, capture=True).stdout or "") + else: + print(f"no console log at {log}") def cycle(host, certs, keep=False, no_reboot=False):