Bootstrapping a host¶
Installing NixOS on a new (or dead) machine from this repo, using
nixos-anywhere over an installer USB. This is the generic per-host
procedure. Do one host at a time and verify it before starting the next,
rather than installing two machines in parallel.
The procedure itself is simple: the first install always goes through
nixos-anywhere. Every rebuild after that is an ordinary just test /
just switch. Build and activation run on the target host, so the
workstation driving the deploy can be macOS.
This wipes the target disk
disko repartitions and formats the disk it is pointed at. Triple-check
the disk ID before installing, and never point it at the USB installer's
own disk.
Prepare¶
- Boot the target machine from a NixOS installer USB.
-
On its console, set a temporary root password and start SSH, then note the LAN IP. That password lives only in the installer environment; the installed system disables root and password login.
sudo passwd root sudo systemctl start sshd ip addr -
From the workstation, confirm you reached the right machine:
ssh root@<INSTALLER_IP> 'hostname; cat /etc/os-release' -
Find the internal disk's stable
by-idpath. Match it by size, model, and serial, never by a/dev/sda-style name (those move with boot order and USB devices). If you cannot tell which disk is the internal one, stop and recheck.ssh root@<INSTALLER_IP> 'lsblk -o NAME,SIZE,MODEL,SERIAL,TYPE; ls -l /dev/disk/by-id/'Use a path like
ata-Samsung_SSD_860_EVO_...ornvme-..., not/dev/sdaor/dev/nvme0n1. -
Set that path as
deviceinhosts/<host>/disko.nix. It produces a single-disk GPT layout: a 512M EFI partition at/boot(vfat) and the rest as/(ext4). -
Generate the hardware config from the target, then review it instead of trusting it as-is:
ssh root@<INSTALLER_IP> 'nixos-generate-config --show-hardware-config' \ > hosts/<host>/hardware-configuration.nixKeep the
not-detected.niximport, the kernel module lines,nixpkgs.hostPlatform, and the CPU microcode entries. DropfileSystems."/",fileSystems."/boot", andswapDevices:diskoowns/and/boot, andmodules/swap.nixowns swap via zram. Those entries are usually live-environment values that would clash withdisko. -
Enable (uncomment) the
importsinhosts/<host>/default.nix:imports = [ ./hardware-configuration.nix ./disko.nix ]; -
Review the changes before installing. A parse check catches syntax errors early:
git diff -- hosts/<host> nix-instantiate --parse hosts/<host>/default.nix >/dev/null nix-instantiate --parse hosts/<host>/disko.nix >/dev/null nix-instantiate --parse hosts/<host>/hardware-configuration.nix >/dev/null
Install¶
nix run github:nix-community/nixos-anywhere -- \
--flake .#<host> \
--build-on-remote \
root@<INSTALLER_IP>
This partitions and formats with disko, copies and installs the system
closure, installs the bootloader, and applies the initial config. This step
erases the disk, so confirm the disk ID one last time.
If the workstation has flakes disabled
Add the experimental features to the same command:
nix --extra-experimental-features "nix-command flakes" \
run github:nix-community/nixos-anywhere -- \
--flake .#<host> --build-on-remote root@<INSTALLER_IP>
After first boot¶
Remove the USB and boot from the internal disk, then:
-
ssh poby@<host>works;ssh root@<host>and password login fail - Join the tailnet:
sudo tailscale up, verify withtailscale status - Switch to the normal deploy model:
just test <host>, thenjust switch <host>(see Deploy & rollback) - If the host is a sops recipient: re-key secrets if its host key changed (see Secrets)
- Commit
hosts/<host>/changes andflake.lock
Validation¶
hostname && whoami && sudo -n true
systemctl is-active sshd tailscaled
zramctl && df -h && bootctl status
Expected: poby with passwordless sudo, both services active, zram swap
present, vfat /boot, ext4 /.
After the base install¶
With the host booting and reachable, build it out one change at a time:
- Configure its secrets with sops-nix (see Secrets).
- Add and expose services (see Adding a new service).
- Every change follows the same loop: edit the repo,
just test <host>,just switch <host>, then commit.