mirror of
https://github.com/kuhyx/utils.git
synced 2026-11-11 17:14:03 +01:00
The peer-address service read a `vmbox.peer=` kernel argument that the launcher never passed -- the reader was written and the writer forgotten -- so eth1 came up DOWN with no address on every guest. It now derives the address from the NIC's own MAC (the host allocates 52:54:00:be:ef:<index>), which removes the coupling entirely: nothing has to be passed in, so the launcher and the guest cannot disagree about which index a VM has. That fixed the addressing but NOT the connectivity. `mcast=` cannot work on this host at all -- `lo` carries no MULTICAST flag and there is no multicast route, so qemu accepts the option and silently drops every frame, which is the same "flags accepted, nothing happens" trap as POWERDOWN. A listen=/connect= socket hub over loopback TCP was tried next: the hub port listens, both guests bring eth1 up with the right addresses, and ARP still fails. Documented as a known limitation rather than papered over. Sandboxes work fully on their own; two guests cannot talk to each other yet. Also corrects the destructive demo: a running Linux SURVIVES `rm -rf /usr` because processes hold their binaries through open file handles, so sshd keeps answering. The honest proof of destruction is that the machine cannot boot again, so the demo now reboots it and asserts it does not come back. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdyMBGCrGVGDWtJ9xe48f3
94 lines
3.8 KiB
Bash
Executable File
94 lines
3.8 KiB
Bash
Executable File
#!/bin/bash
|
|
|
|
# ============================================================================
|
|
# vmbox end-to-end demo: break a sandbox as thoroughly as possible, verify the
|
|
# breakage, reset it, and prove the host was never touched.
|
|
#
|
|
# This is the acceptance test for the whole tool. It is deliberately
|
|
# destructive INSIDE the guest -- that is the point -- and it asserts after
|
|
# each step rather than just printing output.
|
|
# ============================================================================
|
|
|
|
set -uo pipefail
|
|
|
|
VMBOX_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd -P)"
|
|
readonly VMBOX_ROOT
|
|
readonly VM="${VM:-destruct}"
|
|
readonly VM_CLI="$VMBOX_ROOT/bin/vm"
|
|
|
|
# Host-side facts captured up front, re-checked at the end.
|
|
HOST_HOSTS_ATTRS="$(lsattr /etc/hosts 2>/dev/null | awk '{print $1}')"
|
|
readonly HOST_HOSTS_ATTRS
|
|
BASE_SHA_BEFORE="$(sha256sum "$HOME/.local/share/vmbox/base/base.qcow2" 2>/dev/null | awk '{print $1}')"
|
|
readonly BASE_SHA_BEFORE
|
|
|
|
pass=0
|
|
fail=0
|
|
|
|
step() { printf '\n\033[0;34m=== %s\033[0m\n' "$*"; }
|
|
check() {
|
|
local label="$1" expected="$2" actual="$3"
|
|
if [[ "$actual" == *"$expected"* ]]; then
|
|
printf '\033[0;32m PASS\033[0m %s\n' "$label"
|
|
pass=$(( pass + 1 ))
|
|
else
|
|
printf '\033[0;31m FAIL\033[0m %s\n wanted: %s\n got: %s\n' \
|
|
"$label" "$expected" "$actual"
|
|
fail=$(( fail + 1 ))
|
|
fi
|
|
}
|
|
|
|
step "0. Fresh sandbox, clock pinned into the shutdown window"
|
|
"$VM_CLI" rm "$VM" >/dev/null 2>&1
|
|
"$VM_CLI" new "$VM" --rtc 2026-08-22T20:55:00
|
|
|
|
step "1. Baseline: the guest is a healthy Arch system"
|
|
out="$("$VM_CLI" run "$VM" 'cat /etc/os-release | head -1' 2>&1)"
|
|
check "guest is Arch Linux" "Arch Linux" "$out"
|
|
|
|
step "2. DESTROY: wipe /etc and /usr"
|
|
overlay="$HOME/.local/share/vmbox/vms/$VM/overlay.qcow2"
|
|
size_before="$(stat -c %s "$overlay" 2>/dev/null || echo 0)"
|
|
"$VM_CLI" run "$VM" 'sudo rm -rf --no-preserve-root /etc /usr 2>/dev/null; true' 2>&1 | tail -3
|
|
|
|
step "3. Confirm the guest really is broken -- from the HOST"
|
|
# Deliberately NOT by asking the guest: rm -rf /usr removed bash, sshd and
|
|
# agetty, so nothing is left in there to answer with. A corpse cannot report
|
|
# its own death. Two host-side facts prove it instead.
|
|
size_after="$(stat -c %s "$overlay" 2>/dev/null || echo 0)"
|
|
check "overlay recorded the destruction" "GREW" \
|
|
"$([[ "$size_after" -gt "${size_before:-0}" ]] && echo GREW || echo "same:$size_after")"
|
|
|
|
# A RUNNING Linux survives `rm -rf /usr` for a while: processes keep their
|
|
# binaries alive through open file handles, so sshd answers until it exits.
|
|
# The honest proof of destruction is that the machine cannot come back --
|
|
# reboot it and it will not return.
|
|
#
|
|
# Do not recreate the sandbox here: `vm reset` in step 4 is the claim under
|
|
# test, and recreating would quietly test something easier.
|
|
"$VM_CLI" run "$VM" 'sudo systemctl reboot' >/dev/null 2>&1 || true
|
|
if timeout 180 "$VM_CLI" run "$VM" 'echo alive' 2>&1 | grep -q alive; then
|
|
check "wiped guest cannot boot again" "DEAD" "it came back up"
|
|
else
|
|
check "wiped guest cannot boot again" "DEAD" "DEAD"
|
|
fi
|
|
|
|
step "4. RESET -- one command"
|
|
time_start=$SECONDS
|
|
"$VM_CLI" reset "$VM"
|
|
printf ' reset took %ds\n' "$(( SECONDS - time_start ))"
|
|
|
|
step "5. Confirm the sandbox is pristine again"
|
|
out="$("$VM_CLI" run "$VM" 'cat /etc/os-release | head -1' 2>&1)"
|
|
check "guest restored to Arch Linux" "Arch Linux" "$out"
|
|
|
|
step "6. THE HOST MUST BE UNTOUCHED"
|
|
now_attrs="$(lsattr /etc/hosts 2>/dev/null | awk '{print $1}')"
|
|
check "host /etc/hosts attributes unchanged" "$HOST_HOSTS_ATTRS" "$now_attrs"
|
|
now_sha="$(sha256sum "$HOME/.local/share/vmbox/base/base.qcow2" 2>/dev/null | awk '{print $1}')"
|
|
check "base image unmodified" "$BASE_SHA_BEFORE" "$now_sha"
|
|
check "host /etc still exists" "PRESENT" "$([[ -d /etc && -d /usr ]] && echo PRESENT)"
|
|
|
|
printf '\n\033[0;34m=== RESULT: %d passed, %d failed\033[0m\n' "$pass" "$fail"
|
|
exit $(( fail > 0 ? 1 : 0 ))
|