All checks were successful
Check / eval (push) Successful in 3m18s
Treat the picker-only (none) row as an empty XKB variant at the shared input boundary and again in the template patcher. Guard the real downstream output and console keymap, and carry the sentinel through the offline installer VM. Verified: V0 full flake evaluation plus shell/Python/Nix/diff checks; V1 installer-keyboard, installer-safety, and template-SoT builds; V2 full KVM offline LUKS+swap install and themed first boot. No V3 required.
192 lines
7.7 KiB
Bash
Executable File
192 lines
7.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# End-to-end regression test for nomarchy-install: build the live ISO,
|
|
# run a fully OFFLINE unattended install into a fresh VM disk, then boot
|
|
# the installed system and capture what the first boot looks like.
|
|
#
|
|
# This automates the verification protocol from docs/TESTING.md §4 that
|
|
# debugged the installer in the first place. It asserts the machine-
|
|
# checkable parts (installer reaches poweroff; installed disk boots);
|
|
# the visual part — a fully themed desktop, no autogenerated-config
|
|
# banner — lands as screenshots for a human (or agent) to inspect:
|
|
#
|
|
# $NOMARCHY_VM_DIR/install-typed.png the typed command, pre-enter
|
|
# $NOMARCHY_VM_DIR/luks-prompt.png initrd passphrase prompt
|
|
# $NOMARCHY_VM_DIR/first-boot.png THE result: themed desktop
|
|
#
|
|
# Requirements: KVM, qemu, OVMF, dosfstools (mkfs.vfat), mtools (mcopy),
|
|
# python3. Tunables (env):
|
|
# NOMARCHY_VM_DIR scratch + screenshots (default /tmp/nomarchy-vm)
|
|
# NOMARCHY_TARGET_DIR where the 25G target.img lives — must NOT be
|
|
# tmpfs (default ~/.cache/nomarchy-vm)
|
|
# NOMARCHY_BOOT_WAIT seconds to wait for the live desktop (default 480)
|
|
# NOMARCHY_INSTALL_TIMEOUT seconds before declaring a hang (default 2700)
|
|
#
|
|
# Unattended matrix (BACKLOG #74) — env flags only, no interactive options:
|
|
# NOMARCHY_TEST_SWAP_GB=2 # default; 0 = no swap subvol
|
|
# NOMARCHY_TEST_NO_LUKS=0 # default LUKS+passphrase; 1 = cleartext
|
|
# Matrix to run when checking swap/LUKS contracts:
|
|
# (default) SWAP=2 NO_LUKS=0
|
|
# no-swap SWAP=0 NO_LUKS=0
|
|
# no-LUKS SWAP=2 NO_LUKS=1
|
|
# no-swap+no-LUKS SWAP=0 NO_LUKS=1
|
|
|
|
set -euo pipefail
|
|
cd "$(dirname "$0")/.."
|
|
|
|
VM_DIR="${NOMARCHY_VM_DIR:-/tmp/nomarchy-vm}"
|
|
TARGET_DIR="${NOMARCHY_TARGET_DIR:-$HOME/.cache/nomarchy-vm}"
|
|
BOOT_WAIT="${NOMARCHY_BOOT_WAIT:-480}"
|
|
INSTALL_TIMEOUT="${NOMARCHY_INSTALL_TIMEOUT:-2700}"
|
|
TEST_SWAP_GB="${NOMARCHY_TEST_SWAP_GB:-2}"
|
|
TEST_NO_LUKS="${NOMARCHY_TEST_NO_LUKS:-0}"
|
|
[[ "$TEST_SWAP_GB" =~ ^[0-9]+$ ]] || { echo "NOMARCHY_TEST_SWAP_GB must be a whole number" >&2; exit 1; }
|
|
[[ "$TEST_NO_LUKS" == "0" || "$TEST_NO_LUKS" == "1" ]] || {
|
|
echo "NOMARCHY_TEST_NO_LUKS must be 0 or 1" >&2; exit 1
|
|
}
|
|
export NOMARCHY_QMP_SOCK="$VM_DIR/qmp.sock"
|
|
QMP=tools/vm/qmp.py
|
|
SHOT=tools/vm/vncshot.py
|
|
VNC_DISPLAY=17 # port 5917
|
|
|
|
for tool in qemu-system-x86_64 mkfs.vfat mcopy python3; do
|
|
command -v "$tool" >/dev/null || {
|
|
echo "missing $tool — try: nix shell nixpkgs#qemu nixpkgs#dosfstools nixpkgs#mtools" >&2
|
|
exit 1
|
|
}
|
|
done
|
|
[ -r /dev/kvm ] || { echo "no KVM — this test is too slow without it" >&2; exit 1; }
|
|
|
|
mkdir -p "$VM_DIR" "$TARGET_DIR"
|
|
if df --output=fstype "$TARGET_DIR" | tail -1 | grep -q tmpfs; then
|
|
echo "NOMARCHY_TARGET_DIR=$TARGET_DIR is tmpfs — the install writes ~10G; pick a real disk" >&2
|
|
exit 1
|
|
fi
|
|
|
|
OVMF="${NOMARCHY_OVMF:-}"
|
|
if [ -z "$OVMF" ]; then
|
|
for c in /run/current-system/sw/share/qemu/edk2-x86_64-code.fd \
|
|
/run/current-system/sw/share/OVMF/OVMF_CODE.fd \
|
|
/usr/share/OVMF/OVMF_CODE.fd; do
|
|
[ -f "$c" ] && { OVMF="$c"; break; }
|
|
done
|
|
fi
|
|
# Fall back to nixpkgs OVMF.fd (FV/OVMF_CODE.fd) when not on the host profile.
|
|
if [ -z "$OVMF" ] || [ ! -f "$OVMF" ]; then
|
|
ovmf_store=$(nix build --no-link --print-out-paths nixpkgs#OVMF.fd 2>/dev/null || true)
|
|
for c in "$ovmf_store"/FV/OVMF_CODE.fd "$ovmf_store"/FV/OVMF.fd; do
|
|
[ -f "$c" ] && { OVMF="$c"; break; }
|
|
done
|
|
fi
|
|
[ -n "$OVMF" ] && [ -f "$OVMF" ] || { echo "no OVMF firmware found (set NOMARCHY_OVMF=…)" >&2; exit 1; }
|
|
|
|
echo "==> Building the live ISO..."
|
|
nix build .#nixosConfigurations.nomarchy-live.config.system.build.isoImage
|
|
ISO=$(ls -1 result/iso/*.iso | head -n1)
|
|
|
|
echo "==> Preparing disks..."
|
|
echo " matrix: SWAP_GB=$TEST_SWAP_GB NO_LUKS=$TEST_NO_LUKS"
|
|
# Unattended config rides in on a tiny vfat disk: the installer test
|
|
# can't reliably TYPE a 200-char env line into the guest (keystrokes
|
|
# get dropped), but one short mount-and-run command works.
|
|
{
|
|
echo '#!/usr/bin/env bash'
|
|
echo 'export NOMARCHY_UNATTENDED=1'
|
|
echo 'export NOMARCHY_DISK=/dev/vda'
|
|
echo 'export NOMARCHY_USERNAME=ada'
|
|
echo 'export NOMARCHY_PASSWORD=nomarchy'
|
|
echo 'export NOMARCHY_HOSTNAME=testbox'
|
|
echo 'export NOMARCHY_KB_LAYOUT=us'
|
|
echo 'export NOMARCHY_KB_VARIANT="(none)"'
|
|
echo "export NOMARCHY_SWAP_GB=$TEST_SWAP_GB"
|
|
echo 'export NOMARCHY_FINISH=poweroff'
|
|
if [[ "$TEST_NO_LUKS" == "1" ]]; then
|
|
echo 'export NOMARCHY_NO_LUKS=1'
|
|
echo 'unset NOMARCHY_LUKS_PASSPHRASE || true'
|
|
else
|
|
echo 'export NOMARCHY_LUKS_PASSPHRASE=testtest1'
|
|
fi
|
|
echo 'nomarchy-install 2>&1 | tee /tmp/install.log'
|
|
} > "$VM_DIR/go.sh"
|
|
chmod +x "$VM_DIR/go.sh"
|
|
rm -f "$VM_DIR/config.img"
|
|
truncate -s 8M "$VM_DIR/config.img"
|
|
mkfs.vfat "$VM_DIR/config.img" >/dev/null
|
|
mcopy -i "$VM_DIR/config.img" "$VM_DIR/go.sh" ::go.sh
|
|
|
|
rm -f "$TARGET_DIR/target.img" "$NOMARCHY_QMP_SOCK"
|
|
qemu-img create -f raw "$TARGET_DIR/target.img" 25G >/dev/null
|
|
|
|
qemu_base=(
|
|
qemu-system-x86_64
|
|
-enable-kvm -cpu host -m 6144 -smp 2
|
|
-drive "if=pflash,format=raw,readonly=on,file=$OVMF"
|
|
-device virtio-vga-gl -display egl-headless
|
|
-vnc "127.0.0.1:$VNC_DISPLAY"
|
|
-device virtio-net-pci,netdev=n0 -netdev user,id=n0,restrict=on # OFFLINE
|
|
-qmp "unix:$NOMARCHY_QMP_SOCK,server,nowait"
|
|
)
|
|
|
|
echo "==> Phase 1: installing (offline) — this takes ~20-30 min..."
|
|
"${qemu_base[@]}" \
|
|
-drive "file=$TARGET_DIR/target.img,if=virtio,format=raw" \
|
|
-drive "file=$VM_DIR/config.img,if=virtio,format=raw,readonly=on" \
|
|
-cdrom "$ISO" -boot d \
|
|
>"$VM_DIR/qemu-install.log" 2>&1 &
|
|
QPID=$!
|
|
trap 'kill $QPID 2>/dev/null || true' EXIT
|
|
|
|
until [ -S "$NOMARCHY_QMP_SOCK" ]; do sleep 2; done
|
|
echo " waiting ${BOOT_WAIT}s for the live desktop..."
|
|
sleep "$BOOT_WAIT"
|
|
python3 "$QMP" key meta_l-ret
|
|
sleep 8
|
|
python3 "$QMP" type 'sudo bash -c "mkdir -p /m; mount /dev/vdb /m; bash /m/go.sh"'
|
|
sleep 2
|
|
python3 "$SHOT" "$VM_DIR/install-typed.png" 127.0.0.1 "59$VNC_DISPLAY"
|
|
python3 "$QMP" key ret
|
|
echo " install running (typed command captured in install-typed.png)..."
|
|
|
|
waited=0
|
|
while kill -0 $QPID 2>/dev/null; do
|
|
if (( waited >= INSTALL_TIMEOUT )); then
|
|
python3 "$SHOT" "$VM_DIR/install-hung.png" 127.0.0.1 "59$VNC_DISPLAY" || true
|
|
echo "FAIL: installer did not power off within ${INSTALL_TIMEOUT}s (see install-hung.png)" >&2
|
|
exit 1
|
|
fi
|
|
sleep 20; waited=$((waited + 20))
|
|
done
|
|
trap - EXIT
|
|
echo " VM powered off after ~${waited}s — install completed."
|
|
|
|
echo "==> Phase 2: first boot of the installed system..."
|
|
rm -f "$NOMARCHY_QMP_SOCK"
|
|
"${qemu_base[@]}" \
|
|
-drive "file=$TARGET_DIR/target.img,if=virtio,format=raw" \
|
|
>"$VM_DIR/qemu-boot.log" 2>&1 &
|
|
QPID=$!
|
|
trap 'kill $QPID 2>/dev/null || true' EXIT
|
|
|
|
until [ -S "$NOMARCHY_QMP_SOCK" ]; do sleep 2; done
|
|
sleep 40
|
|
if [[ "$TEST_NO_LUKS" == "0" ]]; then
|
|
python3 "$SHOT" "$VM_DIR/luks-prompt.png" 127.0.0.1 "59$VNC_DISPLAY"
|
|
python3 "$QMP" type 'testtest1'
|
|
python3 "$QMP" key ret
|
|
sleep 150
|
|
else
|
|
# Cleartext disk — no LUKS prompt; give the session time to come up.
|
|
python3 "$SHOT" "$VM_DIR/boot-noluks.png" 127.0.0.1 "59$VNC_DISPLAY" || true
|
|
sleep 150
|
|
fi
|
|
python3 "$SHOT" "$VM_DIR/first-boot.png" 127.0.0.1 "59$VNC_DISPLAY"
|
|
python3 "$QMP" quit || true
|
|
trap - EXIT
|
|
|
|
echo
|
|
echo "PASS (machine-checkable part): install powered off, installed disk boots."
|
|
echo " matrix: SWAP_GB=$TEST_SWAP_GB NO_LUKS=$TEST_NO_LUKS"
|
|
echo "Now INSPECT $VM_DIR/first-boot.png:"
|
|
echo " expected: themed desktop (wallpaper + waybar), NO red banner, no login prompt"
|
|
echo " if it shows the Hyprland water-drop or an 'autogenerated config' banner,"
|
|
echo " log in on tty2 (ada/nomarchy) and read /var/log/nomarchy-hm-preactivate.log"
|