feat: add Linux clean-install test harness #534
Reference in New Issue
Block a user
Delete Branch "feat/linux-install-test-harness"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
What
Adds
scripts/linux-install-test.sh, a throwaway-VM harness that exercisesinstall.shthe way a brand-new user would across a Linux distro matrix (Ubuntu, Fedora, Arch, Alpine, NixOS), plus the research notedocs/research/testing-clean-install-on-linux.mdthat motivates the approach. This is the Linux counterpart to the macOS clean-install harness (#529).Why a disposable VM (not a throwaway user or a container)
On macOS the pick was a throwaway user account (nested virt is gated to M3+). On Linux the calculus flips:
qemu-img create -b base) is cheap — the Linux equivalent ofdocker run --rm, for a whole machine.How a run works
Cache one read-only base image → boot a throwaway overlay via QEMU/KVM with user-mode networking (no root, no bridge) → inject an ephemeral SSH key + passwordless login via a cloud-init seed ISO → install the distro's prerequisites (
python3 + git + pipx) → pipe this checkout'sinstall.shinto the guest exactly ascurl … | shwould → assert the CLI installed → delete the overlay. Because the overlay carries the OS-level prerequisites too, the reset is total.Subcommands
up— fetch/verify base image (cached), boot a fresh VMrun— install prereqs +install.sh+doctorin the VMstatus— VM reachable? entry point installed? runsdoctordown— kill the VM, delete overlay + seed (the reset)test—up → run → status → downfor one distro; arms an EXIT/INT/TERM trap the moment the VM exists, so a failure or Ctrl-C still tears it downtest-all— the matrix, each distro in its own subshell on its own forwarded port, with a PASS/FAIL summaryssh— an interactive shell in the running guestDistro selected via
BB_TEST_DISTRO; see the header for all env knobs (BB_TEST_KEEP,BB_TEST_INSTALL_URL,BOT_BOTTLE_INSTALL_SPEC, …).Scope: installer correctness, not runtime
There is no nested KVM/Docker in the VM, so
bot-bottle doctorcorrectly reports every backend not-ready and exits non-zero by design. The pass criterion is thereforeinstall.shexiting 0, thebot-bottleentry point present on the fresh user's PATH, andbot-bottle --versionrunning — not a green doctor. doctor's full output is still printed so a real installer regression (broken shim, import error) stays visible.Notes baked into the script
SHA256 (file) = hashformats). The NixOS channel image ships no stable sums file → useBB_TEST_SKIP_VERIFY=1.DISTROtable are the one place to bump when a distro cuts a newer build.Validated with
bash -nandshellcheck. Runtime is host-only (needs/dev/kvm,qemu,cloud-localds), so like the macOS harness it isn't exercised by the Linux PR CI.🤖 Generated with Claude Code
Splits the Linux clean-install harness into two variants, selected with BB_TEST_PREREQS: with (default) — install python3 + git + pipx first, then install.sh. The ready-host happy path; PASS = install.sh exits 0 and leaves a runnable bot-bottle entry point. without — run install.sh on the BARE cloud image. Exercises install.sh's own prerequisite-guard logic (python gate, git-for-git-specs gate, pipx/pip PEP-668 handling). PASS = install.sh either fully succeeds OR declines with one of its own recognized, actionable prerequisite errors; a crash or unrecognized failure is a FAIL. cmd_run now captures install.sh's exit code + full output (install.rc / install.log) instead of aborting on non-zero, so the verdict step applies the variant's criterion. The old assert_installed is replaced by a quiet entry_point_runnable helper plus classify_outcome, which matches the bare-host declines against the exact die() messages install.sh prints. doctor still runs for visibility, but only when an entry point exists. test-all now runs the full matrix -- every distro x both variants -- each cell in its own throwaway VM, and can be pinned to one variant via BB_TEST_PREREQS. Research note updated to document both variants and their pass criteria. Validated with `bash -n` and `shellcheck`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Replaces the BB_TEST_PREREQS env toggle with the two subcommands the macOS harness established, so the two repos read the same: test A bare host — prerequisites NOT set up (the default state of a stock cloud image). Exercises install.sh's own prerequisite-guard logic. SOUND (PASS) when install.sh either installs cleanly or declines with one of its own recognized, actionable errors; a crash or unrecognized failure fails. test-ready Prerequisites satisfied — the harness installs python3/git/pipx first, then runs install.sh, which must actually land: entry point runnable, doctor reporting a usable python and config. Structure now mirrors scripts/macos-install-test.sh: a shared _test_cycle driving up -> (prereqs) -> run -> verdict -> down, thin cmd_test / cmd_test_ready wrappers setting _STEPS / _PASS_CLAIM / _REQUIRE_INSTALL, a standalone `prereqs` subcommand, and a _test_teardown that prints the PASS/FAIL claim. The doctor check is now the macOS-style classifier rather than a bare exit-code read: a Traceback is an install defect (fail), a missing `ok: python:` / `ok: config:` line is a fail, and a not-ready backend is reported, not fatal (BB_TEST_REQUIRE_BACKEND=1 makes it fatal for a nested-virt host). This is where Linux diverges from macOS test-ready — a plain VM has no nested KVM for Firecracker and the harness doesn't provision the Docker backend, so backend readiness is never the Linux criterion. test-all now runs every distro × {test, test-ready}. Research note updated. Validated with `bash -n` and `shellcheck`. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>Completes the matrix: all five distros now pass both `test` and `test-ready`, validated end-to-end on the delphi KVM host (QEMU 11.0.2). Alpine — the generic_ cloud image silently ignores a NoCloud seed, so cloud-init never ran. Fixes, all confirmed by booting: - switch to the nocloud_ image variant (built for a local seed), - start sshd from a runcmd (OpenRC doesn't auto-start it after key injection), - give the account a throwaway password — Alpine's non-PAM sshd refuses pubkey auth for a cloud-init-locked ('!') account, unlike the UsePAM=yes distros, - install sudo via cloud-init packages: (the minimal image has none, so the test-ready `sudo apk add` prereq failed). Also add instance-id to the NoCloud meta-data, which the nocloud image requires. NixOS — publishes no downloadable cloud qcow2 (Hydra builds AMIs), so the harness now BUILDS one with nixos-generators (new scripts/linux-install-test-nixos.nix: cloud-init for the key, flakes enabled, deliberately no python/git/pipx). ensure_base_image branches to build_nixos_image for the nixos distro. The test-ready prereq uses `nix profile install` pinned to nixpkgs/nixos-24.11, because the guest's default unstable registry builds pipx from source and its test suite currently fails to build. Validation: 10/10 cells pass, clean teardown, no leaked VMs or run dirs. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>