Day 8 — Boot, Filesystems & Gate I
Day 8 — Boot, Filesystems & Gate I
Day 20 — Boot & hardware
Stage II · ~4h
Goal: Document and intentionally configure the lab’s boot loader, kernel packages, and firmware—so recovery and hardware quirks are not mysteries.
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 awarenessVMs 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 VMsUseful 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 | headExample B — Kernel identity
uname -r
nixos-version
ls /run/current-system/kernelExample 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
- Record current
boot.kernelPackages(implicit default if unset).
- Optionally try
linuxPackages_latestin a VM with snapshot.
- Reboot;
uname -r; note difference.
- 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:
- List generations in bootloader docs
- Rehearse verbally: “If black screen after upgrade, I will…”
- Update Day 13 rollback card with boot-menu specifics
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/KVMHelps 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 | headMap 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 onlyRebuild, 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.mdcomplete
- 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"Tomorrow
Day 21 — Filesystems intro. Mounts, state vs store, and the impermanence idea (persist deliberately)—without a full impermanence deployment yet.
Day 21 — Filesystems intro
Stage II · ~4h (theory-heavy)
Goal: Inventory mounts and partitions; separate store, state, and ephemeral paths; understand the impermanence idea (persist deliberately) without implementing full impermanence yet.
Why this day exists
Nix makes the system closure rebuildable; it does not automatically make your data policy sane. Ops failures often come from “I rolled back the system but the database is gone” or the reverse. Label what must persist.
Theory 1 — Three layers of “the disk”
| Layer | Examples | Rebuild replaces? |
|---|---|---|
| Store | /nix/store |
Via new paths; GC collects dead |
| Config activation | much of /etc |
Yes, from generation |
| State | /var, /home, databases |
No (unless you design it) |
┌────────────────────────────┐
│ /home /var (state) │ ← backup & persist policy
├────────────────────────────┤
│ /etc (mixed; mostly managed)│
├────────────────────────────┤
│ /nix/store (immutable pkgs) │
└────────────────────────────┘
Theory 2 — fileSystems options
From hardware-configuration.nix typically:
fileSystems."/" = {
device = "/dev/disk/by-uuid/…";
fsType = "ext4";
};
fileSystems."/boot" = {
device = "/dev/disk/by-uuid/…";
fsType = "vfat";
};| Field | Role |
|---|---|
device |
UUID/LABEL preferred over raw /dev/sdX |
fsType |
ext4, btrfs, xfs, vfat, zfs, … |
options |
mount flags (noatime, …) |
neededForBoot |
initrd critical mounts |
Swap:
swapDevices = [{ device = "/dev/disk/by-uuid/…"; }];Theory 3 — Why UUID/LABEL
Device names (vda2, sda1) can reorder. By-uuid is stable:
ls -l /dev/disk/by-uuid
lsblk -fTheory 4 — State directories that matter
| Path | Typical content |
|---|---|
/var/lib |
Service state (postgres, docker, …) |
/var/log |
Logs (sometimes ephemeral OK) |
/var/cache |
Regenerable caches |
/home |
User data, secrets, keys |
/root |
Root’s home |
/etc/ssh host keys |
Machine identity (special) |
/nix |
Store + nix state database |
When you enable services (Stage IV+), each service’s state dir is part of your persist list.
Theory 5 — Impermanence idea (concept only)
Impermanence (pattern/tooling, Day 58 deep dive): treat root as mostly ephemeral; bind-mount or symlink only chosen paths from a persistent volume.
boot → empty-ish root
→ restore/persist: /home, /var/lib/postgres, /etc/ssh, …
→ everything else regenerated by NixOS activation
Why people want it
| Benefit | Meaning |
|---|---|
| Force declarative truth | Untracked snowflakes disappear |
| Cleaner systems | Less drift |
| Clear backup targets | Persist volume = backup unit |
Costs
| Cost | Meaning |
|---|---|
| Design work | Must list persist paths correctly |
| Host keys / secrets | Easy to forget; painful |
| Debugging | “Where did my file go?” |
Today: you do not need to enable impermanence. You do need a written list of what would be persisted.
Theory 6 — Store is not a backup of state
nixos-rebuild switch
→ new system closure
≠ snapshot of /var/lib/postgresql
Backups are a separate story (Day 69). Generations help software; not data.
Theory 7 — tmpfs and ephemeral mounts (awareness)
# conceptual examples — do not apply casually
# boot.tmp.useTmpfs = true;
# fileSystems."/tmp" = { device = "tmpfs"; fsType = "tmpfs"; };Useful later with impermanence. Understanding: not every path needs disk durability.
Worked examples bank
Example A — Mount inventory
findmnt -R /
lsblk -f
df -hTExample B — What’s big?
sudo du -xhd1 / 2>/dev/null | sort -h
sudo du -sh /nix/store /var /home 2>/dev/nullExample C — SSH host key paths
ls -l /etc/ssh/ssh_host_*These are state even though under /etc.
Lab 1 — Full inventory table
In docs/filesystems.md:
| Mountpoint | device/UUID | fsType | options | role (root/boot/state/…) |
|---|
Fill from findmnt + lsblk -f.
Lab 2 — Persist classification
Label each:
| Path | Persist? | Why | Backup? |
|---|---|---|---|
/nix/store |
rebuildable | no (or cache strategy) | |
/home/alice |
yes | yes | |
/var/log |
maybe | optional | |
/var/lib |
mostly yes | yes | |
/etc/ssh host keys |
yes | identity | yes |
/etc/nixos if still used |
prefer git flake | git |
Be specific to your lab services.
Lab 3 — State created by your Stage II work
List state produced so far:
- user homes
- SSH host keys
- timer logs you wrote
- any DB? (probably not yet)
Lab 4 — Impermanence reading (light)
Skim what Day 58 will cover (impermanence module concepts) from memory of Theory 5. Write five sentences: Would I want impermanence on this lab VM? Why/why not?
No implementation required.
Lab 5 — Danger checklist
Confirm you will never:
rm -rf /nix/storeas “cleanup”
- Format ESP casually
- Delete
/var/libbecause “Nix will regenerate” (it will not for app data)
Theory 8 — Btrfs / ZFS awareness without deploying them
You may see labs using btrfs snapshots or ZFS datasets. For Stage II:
| FS | Persist story |
|---|---|
| ext4 default VM | Simple; backups = copy/dump |
| btrfs | Subvolumes + snapshots possible |
| ZFS | Datasets; often external to vanilla hardware-config |
Do not switch root FS casually mid-lab. Understand that snapshot tools are about state, not store purity.
Theory 9 — /nix on its own mount (optional design)
Some installs put /nix on a dedicated volume:
| Benefit | Cost |
|---|---|
| Reinstall root without losing store cache | More mount points to get right |
| Clear size accounting | Hardware-config complexity |
Record whether your lab is single-root or split.
Theory 10 — Bind mounts as proto-impermanence
# conceptual — only if you knowingly add a data disk
# fileSystems."/var/lib/postgres" = {
# device = "/data/postgres";
# options = [ "bind" ];
# };Bind mounts are how many impermanence setups pin state onto a durable dataset. Note the idea; implement later (Day 58).
Worked example D — neededForBoot meaning
fileSystems."/" = {
device = "/dev/disk/by-uuid/…";
fsType = "ext4";
neededForBoot = true; # default true for /
};Extra mounts required to reach multi-user (e.g. separate /var) may need neededForBoot = true or the system reaches emergency targets.
Lab 6 — What survives nixos-rebuild switch?
Create a marker:
echo "state-$(date -Is)" | sudo tee /var/tmp/day21-marker
echo "etc-try" | sudo tee /etc/day21-hand-edit 2>/dev/null || trueRebuild switch. Check which markers remain. Note: hand-edited /etc files may be replaced by activation—another reason flake is SoT.
Lab 7 — Size budget sentence
From Lab 2 du output, write one budget sentence:
“If I wipe the VM disk, I lose X (state) but can rebuild Y (system) from git+flake.lock.”
Common gotchas
| Symptom | Theory |
|---|---|
| Rolled back OS, data weird | Data ≠ generation |
| Copied hardware-config UUIDs wrong | Unbootable |
Thought /etc fully ephemeral |
Host keys, some static files |
| No backup of home | Lab loss on disk wipe |
| Impermanence without persist list | Instant footgun |
Checkpoint
- Mount inventory table complete
- Persist classification written
- Store vs state explained in own words
- Impermanence idea summarized
- Host keys noted as state
- Docs committed
Commit
cd ~/nixos-config
git add .
git commit -m "day21: filesystems inventory persist vs store"Tomorrow
Day 22 — Stage II gate. Minimal production-shaped host: users, SSH, firewall, one service, one custom module—all in the flake; cold boot + rollback proof.
Day 22 — Stage II gate
Stage II · ~4h (review + proof)
Goal: Prove a minimal production-shaped flake-managed NixOS lab: users, SSH keys, firewall, one service, one custom module—cold boot and rollback work; the flake is the only source of truth.
Why this day exists
Stage III multiplies structure (layouts, Home Manager). Entering it with a snowflake host guarantees pain. Today is an honesty gate: the machine is rebuildable, operable, and documented.
Theory 1 — Syllabus exit criteria
You may leave Stage II when:
- From cold boot you can roll back a bad generation
- The flake is the only source of truth for the lab host
- Host includes: declarative users, SSH key access, firewall policy, at least one service/timer, one custom module
- Notes cover boot path + network + recovery
Theory 2 — Minimal production-shaped host (definition for this gate)
| Capability | Evidence |
|---|---|
| Flake host | nixosConfigurations.<name> rebuilds |
| Pin | flake.lock committed; nixpkgs 26.05 |
| User | Non-root admin in wheel |
| Remote access | SSH key login; password SSH off (or justified) |
| Firewall | Enabled; SSH allowed; extras documented |
| Packages | Daily tools declarative |
| Service | Custom or high-level service active |
| Module | Local options+config module imported |
| Docs | rebuild, rollback, boot, network |
| Recovery | Rollback card tested |
Not required yet: secrets machinery, Home Manager, Disko redesign, multi-host, reverse proxy zoo.
Theory 3 — Cold boot proof
running system (gen N)
→ reboot
→ firmware → loader → kernel → multi-user
→ SSH/console login works
→ systemctl is-system-running (or degraded explained)
Cold boot catches “worked in this session only” activation myths.
Theory 4 — Source of truth test
| Signal flake is SoT | Smell of drift |
|---|---|
| Rebuild from git repo only | Hand edits in /etc that vanish/surprise |
| README rebuild command works | “Also run these shell fixes” |
| hardware-config in git | Hardware only on machine, never backed up |
Theory 5 — Gate knowledge verbalization
Prepare short answers:
switchvstestvsboot
- What rollback does not restore
- Why SSH public keys in git are OK but passwords are not
- Store vs state paths
- Why custom modules use
mkIf cfg.enable
- How you open the boot generation menu
Worked examples bank
Example A — Gate rebuild
cd ~/nixos-config
sudo nixos-rebuild switch --flake .#labExample B — Eval sanity
nix flake show
nix eval .#nixosConfigurations.lab.config.networking.hostName
nix eval .#nixosConfigurations.lab.config.networking.firewall.allowedTCPPortsExample C — Service proof
systemctl is-enabled sshd || systemctl status sshd
systemctl list-timers --all | headLab 1 — Gate repository hygiene
cd ~/nixos-config
git status
git log --oneline | head
test -f flake.lock && echo lock-okEnsure structure roughly:
flake.nix
flake.lock
hosts/lab/configuration.nix
hosts/lab/hardware-configuration.nix
modules/*.nix
docs/{boot,network,rollback,filesystems,modules-study}.md
README.md
Missing docs → write them now (brief is fine).
Lab 2 — Checklist (tick only if true)
Stage II gate checklist
- NixOS 26.05 lab boots (VM preferred)
nix --version/nixos-versionrecorded
- Flake
nixosConfigurationsrebuild switch works
flake.lockcommitted
- Hostname + flake attr documented
- Admin user declarative +
wheel
- SSH key login works
- PasswordAuthentication disabled or exception documented
- Firewall enabled; SSH port allowed
- Port audit exists
environment.systemPackages/programs.*used intentionally
- ≥1 custom systemd service or timer or meaningful
services.*
- ≥1 custom module with
enableoption
- Assertion or deliberate option validation somewhere
- Generations listed; rollback performed at least once in Stage II
- Boot loader + generation menu documented
- Mount inventory + persist list written
- README rebuild instructions accurate
- No private keys or live passwords in git
- Cold boot → login succeeds
Any empty box is a stop for Stage III.
Lab 3 — Cold boot ceremony
- VM snapshot labeled
day22-pre-coldboot(optional safety)
- Reboot lab
- Login console and/or SSH
systemctl is-system-running || true
hostname;curllocalhost service if any
- Record timestamp + generation
Lab 4 — Rollback proof (again)
- Make a tiny visible change (motd package,
environment.etc."day22".text, …)
switch
- Confirm change
sudo nixos-rebuild switch --rollback
- Confirm reversion
- Re-apply good config if needed
Lab 5 — Architecture one-pager
docs/host-diagram.md:
# lab host
- flake: ~/nixos-config #lab
- users: …
- ssh: keys only …
- firewall: …
- services/timers: …
- modules: …
- boot: …
- disks: …
- what is state: …Lab 6 — Teach-back (10–15 min)
Explain aloud or written:
“Here is how I rebuild this host from git; here is how I recover from a bad generation; here is what I must back up because Nix will not.”
If rough, revisit Days 13–15 and 21.
Theory 6 — Generation evidence commands
sudo nix-env -p /nix/var/nix/profiles/system --list-generations | tail -5
readlink -f /run/current-system
nixos-versionGate notes should include current generation number and flake shortrev if you embed git info (optional).
Theory 7 — What “flake is SoT” excludes
Still allowed outside the flake (state):
- Home directory contents (until HM owns them)
- SSH host keys
- Service databases (when you add them)
- Hypervisor snapshots
Not allowed as required ops procedure:
- “Also apt install…”
- “Then manually systemctl edit…”
- “Copy this binary into /usr/local”
Worked example D — Eval probes for gate
cd ~/nixos-config
nix eval .#nixosConfigurations.lab.config.users.users.alice.isNormalUser
nix eval .#nixosConfigurations.lab.config.services.openssh.enable
nix eval .#nixosConfigurations.lab.config.networking.firewall.enableBoolean/string outputs are quick CI-less checks that config intent is present.
Lab 7 — Secret smell re-scan
cd ~/nixos-config
rg -n "initialPassword|password\s*=\s*\"" || true
rg -n "PRIVATE KEY|AKIA" || trueStage IV will add secret machinery; Stage II must not already leak.
Lab 8 — Offline notes pack
Bundle in docs/ or a gate/ folder:
- Rebuild one-liner
- Rollback one-liner
- Boot menu steps
- Console lockout recovery
- Network identity (IP/interface)
Print or keep on the host that is not the VM (so a broken VM does not eat the runbook).
Common gotchas
| Symptom | Theory |
|---|---|
| Gate “passes” but docs missing | Future you fails |
| Secrets in history | Stage IV will not save past leaks |
| Service only started manually | Not declarative |
| Hardware file not in git | Disk loss → pain |
| Still using channels for system | Diverges from flake story |
Checkpoint
- All Stage II checklist boxes ticked
- Cold boot verified
- Rollback re-verified
- Host diagram written
- Teach-back completed
- Ready for Stage III layout + Home Manager
Commit
cd ~/nixos-config
git add .
git commit -m "day22: stage II gate production-shaped host"Tag optional:
git tag stage-ii-gateTomorrow
Day 23 — Flake layout. Split hosts/ / modules/ / homes/ cleanly; stop growing a single tangled config as Stage III begins (Home Manager arrives soon after).