Day 5 — NixOS Install, Flake Host & Generations

Updated

July 30, 2025

Day 5 — NixOS Install, Flake Host & Generations

Day 11 — Install NixOS lab

Stage II · ~4h (hands-on heavy)
Goal: Install a disposable NixOS 26.05 “Yarara” lab system (VM strongly preferred), boot it, capture hardware config notes, and prepare for flake-based management tomorrow.

Important

Do not experiment on your only daily-driver disk. Use a VM, spare machine, or spare disk. Reinstall without drama is a feature.

Why this day exists

Generations, rollback, modules, and firewall only become real on a host. Stage I was the package manager + language. Stage II is owning a machine as code—starting with a clean install.


Theory 1 — What you are installing

Piece Role
NixOS Linux distribution built from Nix modules
nixpkgs 26.05 “Yarara” Package + module set baseline for this volume
Installer image Live environment to partition + install
First generation Initial system closure after install

After install you have a bootable system. Flake ownership is Day 12—today can use installer defaults, but mentally prepare to migrate immediately.


Theory 2 — Disposable VM emphasis

Host choice Recommendation
VM (QEMU/KVM, UTM, VirtualBox, VMware, …) Preferred
Spare laptop/NUC Good
Dual-boot daily driver Risky for learning
Cloud VM Fine if you accept networking/SSH from the start

VM resources (practical)

Resource Starting point
RAM 4–8 GB
Disk 40–64 GB+
CPU 2+ cores
Firmware UEFI if available
Snapshot Take one after first successful boot

Snapshots turn “I broke GRUB” into “restore 30 seconds.”


Theory 3 — UEFI vs legacy BIOS

Mode Common loader on NixOS
UEFI systemd-boot (often default) or GRUB EFI
BIOS/CSM GRUB

Prefer UEFI for new labs. Note which you used—Day 20 returns to boot loaders.


Theory 4 — Disk layout options

Manual (classic installer path)

  1. Partition (ESP + root, optional swap)
  2. Format
  3. Mount at /mnt, ESP at /mnt/boot
  4. nixos-generate-config --root /mnt
  5. Edit configuration.nix
  6. nixos-install

Disko (intro only)

Disko declares disks as code. Full Disko redesign is Stage VI (Day 57). Today:

  • Know Disko exists
  • Optionally follow a simple Disko example if you already know it
  • Otherwise manual once is fine and educational

Do not block install on learning Disko deeply today.

Suggested simple layout (UEFI)

ESP  (vfat)  512MB–1GB   → /boot
root (ext4 or btrfs)     → /
swap (optional)

Theory 5 — nixos-generate-config outputs

File Role
hardware-configuration.nix Detected devices, fileSystems, initrd modules
configuration.nix High-level system config (you own this)

Rules of thumb

  • Treat hardware-configuration.nix as machine-specific; rarely hand-merge casually
  • Put policy (users, services, packages) in configuration.nix / modules
  • Tomorrow both get imported from a flake

Theory 6 — Installer networking and time

Install needs:

  • Network for substitutes (unless offline media strategy)
  • Correct clock helps TLS
# live environment examples (names vary)
ip a
ping -c 2 1.1.1.1

Theory 7 — First boot mental model

After nixos-install and reboot:

bootloader → kernel+initrd → stage-1/2 → systemd → multi-user
system profile: /nix/var/nix/profiles/system
generation N is current

You can log in as root or the user you created. Prefer SSH key setup ASAP (Day 14 formalizes); avoid building habits around password-only remote root.


Theory 8 — Credentials hygiene on a lab

Practice Why
Unique lab password Compromise isolation
Note in password manager Labs still get attacked if exposed
Prefer host-only networking until ready Reduce drive-by SSH noise
Snapshot before experiments Fast recovery

Worked examples bank

Example A — Download image

Use official docs for current ISO:

Pick 26.05 graphical or minimal ISO matching your arch (x86_64, aarch64).

Verify checksums when provided.

Example B — Partition sketch (sfdisk/parted-level ideas)

/dev/vda1  EFI System  FAT32  /boot
/dev/vda2  Linux root  ext4   /

