Proxmox System Safety Rules
LVM Operations on Root Volumes
Do NOT create LVM snapshots of the Proxmox root volume (
pve/root). Merging snapshots on a live root volume is unreliable and can leave the system in a stuck merge state requiring reboot.Use file-based config backups (
tar) andvzdumpfor VMs instead.
Reboot Awareness
If a playbook changes GRUB, initramfs, or kernel modules, a reboot may be needed.
Set
pci_passthrough_allow_reboot: truein host vars to allow automated reboots.After reboot, wait for SSH to come back with
wait_for_connection.
Hardware Detection Requirements
iGPU: every modern Intel CPU has one.
proxmox_igpuMUST hard-fail if absent.WiFi + VT-d/IOMMU: required for PCI passthrough.
proxmox_pci_passthroughMUST hard-fail if IOMMU is not active after reboot or groups are invalid.NIC count: OK to handle dynamically (hardware legitimately varies).
Hardware Failure Requirements
NEVER add "graceful skip" for hardware expected on every host. Silent skips mask fixable BIOS settings (VT-d disabled) behind warnings that are easy to miss.
Previous bug:
proxmox_pci_passthroughsilently skipped WiFi passthrough when IOMMU groups were invalid on mesh1. Root cause was VT-d disabled in BIOS — a 30-second fix masked for an entire test cycle.
System Safety Decision Tree
- Use this decision tree:
Is it modifying LVM on root? ├── YES → BLOCK. Use tar + vzdump instead. └── NO → SAFE. Proceed.
Hardware Detection Pattern
- For expected hardware (iGPU, WiFi with VT-d/IOMMU), always hard-fail when absent rather than graceful skip. This ensures critical issues are caught immediately rather than silently ignored.
PCI Passthrough Prerequisites
WiFi PCIe passthrough requires the
q35machine type. Setmachine: q35whenwifi_pci_devicesis non-empty.IOMMU group isolation is mandatory. ALWAYS verify before binding to vfio-pci.
WiFi NICs must be excluded from bridge creation — they're passed through via PCIe, not bridged.
Missing DRI Devices Recovery
Between test cycles, after VM passthrough, or hookscript operations, the iGPU can lack
/dev/drinodes even though the native driver module is loaded. Two causes: (a) device bound to vfio-pci, (b) device unbound from native driver but module still loaded (PCI rescan alone won't re-bind).proxmox_igpuMUST check/dev/dri/renderD128existence BEFORE device detection. If missing: unbind from whatever driver holds the device, clear driver_override, PCI rescan, then EXPLICITLY bind to the native driver (echo PCI_ADDR > /sys/bus/pci/drivers/i915/bind). PCI rescan alone does NOT auto-bind when the module is already loaded.Previous bug:
molecule converge(no cleanup) after a passthrough test left the iGPU without DRI nodes onhome.proxmox_igpusawi915inlsmod, skipped recovery, then hard-failed on "DRI devices missing." Explicit driver bind after PCI rescan is the fix.
Proxmox firmware package conflicts
On Proxmox VE,
pve-firmwarebundles all Intel/AMD firmware including iwlwifi. NEVER install standalonefirmware-iwlwifi— it conflicts withpve-firmwareand triggers the Proxmox apt hook to block removal of theproxmox-vemeta-package.proxmox_pci_passthroughMUST check forpve-firmwarebefore attempting to installfirmware-iwlwifi. Ifpve-firmwareis present, skip the install — the firmware is already available.Previous bug:
firmware-iwlwifiinstall failed on mesh1, bridge-1, bridge-2, and home withpve-apt-hook returned error code (1). All hosts hadpve-firmwareinstalled, which already provides iwlwifi firmware.
Enterprise Repository Management
Proxmox enterprise repo disabling MUST happen in
pre_tasksof infrastructure plays, BEFORE any role that callsapt update. Theproxmox_pci_passthroughrole needsaptfor firmware packages and runs beforeproxmox_igpu.NEVER put repo management inside a role that isn't the FIRST role in the play. If any earlier role needs
apt, the repos won't be ready.Previous bug: Enterprise repo disabling was inside
proxmox_igpu(third role in infra play).proxmox_pci_passthrough(second role) ranapt updatefor firmware packages and failed with 401 Unauthorized onmesh2. Fix: moved topre_tasksof both infra plays insite.yml.
Package Name Verification
NEVER assume a package name is correct without checking. Package names vary between Debian releases, architectures, and distributions. ALWAYS verify with
apt-cache search <keyword>orapt list <name>.Previous bug:
intel-media-va-driver-non-freewas correct on Debian Bullseye but does not exist on Debian Trixie. The correct package isintel-media-va-driver. The task failed with "No package matching" and required manual investigation.
Dynamic Device Detection
NEVER hardcode device paths like
/dev/dri/card0. The card number depends on driver probe order and can change across reboots or kernel updates.ALWAYS detect devices dynamically by querying sysfs driver bindings: iterate
/dev/dri/card*, checkreadlink -f /sys/class/drm/cardN/device/driver, and match on the driver name.Previous bug:
/dev/dri/card0was assumed to be the Intel iGPU, but on a multi-GPU systemcard0was the discrete GPU. Sysfs-based detection finds the correct device regardless of probe order.
iGPU PCI Passthrough (vfio-pci)
Prefer runtime sysfs manipulation over persistent modprobe configs for iGPU passthrough. Writing to
/sys/bus/pci/drivers/vfio-pci/new_idand/sys/bus/pci/drivers/vfio-pci/bindis reversible without initramfs updates. Modprobe blacklists requireupdate-initramfsand a reboot.Single-GPU passthrough is supported for Intel iGPUs and discrete GPUs via Proxmox hookscripts. The hookscript pattern:
pre-startunbinds the GPU from the native driver via sysfs and binds to vfio-pci;post-stopreverses the operation. The host runs headless (SSH/web only) while the VM has the GPU. NEVER attempt GPU passthrough on AMD APU iGPUs (Raven Ridge, etc.) — the GPU shares the SoC die and ANY unbind path (sysfs or modprobe -r) hangs the entire system. This is a hardware limitation, not fixable with hookscripts.NEVER run
modprobe -r amdgpuormodprobe -r i915as GPU cleanup. ALWAYS use sysfs operations: unbind from vfio-pci, cleardriver_override, PCI rescan. The rescan triggers the kernel to auto-bind the native driver. This is safe on ANY host regardless of GPU count.GPU passthrough hookscript (
/var/lib/vz/snippets/gpu-passthrough-hook.sh) manages the full lifecycle: discovers hostpci devices from VM config, stops GPU-consuming LXC containers, suspends conflicting VMs, binds/unbinds via sysfs, persists state in/run/gpu-passthrough/vm-<VMID>.statefor post-stop recovery. The hookscript is generalized — works with any GPU vendor (Intel, AMD, NVIDIA) and any VM that uses--hostpci.Previous bug: cleanup ran
modprobe -r amdgpuon ALL hosts via E2E cleanup. Onai(single AMD GPU, USB ethernet), this caused a kernel panic. Fix: replaced all GPUmodprobe -rwith sysfs unbind + PCI rescan.Previous bug: sysfs unbind of AMD Raven Ridge APU iGPU (1002:15dd) via hookscript pre-start hung the entire system. Unlike discrete GPUs, APU iGPUs share the SoC die with the CPU — even sysfs unbind triggers a GPU reset that freezes the NBIO, killing the entire system including USB ethernet (EHOSTUNREACH). This is the same class of failure as
modprobe -r amdgpubut at the hardware level.Previous bug: PCI rescan after vfio-pci unbind did NOT auto-bind the native driver when the module was already loaded. DRI devices (
/dev/dri/renderD128) did not reappear. Fix: explicitly bind to the native driver after rescan (echo PCI_ADDR > /sys/bus/pci/drivers/i915/bind). The cleanup and hookscript post-stop both must do explicit rebinding, not rely on auto-binding.Cleanup MUST match deployment scope. If the role uses sysfs-only binding (no modprobe configs), cleanup MUST NOT remove modprobe config files. Cleanup MUST also remove hookscript state files from
/run/gpu-passthrough/and the hookscript itself from/var/lib/vz/snippets/.
Hookscript Attachment Ordering
Display-exclusive hookscripts that stop DRI-sharing containers MUST NOT be attached during provisioning. Attaching during
kiosk_lxc(Phase 2.5) causes the hookscript to fire whendesktop_lxcstarts in Phase 3, stopping all DRI containers (Kodi, Moonlight, Kiosk) before their configure plays run.Pattern: deploy the hookscript FILE in the provisioning role, attach it to containers/VMs in a dedicated play AFTER all configure plays finish. This ensures all containers are configured before the hookscript can stop them.
Configure roles for DRI-sharing containers should include a defensive "ensure container is running" guard at the top (check
pct status, start if stopped, wait for readiness). This handles re-runs where hookscripts may already be attached from a previous cycle.Previous bug:
kiosk_lxcdeployed AND attached the display-exclusive hookscript during Phase 2.5. Whendesktop_lxcstarted in Phase 3, the hookscript'spre-start(400)stopped Kodi (301), Moonlight (302), and Kiosk (401).Configure Kodithen failed with "container '301' not running!" Fix: split hookscript deployment (provisioning) from attachment (post-configure play).Verify assertions for DRI-sharing containers (Kodi, Kiosk, Moonlight) MUST skip
systemctl is-activechecks when the Desktop LXC is running and has grabbed the DRI render node. The display-exclusive hookscript manages this conflict. Checkpct status {{ desktop_ct_id }}and gate the service-active assertion.Previous bug: Kodi container was
runningbutsystemctl is-active kodireturnedinactiveduring verify. Root cause: Desktop VM held the iGPU, DRI devices absent from container. Fix: added"'running' not in (_desktop_status.stdout | default(''))"condition to the Kodi service-active assertion.Not all services run as systemd daemons. Moonlight-embedded is an on-demand streaming client binary (
/usr/local/bin/moonlight), not a persistent service. Verify assertions for such services should check binary existence and config deployment, NOTsystemctl is-active.Previous bug: Moonlight verify assertion checked
systemctl is-active moonlightbut there IS nomoonlight.service— moonlight-embedded is compiled from source as a CLI binary with no systemd unit file. The assertion always failed. Fix: changed to check binary existence and config file presence.
Host Recoverability
Every host MUST declare
wol_capable(true/false) in host_vars. This tracks whether the host can be remotely recovered via Wake-on-LAN after a crash or shutdown.USB ethernet adapters do NOT support WoL. The USB host controller powers down in S5 (standby) and lacks magic packet detection circuitry. Hosts connected exclusively via USB ethernet (e.g.,
ai) MUST havewol_capable: false.NEVER run operations that could crash a non-WoL host from automation. This includes
modprobe -rof the sole GPU driver,shutdown,poweroff, or any operation that could trigger a kernel panic. Non-WoL hosts require physical intervention to recover.scripts/wol.shMUST NOT include non-WoL hosts. Unit tests intests/test_wol.pyenforce this. The E2E verify playbook also assertswol_capableis defined for every host and that non-WoL hosts don't appear in wol.sh.Previous bug:
aiwas listed inwol.shwith its PCIe NIC MAC, butaiis connected via USB ethernet only. The PCIe NIC is not connected to the network, making WoL impossible.