Build and run an unattended Debian installer in QEMU with UEFI, Btrfs root, and Snapper.
amd64(default on Linux hosts)arm64(default on macOS hosts)
Executables required by public target:
| Target | Required executables | Conditional executables |
|---|---|---|
make clean |
none | none |
make full-clean |
none | none |
make build |
qemu-img, xorriso, rg |
curl or wget (only when ISO is missing locally) |
make install |
qemu-system-x86_64 or qemu-system-aarch64, qemu-img, xorriso, rg |
curl or wget (only when ISO is missing locally) |
make start |
qemu-system-x86_64 or qemu-system-aarch64 |
none |
make test |
qemu-system-x86_64 or qemu-system-aarch64, qemu-img, xorriso, rg |
Linux only: timeout |
Required firmware files:
amd64: OVMF (OVMF_CODE.fd,OVMF_VARS.fd)arm64: AAVMF/EDK2 (edk2-aarch64-code.fd,edk2-aarch64-vars.fd)
Install suggestions (recommended superset for all targets):
# macOS (Homebrew)
brew install qemu xorriso ripgrep curl wget# Debian/Ubuntu (example)
sudo apt update
sudo apt install -y qemu-system-x86 qemu-system-arm qemu-utils ovmf qemu-efi-aarch64 xorriso ripgrep curl wget# Fedora (example)
sudo dnf install -y qemu-system-x86 qemu-system-aarch64 qemu-img edk2-ovmf edk2-aarch64 xorriso ripgrep curl wget# Arch Linux (example)
sudo pacman -S --needed qemu-base edk2-ovmf xorriso ripgrep curl wgetPackage names can vary by distro/release.
The Makefile auto-detects common firmware paths. If detection fails, set:
EFI_CODE=/path/to/firmware-code.fdEFI_VARS_TEMPLATE=/path/to/firmware-vars.fd
Common command sets by host/guest architecture.
make build
make install
make startmake build ARCH=arm64 ISO=debian-13.3.0-arm64-netinst.iso
make install ARCH=arm64 ISO=debian-13.3.0-arm64-netinst.iso
make start ARCH=arm64make build
make install
make startmake build ARCH=amd64 ISO=debian-13.3.0-amd64-netinst.iso
make install ARCH=amd64 ISO=debian-13.3.0-amd64-netinst.iso
make start ARCH=amd64make test
make test ARCH=amd64
make test ARCH=arm64 ISO=debian-13.3.0-arm64-netinst.isomake clean: remove generated artifactsmake full-clean: remove build artifacts and downloaded installer ISOsmake build: create disk image and unattended installer ISO assetsmake install: run unattended installer in a QEMU window (exits on first reboot)make test: CI/local verification that unattended install completes and records log in.build/testmake start: boot installed OS from disk
Prerequisite checks run automatically for build, install, test, and start.
ARCH:amd64on Linux,arm64on macOSDEBIAN_VERSION:13.3.0ISO:debian-$(DEBIAN_VERSION)-$(ARCH)-netinst.isoAUTO_ISO_INSTALL:debian-auto-install-$(ARCH).isoAUTO_ISO_TEST:debian-auto-test-$(ARCH).isoDISK:os-$(ARCH).qcow2DISK_SIZE:20GRAM_MB:2048CPUS:4EFI_VARS:efi-vars-$(ARCH).fdTEST_TIMEOUT:45m(Linux only)
- installer user:
installer - console password:
installer - SSH password auth: disabled (
PasswordAuthentication no) - SSH root login: disabled (
PermitRootLogin no) - sudo: passwordless for
installer(NOPASSWD:ALL) - default key discovery on host:
~/.ssh/id_*.pub - guest authorized keys path:
/home/installer/.ssh/authorized_keys
Use overrides at runtime:
make build ARCH=amd64 ISO=debian-13.3.0-amd64-netinst.iso DISK=myvm.qcow2 DISK_SIZE=40G RAM_MB=4096 CPUS=8DEBIAN_VERSIONISOISO_FILENAMEISO_MIRRORISO_URL
ARCHQEMUACCELCPU_MODELDISKDISK_SIZERAM_MBCPUSDISPLAY_ARGSVIDEO_ARGSNETWORK_ARGSMONITOR_ARGSINPUT_ARGS
EFI_CODEEFI_VARS_TEMPLATEEFI_VARS
PRESEEDINSTALLER_HOOKS_DIRPARTMAN_EARLY_SCRIPTPRESEED_LATE_SCRIPTPARTMAN_EARLY_ISO_PATHPRESEED_LATE_ISO_PATHAUTO_ISOAUTO_ISO_INSTALLAUTO_ISO_TESTGRUB_KERNEL_ARGS_COMMONGRUB_KERNEL_ARGS_INSTALLGRUB_KERNEL_ARGS_TESTGRUB_KERNEL_ARGSQUIET(1by default; setQUIET=0for verbose tool output)
SSH_PUBLIC_KEY_GLOBSSH_PUBLIC_KEY_FILESSSH_PUBLIC_KEY_FILE(legacy alias)SSH_PUBLIC_KEY(inline override; highest priority)
TEST_TIMEOUTTEST_LOGTEST_TAIL_LINESTEST_SUCCESS_REGEXTIMEOUT
- Partitioning and post-install customization are executed from injected ISO scripts:
/installer-hooks/partman-early.sh/installer-hooks/preseed-late.sh/installer-hooks/common.sh(shared helper functions sourced by hook scripts)
- These scripts are sourced from repo files under
scripts/and mapped into the unattended ISO duringmake build. - Hook scripts are executed explicitly via
/bin/sh, so execute permissions are not required.
| Repo source | ISO path | Used by | Execution phase |
|---|---|---|---|
preseed.cfg |
/preseed.cfg |
Debian installer | Installer boot/preseed |
scripts/partman-early.sh |
/installer-hooks/partman-early.sh |
partman/early_command |
Pre-partitioning |
scripts/preseed-late.sh |
/installer-hooks/preseed-late.sh |
preseed/late_command |
Post-install (late command) |
scripts/common.sh |
/installer-hooks/common.sh |
sourced by hook scripts | Helper functions only |
- Partitioning is destructive on the selected target disk.
make installandmake testreset EFI vars each run for deterministic installer boot.make testuses serial/headless mode and follows the install log.- If installer ISO is missing,
build/install/testwill download it automatically.