#!/bin/bash # Test virtual machine for the built image (build/acpc2026.qcow2). # # tests/check_vm.sh start [--wrong-clock] [--usb] boot a throwaway copy # tests/check_vm.sh stop power it off, delete the copy # # The copy (tests/work/check.qcow2) opens SSH for the tests: password login, # no DenyGroups, and the firewall accepts tcp 22. The built image itself is # not modified. --wrong-clock starts with the hardware clock 3 hours behind # (as on a lab machine running Windows), --usb adds a USB controller so that # USB devices can be plugged in with tests/usb.sh. set -eu cd "$(dirname "$0")/.." W=tests/work MON=$W/monitor.sock BUILT=$(realpath build/acpc2026.qcow2) running() { pgrep -f "^qemu-system-x86_64 -name acpc2026-check" >/dev/null; } case "${1:-}" in start) shift running && { echo "The test VM is already running." >&2; exit 1; } mkdir -p "$W" qemu-img create -q -f qcow2 -b "$BUILT" -F qcow2 "$W/check.qcow2" for d in /usr/share/edk2/x64 /usr/share/OVMF /usr/share/edk2/ovmf; do for c in OVMF_CODE.4m.fd OVMF_CODE_4M.fd OVMF_CODE.fd; do [ -f "$d/$c" ] && { CODE=$d/$c; VARS=$d/${c/CODE/VARS}; break 2; } done done cp "$VARS" "$W/vars.fd" # Open SSH in the copy, offline touch "$W/fuse.raw" qemu-storage-daemon --blockdev driver=file,node-name=f,filename="$W/check.qcow2" \ --blockdev driver=qcow2,node-name=q,file=f \ --export type=fuse,id=e,node-name=q,mountpoint="$W/fuse.raw",writable=on > "$W/qsd.log" 2>&1 & for _ in $(seq 1 20); do mountpoint -q "$W/fuse.raw" && break; sleep 0.5; done start=$(sfdisk -J "$W/fuse.raw" | python3 -I -c 'import json,sys; p=json.load(sys.stdin)["partitiontable"]["partitions"]; print(max(p,key=lambda x:x["size"])["start"])') D="$W/fuse.raw?offset=$((start * 512))" printf 'PasswordAuthentication yes\n' > "$W/00-test.conf" w() { debugfs -w -R "$1" "$D" >/dev/null 2>&1 || true; } w "rm /etc/ssh/sshd_config.d/icpc.conf" w "write $W/00-test.conf /etc/ssh/sshd_config.d/00-test.conf" w "mkdir /etc/systemd/system/firewall.service.d" w "write tests/test-ssh.conf /etc/systemd/system/firewall.service.d/test-ssh.conf" for f in /etc/ssh/sshd_config.d/00-test.conf /etc/systemd/system/firewall.service.d/test-ssh.conf; do for x in "mode 0100644" "uid 0" "gid 0"; do w "set_inode_field $f $x"; done done for x in "mode 040755" "uid 0" "gid 0"; do w "set_inode_field /etc/systemd/system/firewall.service.d $x"; done fusermount3 -u "$W/fuse.raw" pkill -f "^qemu-storage-daemon .*$W/check.qcow2" || true sleep 1 EXTRA=() for a in "$@"; do case "$a" in --wrong-clock) EXTRA+=(-rtc "base=$(date -u -d '-3 hours' +%Y-%m-%dT%H:%M:%S)") ;; --usb) EXTRA+=(-device qemu-xhci,id=xhci) ;; esac done setsid qemu-system-x86_64 -name acpc2026-check -machine q35,accel=kvm -cpu host -m 6144 -smp 4 \ -drive if=pflash,format=raw,readonly=on,file="$CODE" \ -drive if=pflash,format=raw,file="$W/vars.fd" \ -drive id=disk0,file="$W/check.qcow2",format=qcow2,if=none \ -device virtio-blk-pci,drive=disk0,bootindex=1 \ -netdev user,id=net0,hostfwd=tcp:127.0.0.1:2222-:22 -device virtio-net-pci,netdev=net0 \ -vga virtio -display none -monitor unix:"$MON",server,nowait \ -serial file:"$W/serial.log" "${EXTRA[@]}" > "$W/qemu.log" 2>&1 & for _ in $(seq 1 120); do tests/vssh.sh team true 2>/dev/null && break sleep 3 done # Wait for the desktop session of the team account for _ in $(seq 1 60); do tests/vssh.sh team 'loginctl list-sessions --no-legend' 2>/dev/null | grep -q seat0 && break sleep 2 done echo "Test VM ready (SSH 127.0.0.1:2222, monitor $MON)." ;; stop) if running; then python3 -I tests/monitor.py "$MON" quit || true for _ in $(seq 1 30); do running || break; sleep 1; done fi rm -rf "$W" echo "Test VM stopped." ;; *) sed -n '2,12p' "$0" | sed 's/^# \{0,1\}//' exit 2 ;; esac