Context. DOCFIX-188 fixed the OPNsense boot triple-fault (a 2 MiB guest). With the guest finally getting its RAM, the kernel got far enough to reach interrupt initialisation -- and hit a second, independent defect that the first bug had been masking.
Domain reports running, burns 100% of one core indefinitely, emits nothing further on the console, and never touches the network. Serial log (measured) ends at:
---<<BOOT>>--- panic: running without device atpic requires a local APIC cpuid = 0 KDB: stack backtrace: apic_init() at apic_init+0xfc/frame ... mi_startup() at mi_startup+0xb5/frame ... KDB: enter: panic db>
The 100% CPU spin is the guest parked at the db> kernel debugger prompt. A domain in this state looks healthy to virsh -- running (booted), no error -- which is exactly how it evades casual checks.
The QEMU cmdline carried -machine pc-i440fx-noble,...,acpi=off.
None of the three VM modules emitted a features block, so libvirt defaulted ACPI off. FreeBSD discovers the local APIC from ACPI's MADT table, and the OPNsense kernel ships no atpic fallback -- so with ACPI disabled it has no usable interrupt controller and panics in apic_init() before userland.
features = {
acpi = true
apic = {}
}
modules/opnsense-edge -- the live blocker; FreeBSD panics outright without it.modules/cloudinit-vm -- MAAS/NetBox/GitBucket. A Linux guest boots without ACPI but degraded: no clean ACPI shutdown/reboot signalling, unreliable CPU/IRQ enumeration.modules/node-vm -- the MAAS-managed node VMs. MAAS drives power off/on via ACPI signalling, so without it a graceful virsh shutdown (and therefore MAAS power control) never works -- nodes could only ever be hard-stopped. This one would have been a genuinely nasty Stage-3 debug.Every ordinary libvirt guest sets both; there was no reason ours did not.
Like memory_unit, tofu validate cannot catch this: the entire features block is optional, so its absence is schema-valid. scripts/opentofu-validate.sh gains S2, which fails any libvirt_domain that does not enable ACPI. The --check-memory-unit flag is renamed --static-only (old name kept as a back-compat alias) since it now runs S1 + S2.
Harness tests/opentofu-validate/ 4 -> 6 PASS. New fixtures/s2-bad/ is the key case: it passes S1 and still fails S2, proving the two guards catch independent classes rather than one masking the other.
| Before | After | |
|---|---|---|
| QEMU machine | acpi=off |
acpi=on |
| Kernel | panic: ... requires a local APIC, db> |
boots to userland |
| CPU | 100.0% of one core (ddb spin) | idle (~19s total) |
| Network | nothing | DHCP lease 172.30.1.126, hostname OPNsense |
| Memory (DOCFIX-188, re-confirmed) | -- | BIOS 639kB/2096108kB available memory = full 2 GiB |
OPNsense now boots and runs. Both boot bugs are closed.
tests/opentofu-validate/run-tests.sh: 6 PASS / 0 FAIL.scripts/repo-lint.sh: 0 fail (1 documented legacy warn).tofu fmt/validate: clean.OPNsense came up on factory defaults (WAN via DHCP) instead of our static 172.30.1.2 / LAN 10.10.0.1. The config ISO is verified well-formed -- it is ISO9660 labelled OPNSENSE_CFG and contains CONF/CONFIG.XML with <opnsense> and 10.10.0.1 inside (grepped from the raw image) -- and it is attached as a SATA cdrom. So the payload is right and the Importer simply did not consume it.
This is the mechanism modules/opnsense-edge's own header has always flagged UNVERIFIED ("whether the Configuration Importer's ISO9660 support actually behaves as described once booted for real"). The research is well-sourced; it had just never been exercised. Now it has, and it did not work as assumed.
Do NOT guess the fix. Read the Importer's own console output first -- it runs early in boot and reports what it scanned. Untested hypothesis worth checking (NOT a conclusion): ISO9660 8.3 uppercase naming vs the lowercase /conf/config.xml the Importer looks for.
Note the unanswered ping to the WAN address is NOT a fault -- OPNsense blocks inbound on WAN by default. Do not chase it.
libvirt creates the serial log root:0600 and recreates it on every domain create, so reading the single most valuable artifact during a boot incident needs an interactive sudo each time. This materially contributed to the original misdiagnosis. A default ACL on the staging dir does NOT fix it: libvirt's 0600 creation mode sets the ACL mask to ---, which nullifies any named-user entry (measured: user:jessea123:r-- #effective:---).
Proposed durable fix (NOT yet implemented/verified): have the runbook/module pre-create the serial log 0644 before tofu apply, so libvirt appends to an already-readable file rather than minting a root:0600 one. Verify on a real recreate before trusting it.
features blocks from the three libvirt_domain resources (restores the panic -- do not).s2_check + the --static-only rename in scripts/opentofu-validate.sh, and tests/opentofu-validate/fixtures/s2-bad/ + T5/T6/T7.git revert <sha>. No live-state dependency.