a7db23cbf680
add test/vm golden-image build harness: configuration.nix, scripted installer, disposable overlay boot
- time
- 2026-05-29 13:41 UTC · 4 months ago
- machine
- koh2
- parent
- a336cdb20502
↓ nina-a7db23cb.tar.gzatlas at this save
files changed5 files
test/vm/README.md+41 −0test/vm/boot-overlay.sh+62 −0test/vm/build-golden.sh+85 −0test/vm/configuration.nix+72 −0test/vm/install.sh+28 −0test/vm/README.md+41 −0
@@ -0,0 +1,41 @@1+# nina test VM — golden NixOS dev image2+3+A reproducible NixOS 24.11 dev image for exercising nina, built once and booted4+as disposable copy-on-write overlays so every run starts from the exact same5+snapshot. Runs under qemu (TCG) on Apple Silicon — x86_64 emulation, so slow.6+7+## Files8+9+- `configuration.nix` — the golden system's exact starting state (the spec).10+ Serial console + root autologin (headless drivable), BIOS GRUB on `/dev/vda`,11+ the brief's packages, `~/.nina.conf`, hostname `test-primary`.12+- `install.sh` — runs inside the minimal installer: partition, `nixos-install`.13+- `build-golden.sh` — host orchestration: boots the ISO headless and drives the14+ scripted install over serial, producing the golden qcow2.15+- `boot-overlay.sh` — boots a throwaway overlay of the golden image, runs a16+ command (default: nina smoke test), discards the overlay.17+18+## Build the golden image (once, slow)19+20+```sh21+# needs the NixOS 24.11 minimal x86_64 ISO and a linux nina binary22+./build-golden.sh /tmp/nixvm/nixos-24.11-minimal-x86_64.iso \23+ /tmp/nina-linux/bin/nina \24+ /tmp/nixvm/golden.qcow225+```26+27+The linux nina binary comes from the nina repo:28+`zig build -Dtarget=x86_64-linux-musl -Doptimize=ReleaseSafe -p /tmp/nina-linux`.29+30+## Boot a disposable copy31+32+```sh33+./boot-overlay.sh /tmp/nixvm/golden.qcow2 # nina smoke test34+./boot-overlay.sh /tmp/nixvm/golden.qcow2 'nina status'35+```36+37+The golden image is never mutated. Kiln can also boot it directly with38+`kiln chamber run --os /tmp/nixvm/golden.qcow2` (fresh overlay per run).39+40+Artifacts (ISO, golden.qcow2) live outside the repo (`/tmp/nixvm`); only the41+build inputs are version-controlled here.test/vm/boot-overlay.sh+62 −0
@@ -0,0 +1,62 @@1+#!/usr/bin/env bash2+# Boot a THROWAWAY copy-on-write overlay of the golden image, run commands over3+# the serial console, then discard the overlay. The golden image is never4+# mutated, so every boot starts from the exact same snapshot.5+#6+# Usage: boot-overlay.sh <golden.qcow2> [command ...]7+# With no command, runs a nina smoke test (help / hello / status).8+# Tunables via env: MEM_MB (2048), SMP (2), BOOT_TIMEOUT (600)9+set -euo pipefail10+11+GOLDEN="${1:?usage: boot-overlay.sh <golden.qcow2> [command...]}"12+shift || true13+MEM_MB="${MEM_MB:-2048}"14+SMP="${SMP:-2}"15+BOOT_TIMEOUT="${BOOT_TIMEOUT:-600}"16+[ -f "$GOLDEN" ] || { echo "missing golden: $GOLDEN" >&2; exit 1; }17+18+if [ "$#" -gt 0 ]; then CMD="$*"; else19+ CMD='/root/nina help; echo ---; /root/nina hello; echo ---; /root/nina --version 2>/dev/null || true'20+fi21+22+work="$(mktemp -d)"23+trap 'rm -rf "$work"' EXIT24+overlay="$work/overlay.qcow2"25+qemu-img create -f qcow2 -b "$(cd "$(dirname "$GOLDEN")" && pwd)/$(basename "$GOLDEN")" -F qcow2 "$overlay" >/dev/null26+echo "throwaway overlay: $overlay (backing: $GOLDEN)"27+28+expect -c '29+ set timeout [lindex $argv 3]30+ set overlay [lindex $argv 0]31+ set mem [lindex $argv 1]32+ set smp [lindex $argv 2]33+ set cmd [lindex $argv 4]34+35+ spawn qemu-system-x86_64 \36+ -machine q35 -accel tcg -cpu max -smp $smp -m $mem \37+ -drive file=$overlay,if=virtio,format=qcow2 \38+ -netdev user,id=n0 -device virtio-net-pci,netdev=n0 \39+ -nographic40+41+ expect {42+ timeout { puts "\n!! no login on serial"; exit 2 }43+ -re {\[root@test-primary[^\]]*\]#} {}44+ -re {login:} { send "root\r"; exp_continue }45+ }46+ send "\r"47+ expect -re {\[root@test-primary[^\]]*\]#}48+49+ send "echo NINA_SMOKE_BEGIN\r"50+ expect "NINA_SMOKE_BEGIN"51+ send "$cmd\r"52+ send "echo NINA_SMOKE_END_RC=\$?\r"53+ expect {54+ timeout { puts "\n!! command timed out"; exit 3 }55+ -re {NINA_SMOKE_END_RC=[0-9]+} {}56+ }57+ send "poweroff\r"58+ set timeout 12059+ expect eof60+' "$overlay" "$MEM_MB" "$SMP" "$BOOT_TIMEOUT" "$CMD"61+62+echo "=== overlay discarded; golden image unchanged ==="test/vm/build-golden.sh+85 −0
@@ -0,0 +1,85 @@1+#!/usr/bin/env bash2+# Bake the golden NixOS qcow2: boot the minimal ISO headless under qemu, drive a3+# scripted nixos-install over the serial console, power off. Output is a golden4+# disk image; Kiln (or boot-overlay.sh) boots throwaway overlays from it.5+#6+# Usage: build-golden.sh <nixos.iso> <nina-linux-binary> <out-golden.qcow2>7+# Tunables via env: DISK_GB (20), MEM_MB (4096), SMP (2), INSTALL_TIMEOUT (2400)8+set -euo pipefail9+10+ISO="${1:?usage: build-golden.sh <iso> <nina-bin> <out.qcow2>}"11+NINA="${2:?need nina linux binary}"12+OUT="${3:?need output qcow2 path}"13+HERE="$(cd "$(dirname "$0")" && pwd)"14+DISK_GB="${DISK_GB:-20}"15+MEM_MB="${MEM_MB:-4096}"16+SMP="${SMP:-2}"17+INSTALL_TIMEOUT="${INSTALL_TIMEOUT:-2400}"18+19+for f in "$ISO" "$NINA"; do [ -f "$f" ] || { echo "missing: $f" >&2; exit 1; }; done20+21+work="$(mktemp -d)"22+trap 'rm -rf "$work"' EXIT23+24+# Seed media: our config + install script + nina, as an ISO the installer mounts.25+seed="$work/seed"26+mkdir -p "$seed"27+cp "$HERE/configuration.nix" "$seed/configuration.nix"28+cp "$HERE/install.sh" "$seed/install.sh"29+cp "$NINA" "$seed/nina"30+SEED_ISO="$work/seed.iso"31+hdiutil makehybrid -quiet -iso -joliet -default-volume-name NINASEED -o "$SEED_ISO" "$seed"32+33+# Fresh blank disk for the install target.34+qemu-img create -f qcow2 "$OUT" "${DISK_GB}G" >/dev/null35+echo "golden target: $OUT (${DISK_GB}G) · seed: $SEED_ISO"36+37+expect -c '38+ set timeout 60039+ set iso [lindex $argv 0]40+ set seed_iso [lindex $argv 1]41+ set out [lindex $argv 2]42+ set mem [lindex $argv 3]43+ set smp [lindex $argv 4]44+ set install_timeout [lindex $argv 5]45+46+ spawn qemu-system-x86_64 \47+ -machine q35 -accel tcg -cpu max -smp $smp -m $mem \48+ -drive file=$out,if=virtio,format=qcow2 \49+ -drive file=$iso,media=cdrom,readonly=on \50+ -drive file=$seed_iso,media=cdrom,readonly=on \51+ -boot d \52+ -netdev user,id=n0 -device virtio-net-pci,netdev=n0 \53+ -nographic54+55+ # Reach an installer root shell (autologin on the ISO serial console).56+ expect {57+ timeout { puts "\n!! no shell prompt from installer"; exit 2 }58+ -re {\[root@nixos[^\]]*\]#} {}59+ -re {nixos@nixos} { send "sudo -i\r"; exp_continue }60+ -re {login:} { send "root\r"; exp_continue }61+ }62+ send "\r"63+ expect -re {\[root@nixos[^\]]*\]#}64+65+ # Mount seed media and run the installer script.66+ send "mkdir -p /mnt-seed && mount /dev/sr1 /mnt-seed && echo NINA_SEED_OK\r"67+ expect {68+ timeout { puts "\n!! seed mount failed"; exit 3 }69+ "NINA_SEED_OK" {}70+ }71+ send "bash /mnt-seed/install.sh\r"72+73+ set timeout $install_timeout74+ expect {75+ timeout { puts "\n!! install timed out"; exit 4 }76+ "NINA_INSTALL_DONE" {}77+ "NINA_INSTALL_START" { exp_continue }78+ }79+ # qemu exits when the guest powers off.80+ set timeout 12081+ expect eof82+' "$ISO" "$SEED_ISO" "$OUT" "$MEM_MB" "$SMP" "$INSTALL_TIMEOUT"83+84+echo "=== golden image built: $OUT ==="85+qemu-img info "$OUT" | sed 's/^/ /'test/vm/configuration.nix+72 −0
@@ -0,0 +1,72 @@1+# Golden NixOS dev image for nina testing — the reproducible "exact starting2+# snapshot". Built once into a qcow2 (see build-golden.sh); Kiln boots a fresh3+# throwaway overlay from it per run.4+#5+# Mirrors the test environment from the Kiln brief (§2.3): NixOS 24.11,6+# test-primary host, git/curl/vim/htop/ripgrep, America/New_York.7+{ config, pkgs, ... }:8+9+{10+ imports = [ ./hardware-configuration.nix ];11+12+ # BIOS GRUB on the virtio disk — avoids needing UEFI/OVMF firmware under qemu.13+ boot.loader.grub = {14+ enable = true;15+ device = "/dev/vda";16+ };17+ # Serial console so the installed image is drivable headless (qemu -nographic).18+ # Keep a normal vga console too.19+ boot.kernelParams = [ "console=ttyS0,115200" "console=tty0" ];20+21+ networking.hostName = "test-primary";22+ networking.useDHCP = true;23+ time.timeZone = "America/New_York";24+25+ environment.systemPackages = with pkgs; [26+ git27+ curl28+ vim29+ htop30+ ripgrep31+ ];32+33+ # Headless driving: autologin root on the serial console so the build/boot34+ # harness can run commands without credentials.35+ services.getty.autologinUser = "root";36+37+ services.openssh = {38+ enable = true;39+ settings.PermitRootLogin = "yes";40+ settings.PasswordAuthentication = true;41+ };42+43+ # Test VM only — known password, never a real secret.44+ users.users.root.password = "nixos";45+46+ # nina's config (brief §2.2). Written on activation so it stays in this file47+ # rather than as imperative state. confirm=false auto-accepts; teach=true48+ # echoes the underlying command; color=true enables ANSI the checks verify.49+ system.activationScripts.ninaConf.text = ''50+ cat > /root/.nina.conf <<'EOF'51+ editor = vim52+ generations = 553+ confirm = false54+ teach = true55+ color = true56+57+ [machine]58+ name = test-primary59+ config = /etc/nixos60+ local = true61+ default = true62+63+ [machine]64+ name = test-remote65+ host = 192.168.100.266+ user = testuser67+ ssh_key = /home/testuser/.ssh/test_key68+ EOF69+ '';70+71+ system.stateVersion = "24.11";72+}test/vm/install.sh+28 −0
@@ -0,0 +1,28 @@1+#!/usr/bin/env bash2+# Runs INSIDE the NixOS minimal installer (over serial) to lay down the golden3+# system onto /dev/vda from the seed media. Driven by build-golden.sh.4+set -euo pipefail5+6+SEED_DIR="$(cd "$(dirname "$0")" && pwd)"7+echo "=== NINA_INSTALL_START ==="8+9+# Single BIOS-bootable ext4 root (MBR) — matches grub.device=/dev/vda.10+parted -s /dev/vda mklabel msdos11+parted -s /dev/vda mkpart primary ext4 1MiB 100%12+parted -s /dev/vda set 1 boot on13+mkfs.ext4 -F -L nixos /dev/vda114+15+mount /dev/disk/by-label/nixos /mnt16+17+# Generate hardware-configuration.nix for this disk, then drop in our config.18+nixos-generate-config --root /mnt19+cp "$SEED_DIR/configuration.nix" /mnt/etc/nixos/configuration.nix20+21+# Bake the injected nina binary (prove-the-loop: absolute-path /root/nina).22+install -Dm755 "$SEED_DIR/nina" /mnt/root/nina23+24+nixos-install --no-root-passwd --root /mnt25+26+echo "=== NINA_INSTALL_DONE ==="27+sync28+poweroff