mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 19:07:17 +01:00
Much of testsAndMisc/linux_configuration cannot be tested on the host: 15 files use `chattr +i` (so a bad test is not simply revertable), the shutdown installers write units that power the machine off, and boot_recovery installs pacman hooks. The repo's own CI says as much -- its Ubuntu job runs only the side-effect-free tests, noting the rest "drive pacman, /etc, systemd or adb". vmbox runs them in a throwaway Arch guest and verifies the result from the HOST, which is the part that is not obvious: a successful shutdown kills the connection carrying the report, and ssh returns 255 for a clean poweroff, a crash and a dropped link alike. So the verdict comes from two artefacts that outlive the VM -- a QMP event log written by a recorder attached at launch (attach later and qemu has already exited), and the serial console log. Five outcomes are distinguished: clean poweroff (SHUTDOWN guest:true plus a serial tail reaching "reboot: Power down"), dirty stop, guest-reset (rebooted instead of powering off), GUEST_PANICKED, and hung. A POWERDOWN event proves nothing -- it means the request was delivered, not that anything acted on it. Reset is a qcow2 backing-file trick: the golden image is built once and sealed (chmod 444 plus a sha256 sidecar verified on every launch, since booting the base would silently invalidate every overlay), and each sandbox is a thin overlay, so reset is rm + recreate. No root anywhere -- user-mode networking with per-VM forwarded ssh ports, and a socket multicast segment for VM-to-VM. The guest clock is pinned and re-applied on every launch, because the shutdown installers gate on a 21:00-05:00 window; NTP is masked in the image or it silently undoes the pin. Repos are cloned from a read-only share scoped to an explicit directory, never $HOME, and credentials are sentinel values. `vm build` ends by booting a throwaway sandbox off the sealed image and refusing to ship if ssh does not answer. Several distinct faults produced an identical "boots fine, connection reset" symptom; checks inside the build VM only prove state at seal time, so the gate has to be an end-to-end boot. Tests: 8 bats cases over the verdict table and name/meta/serial handling; shellcheck and ruff clean; every file within the 250-line cap. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
65 lines
2.6 KiB
Bash
65 lines
2.6 KiB
Bash
#!/bin/bash
|
|
# ============================================================================
|
|
# vmbox: get a host repo into a sandbox.
|
|
#
|
|
# Two modes, because they answer different questions:
|
|
# clone (default) -- reproducible: tests exactly what is committed at HEAD
|
|
# worktree -- tests what is on disk right now, uncommitted edits and
|
|
# all, which is usually what "does this script work?" means
|
|
#
|
|
# The host side is always mounted READ-ONLY, so nothing the guest does can
|
|
# reach the real repo.
|
|
# ============================================================================
|
|
|
|
# shellcheck source=common.sh
|
|
source "$(dirname "${BASH_SOURCE[0]}")/common.sh"
|
|
source "$(dirname "${BASH_SOURCE[0]}")/ssh.sh"
|
|
|
|
readonly GUEST_MOUNT="/mnt/hostrepo"
|
|
|
|
# $1 vm name, $2 host repo path, $3 mode (clone|worktree)
|
|
repo_sync() {
|
|
local name="$1" src="$2" mode="${3:-clone}" repo_name
|
|
require_vm "$name"
|
|
[[ -d "$src" ]] || die "no such directory: $src"
|
|
repo_name="$(basename "$src")"
|
|
|
|
vm_is_running "$name" || die "sandbox '$name' is not running"
|
|
|
|
# Warn about a dirty tree: another session's uncommitted work is a real
|
|
# hazard, and it silently changes what a `clone` actually tests.
|
|
if [[ -d "$src/.git" ]] && ! git -C "$src" diff --quiet 2>/dev/null; then
|
|
warn "host repo has uncommitted changes"
|
|
[[ "$mode" == clone ]] &&
|
|
warn " mode=clone tests HEAD, NOT those edits (use --worktree for them)"
|
|
fi
|
|
|
|
case "$mode" in
|
|
clone) _repo_clone "$name" "$src" "$repo_name" ;;
|
|
worktree) _repo_rsync "$name" "$src" "$repo_name" ;;
|
|
*) die "unknown repo mode '$mode' (use: clone | worktree)" ;;
|
|
esac
|
|
}
|
|
|
|
_repo_clone() {
|
|
local name="$1" src="$2" repo_name="$3"
|
|
[[ -d "$src/.git" ]] || die "$src is not a git repo (use --worktree instead)"
|
|
log "Cloning $repo_name into '$name' (HEAD, reproducible)"
|
|
# --no-hardlinks: never link into the read-only host mount.
|
|
vm_ssh_exec "$name" \
|
|
"rm -rf ~/$repo_name && git clone --no-hardlinks -q $GUEST_MOUNT/$repo_name ~/$repo_name" ||
|
|
die "clone failed"
|
|
local head
|
|
head="$(vm_ssh_exec "$name" "git -C ~/$repo_name rev-parse --short HEAD" 2>/dev/null)"
|
|
ok "$repo_name cloned at $head (committed state)"
|
|
}
|
|
|
|
_repo_rsync() {
|
|
local name="$1" src="$2" repo_name="$3"
|
|
log "Copying $repo_name into '$name' (working tree, including uncommitted edits)"
|
|
vm_ssh_exec "$name" \
|
|
"rsync -a --exclude .git $GUEST_MOUNT/$repo_name/ ~/$repo_name/" ||
|
|
die "rsync failed"
|
|
ok "$repo_name copied (working-tree state)"
|
|
}
|