Device names: vda (virtio), sda, nvme0n1—use what the live system sees (lsblk).

Example C — Generate + install skeleton

# after mounts ready
sudo nixos-generate-config --root /mnt
sudo nano /mnt/etc/nixos/configuration.nix
sudo nixos-install
# set root password when prompted

Example D — Minimal configuration knobs to set during install

{
  imports = [ ./hardware-configuration.nix ];

  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  networking.hostName = "lab";
  time.timeZone = "UTC"; # set yours

  users.users.alice = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
    # initialPassword only for offline lab convenience — rotate later
    initialPassword = "changeme";
  };

  services.openssh.enable = true; # if you want SSH into VM
  system.stateVersion = "26.05"; # match release; do not casually change later
}

Exact options must match your installer generation—adapt from generated file rather than blind paste.


Lab 1 — Plan and snapshot strategy

Write in ~/lab/90daysofx/02-nixos/day11/NOTES.md (on the host machine):

  1. Hypervisor / hardware choice
  2. UEFI or BIOS
  3. Disk size
  4. Hostname
  5. Username
  6. Networking mode (NAT/bridge)
  7. Snapshot plan

Lab 2 — Install NixOS 26.05

  1. Boot installer ISO
  2. Partition + format + mount
  3. nixos-generate-config --root /mnt
  4. Edit config: hostname, user, boot loader, optional SSH
  5. nixos-install
  6. Reboot into installed system
  7. Take a VM snapshot labeled day11-fresh-install

If install fails, journal the error class (bootloader, mount, network, disk).


Lab 3 — Post-install inventory

On the new system:

nixos-version
uname -a
lsblk -f
cat /etc/os-release
ls /etc/nixos/

Copy off-box (screenshot/scp) or retype into host NOTES.md:

  • nixos-version output
  • Disk layout
  • Boot loader in use
  • IP address if any

Lab 4 — Preserve hardware config

# on NixOS lab
sudo cp -a /etc/nixos /root/nixos-etc-backup-$(date +%F)

On host lab notes, record that Day 12 will create a flake and import hardware-configuration.nix.

Optional: already enable flakes in system config for smoother Day 12:

nix.settings.experimental-features = [ "nix-command" "flakes" ];

Then sudo nixos-rebuild switch once if you edit on the running system.


Lab 5 — Break-glass familiarity

Without destroying the snapshot casually:

  1. Locate boot menu / how to pick previous generation (may only have gen 1)
  2. Confirm you can log in on console
  3. If SSH enabled, log in from host once

Common gotchas

Symptom Theory
No bootable device ESP not mounted at /mnt/boot or wrong firmware mode
Install can’t reach cache Network/DNS in live env
Forgot root password Live ISO recovery / reinstall—why snapshots matter
hardware-configuration wrong root UUID Generated before final mounts
Using unstable ISO by mistake Check 26.05 label
Daily driver install “for fun” Restore from backup; prefer VM next time

Checkpoint

  • Disposable NixOS 26.05 lab boots
  • User + privilege path known (root/wheel)
  • Disk + boot mode documented
  • /etc/nixos inspected
  • VM snapshot taken (if VM)
  • Notes stored on host machine

Commit

# on host workstation notes repo
cd ~/lab/90daysofx/02-nixos/day11
git init 2>/dev/null || true
git add .
git commit -m "day11: nixos 26.05 lab install notes"

Tomorrow

Day 12 — Flake host skeleton. Put the machine under nixosConfigurations and rebuild with nixos-rebuild switch --flake .#host—flakes from the first serious ownership moment.


Day 12 — Flake host skeleton

Stage II · ~4h
Goal: Manage the lab host with a flake (nixosConfigurations) from day one of ownership—nixos-rebuild switch --flake .#<host> is the source of truth.

Tip

Syllabus expert default: do not invest weeks in non-flake /etc/nixos only to rewrite. Migrate now while the config is small.

Why this day exists

A NixOS system without a flake pin drifts like a channel laptop. Flakes give you:

  • Locked nixpkgs (and later inputs)
  • Named hosts in one repo
  • A rebuild command you can document and automate

