utils/vmbox/lib/smoke.sh
Krzysztof kuhy Rudnicki 0b1f86ae2c vmbox: add a serial transport so shutdown tests do not depend on sshd
The Arch cloud image leaves sshd enabled but never started -- no start line,
no failure line, dependencies satisfied. Four hypotheses (NTP, cloud-init
clean, a socket-disable, a blocking start) were each disproven by measurement,
so rather than keep paying a ~10 min rebuild per guess this adds the transport
the tool should arguably have used all along.

For the case vmbox exists for, serial beats ssh on the merits: it keeps
printing straight through the poweroff that kills an ssh connection, and it
works when sshd does not. `vm run` now picks ssh when it answers and the
serial console otherwise, and recovers the guest exit code either way. Guests
get a second serial port (ttyS1) on a unix socket with a root autologin, kept
separate from ttyS0 so driving a shell never interleaves with the append-only
log the verdict is read from.

serial_exec.py waits for an explicit per-command end marker rather than a
fixed sleep. That is not a detail: an earlier probe used a 4s window, the next
command was typed while the previous one still ran, and the tty echoed that
input -- which reads exactly like output in a transcript and produced a
confident but wrong conclusion. It also strips ANSI CSI and systemd's OSC 3008
shell-integration blocks, verified against a real captured boot log.

Also: a vmbox-sshd-kick unit that forces sshd up after multi-user.target as
belt and braces, and the smoke-test sandbox renamed to a name that actually
passes validate_vm_name -- "__smoke" was rejected, so the gate silently never
ran.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
2026-08-22 20:58:22 +02:00

50 lines
2.0 KiB
Bash

#!/bin/bash
# ============================================================================
# vmbox: post-seal verification.
#
# Split out of base.sh to stay under the 250-line cap.
# ============================================================================
# shellcheck source=common.sh
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
# Boot a real overlay off the freshly sealed base and require that ssh answers.
#
# This is the gate that matters. Every check inside the build VM only proves
# state at seal time; the failures that actually shipped all lived in the gap
# between "correct in the build VM" and "works on a fresh overlay boot". This
# catches missing host keys, a no-op systemctl enable, a clobbered NIC and
# whatever the next cause turns out to be, WITHOUT needing to know which.
base_smoke_test() {
log "Smoke-testing the sealed image (boots a throwaway sandbox)"
source "$VMBOX_LIB_DIR/overlay.sh"
source "$VMBOX_LIB_DIR/launch.sh"
source "$VMBOX_LIB_DIR/ssh.sh"
local smoke="vmbox-smoke"
vm_exists "$smoke" && overlay_rm "$smoke" >/dev/null 2>&1
overlay_new "$smoke" >/dev/null
local failed=0
launch_vm "$smoke" >/dev/null
if vm_wait_ssh "$smoke" 180 && vm_ssh_exec "$smoke" true 2>/dev/null; then
ok "smoke test passed: a fresh sandbox boots and accepts ssh"
else
failed=1
warn "SMOKE TEST FAILED -- the sealed image boots but is unreachable."
warn "Serial log: $(vm_serial "$smoke" 0)"
warn "Debug it with the serial console (root autologin is enabled there)."
fi
# On failure KEEP the sandbox: deleting it would destroy the serial log
# this very message points at, which is the only evidence available when
# ssh is the thing that is broken.
if (( failed == 0 )); then
overlay_rm "$smoke" >/dev/null 2>&1 || true
else
warn "keeping sandbox 'vmbox-smoke' so its logs survive; remove it with: vm rm vmbox-smoke"
die "refusing to ship an unreachable base image"
fi
}