#!/bin/bash # SPDX-FileCopyrightText: 2026 Coffey Labs # SPDX-License-Identifier: AGPL-3.0-or-later # # Build the throwaway machine the installer is tested on. # # The installer writes units, creates users, takes 25 and 443 and can install # a web server. None of that belongs on a workstation, and none of it can be # proved in a container either -- systemd, users and ports are the thing under # test. So: a Debian cloud image in qemu, seeded with cloud-init, with a copy # of the disk kept the moment it is up. Every run starts from that copy, so a # run can break the machine as thoroughly as it likes. # # e2e/vm/up.sh build it and keep a clean copy (idempotent) # e2e/vm/reset.sh back to the clean copy, a few seconds # e2e/vm/run.sh build the installer, copy it in, run a case inside # e2e/vm/down.sh stop it and remove its disks # # Plain qemu, run as you, rather than libvirt: no root, no storage pool, no # permissions to argue with, and the logs are readable. Networking is qemu's # user-mode NAT with ssh forwarded to 127.0.0.1:2222, which gives the guest # the internet it needs to pull images without putting it on the LAN. # # Needs: qemu-system-x86_64, /dev/kvm, xorriso, ssh, curl. set -euo pipefail LAB="${LAB:-$HOME/.cache/inbuxa-lab}" MEM="${MEM:-4096}" VCPUS="${VCPUS:-2}" DISK_GB="${DISK_GB:-20}" SSH_PORT="${SSH_PORT:-2222}" BASE_URL="https://cloud.debian.org/images/cloud/trixie/latest/debian-13-genericcloud-amd64.qcow2" BASE="$LAB/debian-13-base.qcow2" DISK="$LAB/lab.qcow2" CLEAN="$LAB/lab-clean.qcow2" SEED="$LAB/seed.iso" LOG="$LAB/console.log" PIDFILE="$LAB/qemu.pid" KEY="${KEY:-$HOME/.ssh/id_ed25519.pub}" say() { echo "==> $*"; } . "$(dirname "$0")/lib.sh" [ -r "$KEY" ] || { echo "no public key at $KEY (set KEY=)" >&2; exit 1; } [ -w /dev/kvm ] || { echo "no writable /dev/kvm -- is this user in the kvm group?" >&2; exit 1; } if vm_running; then say "already up on 127.0.0.1:$SSH_PORT -- reset.sh rewinds it, down.sh removes it" exit 0 fi mkdir -p "$LAB" if [ ! -f "$BASE" ]; then say "fetching the base image (once)" curl -fL --progress-bar -o "$BASE.part" "$BASE_URL" mv "$BASE.part" "$BASE" fi # cloud-init: one unprivileged user with our key and passwordless sudo, and # almost nothing else. The installer is what is under test, so the machine # should be as plain as a new VPS -- if the installer needs a package, the # installer should say so rather than the lab quietly providing it. WORK="$(mktemp -d)" trap 'rm -rf "$WORK"' EXIT cat > "$WORK/meta-data" < "$WORK/user-data" </dev/null; then break; fi sleep 2 done vm_ssh true 2>/dev/null || { echo "no ssh after three minutes; see $LOG" >&2; exit 1; } say "waiting for cloud-init to finish" vm_ssh 'sudo cloud-init status --wait >/dev/null 2>&1 || true' say "keeping a clean copy of the disk" vm_stop cp --reflink=auto "$DISK" "$CLEAN" vm_start for _ in $(seq 1 90); do if vm_ssh true 2>/dev/null; then break; fi sleep 2 done say "up: ssh lab@127.0.0.1 -p $SSH_PORT (console log: $LOG)" vm_ssh 'echo " guest:" $(. /etc/os-release && echo $PRETTY_NAME) "| kernel" $(uname -r) "| disk" $(df -h / | awk "NR==2{print \$2}")'