Theory 1 — nixosConfigurations output

outputs = { self, nixpkgs, ... }: {
  nixosConfigurations.lab = nixpkgs.lib.nixosSystem {
    system = "x86_64-linux";
    modules = [
      ./hosts/lab/configuration.nix
      ./hosts/lab/hardware-configuration.nix
    ];
  };
};
Piece Role
nixosSystem Module system entry for a host
system CPU-OS triple for packages
modules List of NixOS modules (files/functions)
attr name lab Host id in .#lab

Rebuild:

sudo nixos-rebuild switch --flake /path/to/repo#lab
# or from repo:
sudo nixos-rebuild switch --flake .#lab

Theory 2 — Where the flake lives

Location Pros Cons
/etc/nixos/flake.nix Simple path Mixed with generated hardware; git harder
/home/you/nixos-config git repo Proper history Need path on rebuild
Separate git + copy Flexible Sync discipline

Recommended for this volume: git repo in your home (or shared path), rebuild with explicit flake path. Optionally symlink strategy later.


Theory 3 — Module list composition

modules = [
  ./hardware-configuration.nix
  ./configuration.nix
  ./modules/ssh.nix      # later
];

Each module is typically:

{ config, lib, pkgs, ... }: {
  # options / config
}

Or a plain attrset in simple files. Day 18–19 deepen modules; today: imports work.


Theory 4 — Minimal flake host contents

flake.nix

{
  description = "lab flake host";

  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";

  outputs = { self, nixpkgs }: {
    nixosConfigurations.lab = nixpkgs.lib.nixosSystem {
      system = "x86_64-linux"; # match your VM
      modules = [
        ./hosts/lab/configuration.nix
        ./hosts/lab/hardware-configuration.nix
      ];
    };
  };
}

hosts/lab/configuration.nix

{ config, lib, pkgs, ... }: {
  networking.hostName = "lab";

  nix.settings.experimental-features = [ "nix-command" "flakes" ];

  boot.loader.systemd-boot.enable = true;
  boot.loader.efi.canTouchEfiVariables = true;

  time.timeZone = "UTC";

  users.users.alice = {
    isNormalUser = true;
    extraGroups = [ "wheel" ];
  };

  environment.systemPackages = with pkgs; [ vim git ];

  services.openssh.enable = true;

  system.stateVersion = "26.05";
}

hardware-configuration.nix

Copy from the machine’s generated file—do not invent UUIDs.


Theory 5 — First rebuild flow

edit flake modules
    ↓
nixos-rebuild switch --flake .#lab
    ↓
eval modules → system derivation → realize → activate
    ↓
new system generation
Outcome Meaning
Success New generation is current
Eval error Fix Nix before build
Build error Package/build failure
Activation error System may be partially switched—read output carefully

Theory 6 — specialArgs / extraSpecialArgs (preview)

nixpkgs.lib.nixosSystem {
  specialArgs = { inherit inputs; };
  modules = [];
}

Used later to pass flake inputs into modules. Not required for the minimal host.


Theory 7 — Hostname vs flake attribute

Name Where
networking.hostName OS hostname
nixosConfigurations.lab Flake attribute

They may match but are not required to. Document both in README.


Worked examples bank

Example A — Build without switching (safe)

nixos-rebuild build --flake .#lab
# or
nix build .#nixosConfigurations.lab.config.system.build.toplevel

Example B — Switch

sudo nixos-rebuild switch --flake .#lab

Example C — Show config value

nix eval .#nixosConfigurations.lab.config.networking.hostName

Lab 1 — Repository layout on the lab host

mkdir -p ~/nixos-config/hosts/lab
cd ~/nixos-config
git init
sudo cp /etc/nixos/hardware-configuration.nix ~/nixos-config/hosts/lab/
sudo chown "$USER":"$USER" ~/nixos-config/hosts/lab/hardware-configuration.nix

Write flake.nix + hosts/lab/configuration.nix (adapt user, system, boot).

git add .
nix flake lock
git add flake.lock
git commit -m "day12: initial nixosConfigurations.lab"

