Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
cef2bda
packaging: Add top-level COPYING (GPL-2) and fix Upstream-Name
ggiesen Jun 12, 2026
60c3587
tests: Add pytest scaffolding and unit tests for pure helpers
ggiesen Jun 12, 2026
78a8b7c
tests: Add QEMU/KVM integration test harness
ggiesen Jun 12, 2026
b69bb79
schema: Add answer-file schema for unattended installation (v1)
ggiesen Jun 12, 2026
8eaff62
tests: Add uefi-simple scenario (OVMF smoke-verified)
ggiesen Jun 12, 2026
3e2a789
installer: Route all shell-outs through an injectable CommandRunner
ggiesen Jun 12, 2026
6046a6a
installer: Add headless driver for unattended installation
ggiesen Jun 12, 2026
c4918bd
tests: Add live-session injection tooling for integration tests
ggiesen Jun 12, 2026
b976a85
packaging: Declare python3-yaml/python3-pydantic; fail fast on crash
ggiesen Jun 12, 2026
e46599e
installer: Add live-installer.auto-insecure kernel cmdline flag
ggiesen Jun 12, 2026
94820e5
partitioning: Pass setup into full_disk_format instead of GUI global
ggiesen Jun 12, 2026
ab5b67a
auto_installer: Deduplicate progress output
ggiesen Jun 12, 2026
7c1d1c2
installer: Run driver post-install steps before target teardown
ggiesen Jun 12, 2026
4734d81
auto_installer: Repair dpkg state before installing packages
ggiesen Jun 12, 2026
0585f3f
partitioning: Fix udev race in full_disk_format; fail on format errors
ggiesen Jun 12, 2026
fba0f5c
tests: Add failure-mode scenarios and expect.outcome support
ggiesen Jun 12, 2026
46983e2
tests: Use lsblk for the swap assertion (swapon needs /usr/sbin)
ggiesen Jun 12, 2026
2abfc27
installer: Support LUKS keyfiles over http(s); test lvm-on-luks install
ggiesen Jun 12, 2026
56b4c2c
commandrunner: Redact secrets from command logging
ggiesen Jun 12, 2026
957f381
installer: Feed LUKS passphrase via stdin, not a shell pipe
ggiesen Jun 12, 2026
c281193
ci: Add GitHub Actions workflows for unit and integration tests
ggiesen Jun 12, 2026
f08d88d
ci: Bump actions to Node 24 majors
ggiesen Jun 12, 2026
0a7fb29
installer+tests: Interactive serial, kernel cmdline, multi-disk scenario
ggiesen Jun 12, 2026
2667483
docs: Add user guide, testing/maintenance guide, README; harden CI
ggiesen Jun 12, 2026
ab53ba8
tests: Cover the is_mint engine branch; document Mint vs LMDE coverage
ggiesen Jun 12, 2026
3394c62
tests: Boot the OS disk in multi-disk phase 2 (bootindex)
ggiesen Jun 12, 2026
f4bd687
tests: LUKS scenario verifies install only; serial unlock is follow-up
ggiesen Jun 12, 2026
2326914
ci: Fix ISO-download working dir on fresh checkout
ggiesen Jun 12, 2026
cfebddd
ci: Add KVM diagnostics to vm-setup (investigating hosted-runner KVM)
ggiesen Jun 12, 2026
2f47b87
ci/harness: Fix AF_UNIX path length, KVM race, and QEMU-death masking
ggiesen Jun 12, 2026
f5c4f00
installer: Add serial-console provisioning; LUKS unlock over serial
ggiesen Jun 12, 2026
0810368
installer: Provision serial console via grub.d snippet, not grub edits
ggiesen Jun 13, 2026
becabe8
installer: Regenerate initramfs for LUKS so the encrypted root boots
ggiesen Jun 13, 2026
62bd3aa
installer: Bypass live-tools update-initramfs wrapper for LUKS
ggiesen Jun 13, 2026
67a1eb7
installer: Purge live packages before LUKS initramfs rebuild
ggiesen Jun 13, 2026
eb6d6ae
installer: Remove failing live-boot initramfs hook instead of purging
ggiesen Jun 13, 2026
25aaca8
docs: Document serial console + LUKS boot mechanics and live-system t…
ggiesen Jun 13, 2026
762af2e
schema: Align answer-file format with cloud-init where they overlap
ggiesen Jun 13, 2026
ed3d8e0
auto: Add --check, a disk-free answer-file validator (ksvalidator ana…
ggiesen Jun 13, 2026
8198e2b
auto: Add --list-disks and a getting-started guide for beta testers
ggiesen Jun 13, 2026
f638094
auto: Address review — strict first-non-removable, dedup, doc the tru…
ggiesen Jun 13, 2026
a2cf607
schema: Rename runcmd to late_commands to stop colliding with cloud-init
ggiesen Jun 13, 2026
a854eb6
auto: Add NFS transport and identity-based answer-file auto-discovery
ggiesen Jun 13, 2026
3ed38af
tests: Add a BIOS PXE/iPXE netboot integration scenario
ggiesen Jun 13, 2026
905d06c
tests: Configure initramfs networking (ip=dhcp) for the PXE squashfs …
ggiesen Jun 13, 2026
1386820
auto: Don't preclude IPv6 in the NFS source parser
ggiesen Jun 13, 2026
5f36d9a
tests: Fast-fail PXE boot failures and add live-boot debug output
ggiesen Jun 13, 2026
5c1da33
tests: Drop the live-boot debug flag (it drops to an initramfs shell …
ggiesen Jun 13, 2026
f49c4e4
tests: Merge dev code into one squashfs for PXE (live-boot fetch= is …
ggiesen Jun 13, 2026
80b5de4
tests: Pass -no-xattrs when (un)squashing the combined PXE rootfs
ggiesen Jun 13, 2026
4a888b3
tests: Tolerate unsquashfs rc=2 (uncreatable device nodes) for the PX…
ggiesen Jun 13, 2026
f45e842
installer: Source kernel/initrd/manifests from the rootfs on netboot
ggiesen Jun 13, 2026
99da9d2
tmp: Dump live-system DNS/network state before package install (diagn…
ggiesen Jun 13, 2026
567f48b
auto: Repair resolv.conf from the DHCP lease on netboot
ggiesen Jun 13, 2026
0c78bef
tests: Make NetworkManager manage the netboot NIC so DNS resolves
ggiesen Jun 13, 2026
9a7a1c7
auto: Drive NetworkManager to DHCP the boot NIC for DNS on netboot
ggiesen Jun 13, 2026
c52dc09
auto: Read DNS from NetworkManager and write resolv.conf directly
ggiesen Jun 13, 2026
ed04440
auto: Force a fresh DHCP (disconnect+reconnect) so NM learns DNS on n…
ggiesen Jun 13, 2026
15f1db3
docs: Document PXE/iPXE network installation
ggiesen Jun 13, 2026
bfc0ddc
tests: Add a UEFI PXE/iPXE netboot scenario
ggiesen Jun 13, 2026
4f6b1df
installer: Source the signed EFI bootloader packages from the netboot…
ggiesen Jun 13, 2026
eefbc0e
docs: Mark UEFI PXE done; document the BIOS/UEFI netboot differences
ggiesen Jun 13, 2026
90f871f
tests: Add an IPv6 netboot scenario (rootfs + answer file over IPv6)
ggiesen Jun 13, 2026
e9c16ef
tests: Send QEMU stderr to a file so a chatty backend can't freeze th…
ggiesen Jun 13, 2026
8a68794
tests: IPv6 data-path netboot via direct kernel boot (slirp can't IPv…
ggiesen Jun 13, 2026
edade7a
tests: IPv6 scenario proves the data path (no apt, skip phase 2)
ggiesen Jun 13, 2026
ae7996d
docs: Mark the IPv6 data path done; document the IPv6 PXE boundary
ggiesen Jun 14, 2026
d5a376c
auto: Add TFTP as an answer-file/keyfile transport
ggiesen Jun 14, 2026
392d034
schema: Add custom partition layout (explicit partitions + custom LVM)
ggiesen Jun 14, 2026
ed41be4
installer: Implement custom partition layouts (explicit partitions + …
ggiesen Jun 14, 2026
7408c99
tests: Size the custom-layout root LV to 16GB (8GB filled /var/cache/…
ggiesen Jun 14, 2026
c25b07a
tests: Verify custom-layout swap via fstab/format, not boot-time acti…
ggiesen Jun 14, 2026
3cabb70
docs: Document custom partition layouts (done)
ggiesen Jun 14, 2026
6ce8419
installer: Add btrfs subvolumes to custom partition layouts
ggiesen Jun 14, 2026
4c5fd0f
docs: Mark btrfs subvolumes as supported in custom layouts
ggiesen Jun 14, 2026
11a7e48
installer: Add static IP / VLAN networking (netplan v2 -> NM keyfiles)
ggiesen Jun 14, 2026
1b041c7
installer: Replace repositories with cloud-init apt: + swappable backend
ggiesen Jun 14, 2026
7d64159
schema/engine: Fix custom-layout firmware/ESP validation (review #1-#3)
ggiesen Jun 14, 2026
f125466
installer: Add wifi support to the network: section (item A)
ggiesen Jun 14, 2026
8d60219
discovery/docs: Address review #4-#9 (sentinel DMI, wifi filter, doc …
ggiesen Jun 14, 2026
bedf814
auto_installer: TFTP RFC 2347 option negotiation with RFC 1350 fallba…
ggiesen Jun 14, 2026
bec208c
tests: Real NFS harness (v3 + v4.2, IPv4/IPv6/hostname) + nfs:// vers…
ggiesen Jun 14, 2026
500e40a
tests/nfs: Create /etc/exports.d before writing the export
ggiesen Jun 14, 2026
297f8cc
docs+main: Doc audit fixes; forward all auto flags through the wrapper
ggiesen Jun 14, 2026
a4399df
installer: Implement LUKS passphrase_source prompt-on-first-boot
ggiesen Jun 14, 2026
49e3308
tests: Integration scenario for LUKS prompt-on-first-boot (two-boot r…
ggiesen Jun 14, 2026
522db3a
installer: Make first-boot LUKS rekey prompt reliable on serial
ggiesen Jun 14, 2026
532b050
installer: Multiple keyboard layouts + supplementary locales
ggiesen Jun 14, 2026
a5f8d39
installer: Add system http(s) proxy support (proxy:)
ggiesen Jun 14, 2026
795d3c4
installer: System CA trust store (ca_certs:), cloud-init shape
ggiesen Jun 14, 2026
8869024
tests: Fix ca_certs fixture to be schema-valid (placeholder was not PEM)
ggiesen Jun 14, 2026
39d0df7
installer: Third-party drivers directive (drivers: install)
ggiesen Jun 14, 2026
226b1ec
installer: 802.1X / EAP networking (network auth: block)
ggiesen Jun 14, 2026
6004f46
installer: Timeshift snapshot support (snapshots:), btrfs-only v1
ggiesen Jun 14, 2026
72a078a
tests: Fix YAML in custom-btrfs verify (bare colon in grep command)
ggiesen Jun 14, 2026
0fad122
schema: Software RAID (multi-disk custom layout) — stage 1 (schema)
ggiesen Jun 14, 2026
05abc31
tests: ca custom-btrfs timeshift verify needs root; check installed i…
ggiesen Jun 14, 2026
dbe4365
installer: Software RAID engine (multi-disk + mdadm) — stage 2
ggiesen Jun 14, 2026
8dbd8c5
tests: Software RAID integration — stage 3 (raid1-bios scenario)
ggiesen Jun 14, 2026
1095c2e
tests: RAID5 + LVM-on-RAID integration scenario (raid5-lvm-bios)
ggiesen Jun 14, 2026
a3732a0
docs: Document software RAID (disks:/raid: custom layout)
ggiesen Jun 14, 2026
e77cdb4
tests: raid5-lvm verify uses lsblk (root-free) not pvs
ggiesen Jun 14, 2026
c35ac44
installer: Add early_commands (%pre / autoinstall early-commands)
ggiesen Jun 14, 2026
21df1d0
installer: Flatpak support (remotes + app install)
ggiesen Jun 14, 2026
5ec5837
docs: post-queue audit pass — fix anchors, add RAID/LUKS-prompt/flatp…
ggiesen Jun 14, 2026
2df3d8f
tests: Add a BIOS LVM-on-LUKS integration scenario
ggiesen Jun 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
105 changes: 105 additions & 0 deletions .github/actions/vm-setup/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
name: VM test setup
description: >
Enable KVM, install QEMU/OVMF/swtpm and ISO tooling, fetch the pinned
LMDE ISO (cached), and build the dev ISO with the working-tree
installer. Leaves fixtures/lmde-7-dev.iso ready for run_scenario.py.

inputs:
iso-name:
description: Base ISO filename under tests/integration/fixtures
default: lmde-7-cinnamon-64bit.iso
iso-url:
description: Download URL for the base ISO
default: https://mirrors.kernel.org/linuxmint/debian/lmde-7-cinnamon-64bit.iso

runs:
using: composite
steps:
- name: Free up disk space
shell: bash
run: |
# The PXE scenario unsquashfs's the full desktop rootfs (~8GB) to
# merge our code in; reclaim space the runner image spends on toolsets
# we don't use so it fits. Best-effort, never fails the job.
df -h / | tail -1
sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc \
/usr/local/.ghcup /usr/local/share/powershell || true
df -h / | tail -1

- name: Enable KVM for the runner user
shell: bash
run: |
echo "=== KVM diagnostics ==="
echo "cpu virt flags: $(grep -c -E 'vmx|svm' /proc/cpuinfo) cores"
ls -l /dev/kvm 2>&1 || echo "/dev/kvm absent"
# The udev rule persists the permission; the direct chmod applies
# it synchronously so there is no race with the next step (the
# udev trigger is asynchronous and `test -w` can run before it).
echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' \
| sudo tee /etc/udev/rules.d/99-kvm4all.rules
sudo udevadm control --reload-rules
sudo udevadm trigger --name-match=kvm || true
sudo chmod 0666 /dev/kvm
ls -l /dev/kvm
test -w /dev/kvm && echo "KVM is writable" \
|| { echo "KVM NOT available — hosted runner lacks hardware virtualization"; exit 1; }

- name: Install QEMU, OVMF, swtpm and ISO tooling
shell: bash
run: |
sudo apt-get update
sudo apt-get install -y --no-install-recommends \
qemu-system-x86 qemu-utils ovmf swtpm swtpm-tools \
squashfs-tools xorriso openssh-client

- name: Install Python deps for the harness
shell: bash
run: pip install pyyaml

# UEFI PXE needs an EFI boot binary (OVMF can't run a raw iPXE script the
# way the BIOS NIC option ROM does). Build ipxe.efi once, with an embedded
# script that chainloads the per-run boot.ipxe over TFTP — so the EFI
# binary itself is static and cacheable while the real boot script stays
# dynamic. Bump the cache key when the embedded script changes.
- name: Cache iPXE EFI binary
id: ipxe-cache
uses: actions/cache@v5
with:
path: tests/integration/fixtures/ipxe.efi
key: ipxe-efi-chain-v1

- name: Build iPXE EFI for UEFI netboot
if: steps.ipxe-cache.outputs.cache-hit != 'true'
shell: bash
run: |
sudo apt-get install -y --no-install-recommends \
build-essential liblzma-dev git
git clone --depth 1 https://github.com/ipxe/ipxe.git /tmp/ipxe
printf '#!ipxe\ndhcp\nchain tftp://${next-server}/boot.ipxe\n' \
> /tmp/embed.ipxe
make -C /tmp/ipxe/src -j"$(nproc)" bin-x86_64-efi/ipxe.efi \
EMBED=/tmp/embed.ipxe
mkdir -p tests/integration/fixtures
cp /tmp/ipxe/src/bin-x86_64-efi/ipxe.efi \
tests/integration/fixtures/ipxe.efi

- name: Cache the base ISO
id: iso-cache
uses: actions/cache@v5
with:
path: tests/integration/fixtures/${{ inputs.iso-name }}
key: lmde-iso-${{ inputs.iso-name }}

- name: Download and verify the base ISO
if: steps.iso-cache.outputs.cache-hit != 'true'
shell: bash
run: |
mkdir -p tests/integration/fixtures
cd tests/integration/fixtures
curl -sLO https://mirrors.kernel.org/linuxmint/debian/sha256sum.txt
curl -sLo "${{ inputs.iso-name }}" "${{ inputs.iso-url }}"
sha256sum -c <(grep "${{ inputs.iso-name }}" sha256sum.txt)

- name: Build the dev ISO
shell: bash
run: python tests/integration/harness/make_test_iso.py
90 changes: 90 additions & 0 deletions .github/workflows/integration-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
name: integration tests

# Boots real VMs and performs full unattended installs — slow and
# KVM-dependent, so it runs nightly and on demand rather than per-push.
on:
schedule:
- cron: "0 4 * * *" # 04:00 UTC nightly
workflow_dispatch:
inputs:
scenarios:
description: "Space-separated scenario names (default: all)"
required: false
default: ""

# A newer run supersedes an in-flight one on the same ref (these are
# expensive; don't pile them up).
concurrency:
group: integration-${{ github.ref }}
cancel-in-progress: true

jobs:
# Fast gate: the failure-mode scenarios abort in ~2 min and need no
# successful install, so they run first and block the slow matrix.
failure-modes:
runs-on: ubuntu-latest
timeout-minutes: 30
steps:
- uses: actions/checkout@v6
- uses: ./.github/actions/vm-setup
- name: Run failure-mode scenarios
run: |
for s in fail-malformed-yaml fail-no-disk-match fail-bios-grub-on-uefi fail-snapshots-on-ext4; do
python tests/integration/harness/run_scenario.py \
"tests/integration/scenarios/$s.yaml" \
--junit "tests/integration/.work/$s.xml"
done
- uses: actions/upload-artifact@v7
if: always()
with:
name: failure-mode-results
path: tests/integration/.work/*.xml

install-scenarios:
needs: failure-modes
runs-on: ubuntu-latest
timeout-minutes: 45
strategy:
fail-fast: false
matrix:
scenario: [bios-simple, uefi-simple, uefi-lvm, uefi-lvm-luks, bios-multi-disk, pxe-simple, pxe-uefi-simple, netboot-ipv6-simple, custom-uefi, custom-btrfs, static-network, uefi-luks-prompt, raid1-bios, raid5-lvm-bios, flatpak]
steps:
- uses: actions/checkout@v6

- name: Skip if not selected
id: gate
run: |
sel="${{ github.event.inputs.scenarios }}"
if [ -n "$sel" ] && ! echo "$sel" | grep -qw "${{ matrix.scenario }}"; then
echo "run=false" >> "$GITHUB_OUTPUT"
else
echo "run=true" >> "$GITHUB_OUTPUT"
fi

- uses: ./.github/actions/vm-setup
if: steps.gate.outputs.run == 'true'

- name: Run ${{ matrix.scenario }}
if: steps.gate.outputs.run == 'true'
run: |
python tests/integration/harness/run_scenario.py \
"tests/integration/scenarios/${{ matrix.scenario }}.yaml" \
--keep \
--junit "tests/integration/.work/${{ matrix.scenario }}.xml"

- name: Collect serial log on failure
if: failure() && steps.gate.outputs.run == 'true'
run: |
mkdir -p artifacts
cp "tests/integration/.work/${{ matrix.scenario }}/${{ matrix.scenario }}-serial.log" \
"artifacts/${{ matrix.scenario }}-serial.log" || true
cp tests/integration/.work/*.xml artifacts/ || true

- uses: actions/upload-artifact@v7
if: always() && steps.gate.outputs.run == 'true'
with:
name: ${{ matrix.scenario }}-results
path: |
tests/integration/.work/${{ matrix.scenario }}.xml
artifacts/
if-no-files-found: ignore
34 changes: 34 additions & 0 deletions .github/workflows/nfs-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: nfs tests

# Stands up a real NFS server on the runner and fetches an answer file through
# the installer's actual nfs:// transport over the full matrix of protocol
# version (v3, v4.2) x address form (IPv4, IPv6, hostname). Cheap enough
# (~2 min) to run per push for fast feedback.
on:
push:
branches: [automated-install]
pull_request:
workflow_dispatch:

jobs:
nfs:
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v6

- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Install test dependencies
run: pip install -r tests/requirements-test.txt

- name: Provision NFS server (v3 + v4.2, IPv4/IPv6/hostname)
run: bash tests/nfs/setup-nfs-server.sh

- name: Run real NFS transport tests
# mount(2) needs root, so run pytest under sudo with the same Python
# (its site-packages already hold the installed test deps).
run: sudo env LI_NFS_TEST=1 "$(command -v python)" -m pytest tests/nfs -v
28 changes: 28 additions & 0 deletions .github/workflows/unit-tests.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: unit tests

on:
push:
branches: [master, automated-install]
pull_request:
workflow_dispatch:

jobs:
pytest:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version: ["3.11", "3.12", "3.13"]
steps:
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install test dependencies
run: pip install -r tests/requirements-test.txt

- name: Run unit tests
run: pytest tests/unit -v
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,11 @@ debian/*debhelper*
debian/files
debian/*substvar*
*interface.ui~
usr/share/locale/*
usr/share/locale/*
__pycache__/
*.pyc
.pytest_cache/
.venv/
tests/integration/fixtures/*.iso
tests/integration/fixtures/sha256sum.txt
tests/integration/.work/
Loading