Boot and hardware

Updated

July 30, 2026

Boot and hardware

Goal: Document and intentionally configure the lab’s boot loader, kernel packages, and firmware—so recovery and hardware quirks are not mysteries.

Important

Boot experiments can brick a bad disk layout. Prefer the disposable VM, snapshot first, and change one variable at a time.

Why this day exists

When generations fail to boot, the difference between “NixOS is broken” and “I know how to pick last generation / fix EFI” is this literacy. Hardware config is part of ownership.


Theory 1 — Boot chain (UEFI lab)

UEFI firmware
  → EFI system partition (ESP) files
  → boot loader (systemd-boot or GRUB)
  → kernel + initrd (from a generation)
  → stage-1 (find root, unlock, …)
  → stage-2 / systemd

NixOS generations typically install loader entries pointing at store paths for kernel/initrd.


Theory 2 — systemd-boot vs GRUB

Loader Common on Notes
systemd-boot UEFI VMs/laptops Simple, generation entries
GRUB BIOS + UEFI More knobs; legacy friendly
# systemd-boot (UEFI)
boot.loader.systemd-boot.enable = true;
boot.loader.efi.canTouchEfiVariables = true;

# GRUB example (do not enable both casually)
# boot.loader.grub.enable = true;
# boot.loader.grub.device = "/dev/vda";

Pick one model. Mixing without care is a classic footgun.

Generation UI

  • systemd-boot: machine-id entries, often need key to show menu
  • GRUB: menu with generation list

Document how your VM displays the menu (ESC, hold space, etc.).


Theory 3 — Kernel packages

boot.kernelPackages = pkgs.linuxPackages;          # default channel kernel
# boot.kernelPackages = pkgs.linuxPackages_latest;
# boot.kernelPackages = pkgs.linuxPackages_6_12; # example versioned attr — check 26.05
Choice Trade-off
Default Matches nixpkgs expectations
_latest Newer hardware; more churn
Pinned series Stability / specific needs

Kernel modules:

boot.kernelModules = [ ];
boot.initrd.availableKernelModules = [ /* often from hardware-config */ ];
boot.extraModulePackages = [ ];

hardware-configuration.nix usually fills initrd modules for disks/filesystems—do not delete blindly.


Theory 4 — Firmware

hardware.enableRedistributableFirmware = true;
# hardware.enableAllFirmware = true; # broader; licensing awareness

VMs often need little firmware; bare metal Wi-Fi/GPU might need redistributable firmware enabled.


Theory 5 — CPU / virt hardware bits

Common NixOS knobs:

# virtualisation-friendly examples (enable only if true for you)
# services.qemuGuest.enable = true;
# virtualisation.hypervGuest.enable = true;

Also: microcode updates on real Intel/AMD machines (hardware.cpu.*.updateMicrocode)—relevant on bare metal, less so on many VMs.


Theory 6 — hardware-configuration.nix discipline

Do Don’t
Import it Hand-edit UUIDs casually
Regenerate after disk changes Copy another machine’s hardware file
Review initrd modules Assume it never needs refresh

Regenerate carefully (live media or docs)—wrong root UUID → unbootable.


Theory 7 — Temporary boot parameters

Kernel params:

boot.kernelParams = [ "console=ttyS0" ]; # example serial console for some VMs

Useful for debugging; remove noise when done. One change per rebuild when experimenting.


Worked examples bank

Example A — Document current loader

bootctl status 2>/dev/null | head -40
ls /boot 2>/dev/null | head
ls /boot/loader/entries 2>/dev/null | head

Example B — Kernel identity

uname -r
nixos-version
ls /run/current-system/kernel

Example C — Safe param experiment

Add a harmless boot.kernelParams comment-level change only after snapshot; or set a documented console param if your hypervisor uses serial.


Lab 1 — Boot path document

Create docs/boot.md in the flake repo:

# Boot path — lab
- Firmware: UEFI / BIOS
- Loader: systemd-boot / GRUB
- ESP mount: /boot (options)
- Kernel package option: …
- uname -r: …
- How to open generation menu: …
- Snapshot name: …

Fill with real commands’ output.


Lab 2 — Verify loader config matches reality

Compare flake boot.loader.* with bootctl status / GRUB presence. Fix mismatches.


Lab 3 — Kernel package awareness

  1. Record current boot.kernelPackages (implicit default if unset).
  2. Optionally try linuxPackages_latest in a VM with snapshot.
  3. Reboot; uname -r; note difference.
  4. Revert if not needed.

If you skip the change, still write how you would do it.


Lab 4 — Firmware decision

State in docs:

  • VM only → firmware settings chosen
  • Bare metal → enable redistributable firmware if devices need it

Rebuild if you change it.


Lab 5 — Recovery dry-run (no panic)

Without breaking the system:

  1. List generations in bootloader docs
  2. Rehearse verbally: “If black screen after upgrade, I will…”
  3. Update the rebuild and generations chapter rollback card with boot-menu specifics


Theory 8 — boot.loader.timeout and generation menu

boot.loader.timeout = 3; # seconds; 0 may hide menu unless key held

For labs that practice rollback, a short non-zero timeout is friendlier than zero. Document the key to force the menu on your firmware (often Esc or hold Space for systemd-boot).


Theory 9 — Initrd and LUKS awareness (preview)

Encrypted roots need initrd unlock configuration. Even if your VM is plaintext:

Concept Why note it
boot.initrd.luks.devices Maps UUID → mapped name
Prompt vs keyfile Physical vs automated unlock
Wrong UUID Emergency shell at stage-1

Disko/impermanence (Stage VI) will revisit layout; today only name the risk.


Theory 10 — hardware-configuration.nix vs flake purity

hardware-configuration.nix is machine-specific generated state. It belongs in git for the lab host or is generated on first install and kept private per machine.

Approach Trade-off
Commit lab VM hardware file Easy clone of this VM
Generate per machine Correct for fleet; clone drill needs care

Do not copy UUIDs between bare metal and VM casually.


Worked example D — QEMU guest agent (virt)

services.qemuGuest.enable = true; # if hypervisor is QEMU/KVM

Helps shutdown/reboots from host tooling. Enable only when accurate.


Lab 6 — Loader entries inventory

bootctl list 2>/dev/null | head -60
ls -la /boot/loader/entries 2>/dev/null | head

Map one entry to a NixOS generation number in docs/boot.md.


Lab 7 — Kernel param one-shot (snapshot)

With a VM snapshot:

boot.kernelParams = [ "loglevel=7" ]; # noisier logs; lab only

Rebuild, reboot, confirm cat /proc/cmdline, revert.

Common gotchas

Symptom Theory
Two boot loaders enabled Conflicts
canTouchEfiVariables false in some virt May need alternate install path
Removed initrd modules Disk not found
Latest kernel + guest tools mismatch Virt drivers
Editing /boot by hand Lost on rebuild

Checkpoint

  • docs/boot.md complete
  • Loader in flake matches reality
  • Kernel identity recorded
  • Firmware policy chosen
  • Rollback card updated with boot menu
  • Snapshot available before any kernel play

Commit

cd ~/nixos-config
git add .
git commit -m "day20: boot hardware documentation"