Lab 2 — First flake rebuild

cd ~/nixos-config
sudo nixos-rebuild switch --flake .#lab

Verify:

nixos-version
hostname
nix eval .#nixosConfigurations.lab.config.networking.hostName

Lab 3 — Prove flake is authority

  1. Change a harmless package in environment.systemPackages (add htop).
  2. Rebuild switch.
  3. Confirm htop available.
  4. Commit.

Avoid editing /etc/nixos/configuration.nix as the long-term path—if installer files remain, leave a comment in README: flake repo is SoT.


Lab 4 — README for future you

README.md:

# nixos-config

## Rebuild
sudo nixos-rebuild switch --flake .#lab

## Host
- flake attr: lab
- hostname: lab
- nixpkgs: 26.05 (see flake.lock)

Lab 5 — Optional: match /etc/nixos to flake

Some prefer:

# conceptual — only if you understand implications
# keep hardware in repo; rebuild always with --flake

Do not create circular confusion. Simplest: always cd ~/nixos-config && sudo nixos-rebuild switch --flake .#lab.


Common gotchas

Symptom Theory
Absolute import paths broken Prefer relative module paths in repo
Wrong system Eval/build target mismatch
hardware config not updated after disk change Regenerate carefully
experimental-features missing Rebuild cannot use flakes cleanly
Dirty git / missing files Flake file inclusion
stateVersion folklore Set to install release; read release notes before changing

Checkpoint

  • nixosConfigurations.<host> exists
  • hardware-configuration.nix imported from flake repo
  • switch --flake .#host succeeded
  • Lock committed
  • README documents rebuild
  • Small declarative change proven

Commit

cd ~/nixos-config
git add .
git commit -m "day12: flake host skeleton rebuild switch"

Tomorrow

Day 13 — Rebuild modes & generations. switch / test / boot / build, generation listing, and a deliberate break → rollback.


Day 13 — Rebuild modes & generations

Stage II · ~4h
Goal: Master nixos-rebuild modes, system generations, and rollback—including a deliberate break and recovery on the disposable lab.

Why this day exists

NixOS’s famous safety is not automatic—it is generational profiles + boot entries + knowing which rebuild verb you used. Without that theory, “rollback” is folklore.


Theory 1 — System profile and generations

NixOS points the running system at a profile:

/nix/var/nix/profiles/system → system-N-link → store path

Each successful deployment can add a generation N. Boot loaders list generations so you can boot an older one when the new world is wrong.

Concept Meaning
Generation A complete system closure you can activate/boot
Current What the profile points at now
Rollback Point profile (and/or boot default) at older generation

Theory 2 — Rebuild modes

sudo nixos-rebuild <mode> --flake .#lab
Mode Effect
switch Build + activate now + set boot default to new gen
boot Build + set boot default; do not activate until reboot
test Build + activate now; do not change boot default
build Build only; no activation
dry-activate Show activation plan (when supported/useful)
build-vm Build a VM of the config (extra workflows)

Choosing modes

Situation Prefer
Normal daily change switch
Risky change; want easy reboot escape test first or boot
Remote SSH-only host Extra care—bad switch can lock you out
CI / check build

Remote note

If you break networking/SSH on a remote machine with switch, you may need console. Lab VMs have consoles—use them.


Theory 3 — Activation vs boot default

test:   running config changes; reboot may return to previous default
switch: running + default updated
boot:   default updated; running stays old until reboot

Worked story

  1. test a firewall change → SSH still works →
  2. switch to commit it to boot default

Or:

  1. boot a kernel change → reboot when ready →
  2. If unbootable, select previous generation in boot menu

Theory 4 — Listing and switching generations

sudo nix-env -p /nix/var/nix/profiles/system --list-generations
# or
ls -l /nix/var/nix/profiles/system*

sudo nixos-rebuild list-generations  # when available in your version

Rollback helpers:

sudo nixos-rebuild switch --rollback
# or boot older generation from boot menu

Also:

sudo /nix/var/nix/profiles/system-<N>-link/bin/switch-to-configuration switch

Prefer documented nixos-rebuild interfaces when possible.


