mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-10 20:27:15 +01:00
Some checks failed
dependency freshness / dependencies on newest stable (push) Has been cancelled
file length / file-length (push) Has been cancelled
md-naming / markdown naming convention (push) Has been cancelled
palette drift / palette (push) Has been cancelled
shared gate tests / gate unit tests (push) Has been cancelled
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
142 lines
6.8 KiB
Bash
Executable File
142 lines
6.8 KiB
Bash
Executable File
#!/bin/bash
|
|
# ============================================================================
|
|
# vm -- disposable Arch VM sandbox.
|
|
#
|
|
# Runs destructive system scripts (shutdown installers, chattr +i guards,
|
|
# pacman hooks) against a throwaway Arch guest, and verifies the outcome from
|
|
# the HOST -- a guest that powers itself off cannot report its own success.
|
|
# ============================================================================
|
|
|
|
set -euo pipefail
|
|
|
|
VMBOX_BIN_DIR="$(cd "$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")" && pwd -P)"
|
|
readonly VMBOX_BIN_DIR
|
|
# shellcheck source=../lib/common.sh
|
|
source "$VMBOX_BIN_DIR/../lib/common.sh"
|
|
|
|
usage() {
|
|
cat <<'USAGE'
|
|
Usage: vm <command> [args]
|
|
|
|
build Build the golden base image (once, ~10 min)
|
|
new <name> [--rtc <ts>] [--uefi]
|
|
Create a sandbox (instant; thin overlay)
|
|
run <name> [--uefi|--bios] <command...>
|
|
Run a command in the sandbox, report the verdict
|
|
start <name> Boot the sandbox and wait until ssh answers
|
|
wake <name> [--accel N] [--max-wait S] [--] <command...>
|
|
Run a command that arms the RTC and powers off or
|
|
hibernates; power the guest back on when its
|
|
CMOS alarm fires (see DOCS-rtc-wake.md)
|
|
hibernate-setup <name> [--swap-mb N]
|
|
Swap + resume= so the guest can hibernate/resume
|
|
ssh <name> [args...] Interactive shell in the sandbox
|
|
scp <name> <src> <dest> Copy a host file into the sandbox
|
|
share <host-path> Expose a host dir to sandboxes (read-only bind)
|
|
unshare <name> Stop exposing it
|
|
repo <name> <path> [--worktree]
|
|
Copy a host repo in (clone=HEAD, --worktree=uncommitted)
|
|
screenshot <name> [out.png] Capture the guest screen (for locker tests)
|
|
lightdm <name> Switch the sandbox to lightdm autologin -> i3,
|
|
tty1 a plain getty (the host's login topology)
|
|
shim <name> <tool> [mode] Fake a hardware tool on PATH: exit[:N] | hang |
|
|
sleep:S; calls logged in /var/log/vmbox-shim/
|
|
reset <name> Wipe back to pristine (sub-second)
|
|
rm <name> Delete the sandbox entirely
|
|
list List sandboxes and their state
|
|
|
|
--rtc <ts> Pin the guest clock, e.g. 2026-08-22T20:55:00. Stored in the
|
|
VM's meta and re-applied on EVERY launch: the shutdown scripts
|
|
are gated on a 21:00-05:00 window, so an unpinned clock makes
|
|
their tests nondeterministic.
|
|
--uefi Boot with OVMF instead of SeaBIOS. On `new` it is stored in meta
|
|
(with a per-sandbox varstore); on `run` it is this launch only.
|
|
|
|
Examples:
|
|
vm new demo --rtc 2026-08-22T20:55:00
|
|
vm run demo 'sudo systemctl poweroff' # -> verdict: clean poweroff
|
|
vm reset demo # back to pristine
|
|
USAGE
|
|
}
|
|
|
|
# vm repo <name> <path> [--worktree]
|
|
cmd_repo() {
|
|
local name="${1:-}" src="${2:-}" mode=clone
|
|
[[ -n "$name" && -n "$src" ]] || die "usage: vm repo <name> <host-path> [--worktree]"
|
|
shift 2
|
|
[[ "${1:-}" == "--worktree" ]] && mode=worktree
|
|
repo_sync "$name" "$src" "$mode"
|
|
}
|
|
# Copy a file into a sandbox. lib/ssh.sh has always been able to do this;
|
|
# without a subcommand the only route was base64 through `vm run`.
|
|
cmd_scp() {
|
|
local name="${1:-}" src="${2:-}" dest="${3:-}"
|
|
[[ -n "$name" && -n "$src" && -n "$dest" ]] ||
|
|
die "usage: vm scp <name> <host-src> <guest-dest>"
|
|
name="$(validate_vm_name "$name")"
|
|
require_vm "$name"
|
|
[[ -e "$src" ]] || die "no such file: $src"
|
|
vm_is_running "$name" || die "sandbox '$name' is not running (start it with: vm run $name true)"
|
|
vm_scp_to "$name" "$src" "$dest" || die "copy failed"
|
|
ok "copied $src -> $name:$dest"
|
|
}
|
|
|
|
# Boot without running anything. VMBOX_START_TIMEOUT bounds the ssh wait.
|
|
cmd_start() {
|
|
local name
|
|
name="$(validate_vm_name "${1:-}")"
|
|
require_vm "$name"
|
|
vm_is_running "$name" && { log "sandbox '$name' is already running"; return 0; }
|
|
launch_vm "$name" >/dev/null
|
|
vm_wait_ssh "$name" "${VMBOX_START_TIMEOUT:-150}" ||
|
|
die "sandbox '$name' did not become reachable"
|
|
ok "sandbox '$name' is up"
|
|
}
|
|
|
|
cmd_list() {
|
|
[[ -d "$VMBOX_VMS_DIR" ]] || { echo "no sandboxes yet (create one: vm new <name>)"; return 0; }
|
|
local found=0 d name state rtc fw
|
|
printf '%-20s %-12s %-6s %s\n' "NAME" "STATE" "FW" "RTC"
|
|
for d in "$VMBOX_VMS_DIR"/*/; do
|
|
[[ -d "$d" ]] || continue
|
|
found=1
|
|
name="$(basename "$d")"
|
|
if vm_is_running "$name"; then state="running"; else state="stopped"; fi
|
|
rtc="$(meta_get "$name" rtc 2>/dev/null || echo '-')"
|
|
fw="$(meta_get "$name" firmware 2>/dev/null || echo bios)"
|
|
printf '%-20s %-12s %-6s %s\n' "$name" "$state" "${fw:-bios}" "${rtc:--}"
|
|
done
|
|
(( found )) || echo "(none)"
|
|
}
|
|
|
|
main() {
|
|
local cmd="${1:-}"
|
|
[[ $# -gt 0 ]] && shift || true
|
|
|
|
case "$cmd" in
|
|
build) require_host_deps; source "$VMBOX_LIB_DIR/base.sh"; base_build "$@" ;;
|
|
new) require_host_deps; source "$VMBOX_LIB_DIR/overlay.sh"; overlay_new "$@" ;;
|
|
reset) source "$VMBOX_LIB_DIR/overlay.sh"; overlay_reset "$@" ;;
|
|
rm) source "$VMBOX_LIB_DIR/overlay.sh"; overlay_rm "$@" ;;
|
|
run) require_host_deps; source "$VMBOX_LIB_DIR/run.sh"; run_in_vm "$@" ;;
|
|
start) require_host_deps; source "$VMBOX_LIB_DIR/launch.sh"
|
|
source "$VMBOX_LIB_DIR/ssh.sh"; cmd_start "$@" ;;
|
|
wake) require_host_deps; source "$VMBOX_LIB_DIR/rtcwake.sh"; cmd_wake "$@" ;;
|
|
hibernate-setup)
|
|
require_host_deps; source "$VMBOX_LIB_DIR/hibernate.sh"; cmd_hibernate_setup "$@" ;;
|
|
ssh) source "$VMBOX_LIB_DIR/ssh.sh"; vm_ssh_interactive "$@" ;;
|
|
scp) source "$VMBOX_LIB_DIR/ssh.sh"; cmd_scp "$@" ;;
|
|
screenshot) source "$VMBOX_LIB_DIR/screenshot.sh"; vm_screenshot "$@" ;;
|
|
lightdm) require_host_deps; source "$VMBOX_LIB_DIR/desktop.sh"; cmd_lightdm "$@" ;;
|
|
shim) require_host_deps; source "$VMBOX_LIB_DIR/desktop.sh"; cmd_shim "$@" ;;
|
|
repo) source "$VMBOX_LIB_DIR/repo.sh"; cmd_repo "$@" ;;
|
|
share) source "$VMBOX_LIB_DIR/repo.sh"; repo_share "$@" ;;
|
|
unshare) source "$VMBOX_LIB_DIR/repo.sh"; repo_unshare "$@" ;;
|
|
list|ls) cmd_list ;;
|
|
-h|--help|help|'') usage ;;
|
|
*) die "unknown command '$cmd' (try: vm --help)" ;;
|
|
esac
|
|
}
|
|
|
|
main "$@"
|