Test the USB installer in a virtual machine¶
This acceptance test boots the published reduced USB .img, installs Ubuntu
onto a separate blank virtual disk, boots that disk without the installer, and
checks that Magic Stick reaches its private first-run setup state. It is more
than the media-integrity build check, and it is different
from installing a cloud VM with cloud-init.
What the test does¶
- Resolve an immutable
installer-main-*orinstaller-develop-*candidate using its build-input fingerprint and verified build manifest. Download the image and check its SHA-256 before use. - Create a short-lived Spot host in the approved
qm-dev-vibecodingAzure subscription. Only the CI runner's current IPv4 address may reach SSH. - Copy the image and edit only the copy's
CIDATApartition with disposable autoinstall answers: DHCP, the official Ubuntu archive, a generated Linux identity and SSH key, and the explicitly named empty target disk. Pin the first-boot source to the exact test commit. The published.img, Ubuntu installer payload, target package list and Magic Stick converge command are not changed. - Boot the image as a virtual USB device under UEFI/KVM. The installer must power off after installation. Boot again from the 100 GiB target disk with the USB image detached.
- Check Ubuntu 26.04, successful cloud-init, active K3s, a Ready Node, Ready
first-run-bootstrapFlux Kustomization,ApplianceSetup/localinPending, and the private setup page on the VM's forwarded test port. - Save a short JSON result with stage names only and delete the entire
disposable Azure resource group. The test's SSH key, generated password,
edited
CIDATA, claim and raw guest logs are not CI artifacts.
The test stops before claiming the setup code or creating the first human administrator. A pass therefore means “installed and ready for first-run setup”, not “all product features work”. Manual installer-screen behavior, physical USB boot, Wi-Fi, Ethernet hardware, Secure Boot and GPU inference still require separate acceptance. A Spot eviction is an infrastructure interruption, not proof of an installer defect.
CI configuration¶
The Installer VM acceptance workflow
runs after a successful main installer-image workflow, weekly on main, or
on manual dispatch from main or develop. Manual dispatch can be used for the
first acceptance run. The automatic test and the daily expired-resource cleanup
stay disabled until the repository variable AZURE_INSTALLER_VM_ENABLED is set
to true.
Configure the protected GitHub Actions environment installer-vm-acceptance
with Azure workload-identity federation and these environment secrets:
| Secret | Value |
|---|---|
AZURE_CLIENT_ID |
Application or user-assigned identity client ID |
AZURE_TENANT_ID |
Tenant ID |
AZURE_SUBSCRIPTION_ID |
The ID of qm-dev-vibecoding, never a production subscription |
Grant the CI identity only the rights required to create and remove the test
resources in that dedicated development subscription. The test script refuses
any subscription whose resolved name is not qm-dev-vibecoding. Protect the
environment with required reviewers if CI resource creation needs an approval
gate. The workflow uses OIDC rather than storing a client secret. First run the
workflow manually; enable its recurring triggers only after this has passed.
The host is Standard_D8s_v5 in Germany West Central, with nested
virtualization, a 160 GiB Standard SSD OS disk, and a Spot bid ceiling of
USD 0.20/hour. The test VM inside it gets 4 vCPUs and 16 GiB RAM. Spot
capacity and price are variable; provisioning can fail without implicating
Magic Stick. The resource group is tagged purpose=magicstick-installer-vm-test
and with an expiration time four hours after creation. The script requests and
waits for deletion on exit. A daily cleanup job removes only expired groups
whose name, location and purpose tag all match this test. If a runner is killed
before cleanup and that job is not enabled, inspect the tagged
ms-installer-ci-* groups in qm-dev-vibecoding and remove only groups that
this workflow created; the tag alone is not permission to delete another
project's resources.
The script defaults to Spot. A single explicitly approved diagnostic run may
set both INSTALLER_VM_PRIORITY=Regular and ALLOW_ON_DEMAND=YES; the scheduled
workflow never makes that cost-changing switch automatically. The current
qm-dev-vibecoding Germany West Central Spot quota is only three vCPUs, while
this host needs eight. Raise the Spot quota before enabling scheduled acceptance,
or keep using explicitly approved one-off regular VMs. Check current Azure
prices and quotas again before a long run; the test's disk and public IP also
incur charges.
Local and failure checks¶
The VM harness is installer_vm_acceptance.py.
It requires a Linux host with /dev/kvm, QEMU, OVMF, mtools, python3-yaml,
OpenSSH and network access to Ubuntu's mirror, GitHub and container registries.
The Azure wrapper provisions that
host and supplies an image whose checksum came from the build manifest. The
published media is not rebuilt merely to test it.
Before invoking Azure, check the local contract and shell syntax:
python3 -m unittest tests.test_installer_vm_acceptance
bash -n tools/azure_installer_vm_acceptance.sh
A failed run keeps only report.json as a CI artifact. Its stages distinguish
media preparation, installer shutdown, first boot, host convergence, Kubernetes
readiness and setup-page readiness. Raw logs and test-only credentials remain on
the VM until its resource group is deleted; they are not published. If Azure
cleanup is not confirmed, treat that as an operational failure and inspect the
named test resource group immediately. Do not rerun a failed test against the
same target disk: each run must start from an empty disk.