Theory 5 — What is not rolled back

Rolled back with system gen Not rolled back
Packages, units, users declared in config Your data in /home, /var
Most /etc files managed by NixOS Databases, untracked files
Kernel (for that gen) Out-of-band disk edits

Punchline: rollback restores system closure, not time-travel for application data.


Theory 6 — GC and generations

Old generations root large closures. Cleaning:

sudo nix-collect-garbage -d   # deletes old profile gens — careful
sudo nixos-rebuild …          # after understanding
Important

Do not delete generations until you have a known good current system and understand you are dropping rollback points.


Theory 7 — Flake + rebuild flags worth knowing

sudo nixos-rebuild switch --flake .#lab
sudo nixos-rebuild switch --flake .#lab --show-trace
sudo nixos-rebuild switch --flake .#lab -L
sudo nixos-rebuild switch --flake path:/home/you/nixos-config#lab
Flag Use
--show-trace Eval errors
-L Build logs
--impure Avoid unless you know why
--update-input / flake update Separate concern (Day 24)

Worked examples bank

Example A — Safe build

cd ~/nixos-config
nixos-rebuild build --flake .#lab

Example B — test then switch

sudo nixos-rebuild test --flake .#lab
# exercise the change
sudo nixos-rebuild switch --flake .#lab

Example C — rollback

sudo nixos-rebuild switch --rollback

Lab 1 — Inventory generations before chaos

sudo nix-env -p /nix/var/nix/profiles/system --list-generations
readlink -f /nix/var/nix/profiles/system

Record current generation number in notes.


Lab 2 — Mode drills (harmless package)

  1. Add a tiny package with boot mode only.
  2. Confirm it is not on PATH until reboot (or document actual behavior you observe).
  3. Reboot (VM snapshot first).
  4. Confirm package present.

Then:

  1. Use test for another package.
  2. Confirm available immediately.
  3. Reboot without switch and observe whether it persists as default—write what happened.

Lab 3 — Deliberate break → rollback

VM snapshot first.

Introduce a recoverable break, for example:

# BAD on purpose — comment clearly
# environment.etc."BREAK".text = "…";
# or disable ssh while on console-only VM:
# services.openssh.enable = false;  # only if you have console

Safer break: set environment.systemPackages to include a typo attribute that fails eval—that is not a rollback drill. For rollback you need a successful bad generation:

environment.etc."day13-break".text = "broken intentionally";
# and something mildly annoying but not fatal, OR
# networking.firewall.allowedTCPPorts = [ ]; with ssh still via console

Better pedagogical break:

  1. switch a config that installs a known package cowsay.
  2. Then switch a config that removes it and sets environment.variables.DAY13 = "broken";.
  3. rollback and show cowsay/env difference.

Or classic:

sudo nixos-rebuild switch --flake .#lab
# introduce change that succeeds but you dislike
sudo nixos-rebuild switch --rollback

Document generation numbers before/after.


Lab 4 — Boot menu literacy

On next reboot:

  1. Enter boot loader menu (often hold space / show menu for systemd-boot).
  2. Identify generation entries.
  3. Boot current known-good.

Write how your hypervisor + loader expose this.


Lab 5 — Incident card

Create ~/nixos-config/docs/rollback.md:

# Rollback card
1. Console access method: …
2. List generations: …
3. Rollback command: …
4. Boot menu steps: …
5. Last known good generation: …

Common gotchas

Symptom Theory
Rollback “did nothing” for app data Data ≠ system gen
Locked out via SSH Use console; prefer test for net changes
No older generations GC’d or first install
test change vanished after reboot Expected if never switched
Flake path wrong under sudo Use absolute path or root’s cwd discipline

Checkpoint

  • Explain switch/test/boot/build
  • List system generations
  • Performed a rollback successfully
  • Boot menu generation selection documented
  • Incident card written
  • VM snapshot restored option still available

Commit

cd ~/nixos-config
git add .
git commit -m "day13: rebuild modes generations rollback card"

Tomorrow

Day 14 — Users & access. Declarative users, wheel/sudo, and SSH keys only for remote admin habits.