references
- https://github.com/nilla-nix/nilla
- https://github.com/arnarg/nilla-utils
- https://github.com/arnarg/config
- https://github.com/jakehamilton/config
Enter the dev shell (nilla shell, or direnv allow once for auto-load) to get:
install <host> [user@target]— provision a host (local or remote)deploy <host> [user@target]— rebuild & switch an installed hostbuild-installer— build the self-contained installer ISO- the
nillaCLI (nilla os ...,nilla home ...) for manual use
Hosts live in hosts/; each is systems.nixos.<host> (+ systems.home.<user>@<host>).
One command, two transports. Both erase and reformat the target's disks.
install <host> [user@target] [--project <path>] [--ref <git-ref>] [--facter] [--yes] [-- extra args]
- No target → local on-device install: run it on the booted installer ISO;
diskoformats this machine, thennixos-install. - With a target → remote install via nixos-anywhere: the closure is built on your machine and copied to the target over SSH.
Nilla is not a flake, so under the hood we just build the two store paths the install needs:
nix-build nilla.nix \
-A systems.nixos.<host>.result.config.system.build.diskoScript \
-A systems.nixos.<host>.result.config.system.build.toplevelinstall does this for you, picks the transport, and confirms before wiping (--yes to skip).
Where the config comes from (project resolution, first match wins): --project <path> →
--ref <git-ref> (fetched from github:erdfern/config) → $NCONF_PROJECT → /etc/nconf
(baked into the installer ISO) → the current git repo → ..
-
Build the installer ISO and flash it:
build-installer # prints the built ISO path sudo dd if=<that>.iso of=/dev/sdX bs=4M status=progress conv=fsync
The ISO bundles the whole config at
/etc/nconf, theinstall/deploycommands, sshd withme.ssh.pubKeysauthorized, security-key tooling (pcscd, yubikey/nitrokey), and thekor.cachix.orgsubstituter. -
Boot the target from the USB.
-
On the target itself — no repo clone needed:
install <host>
diskoformats the disk, the writable Nix store is moved onto the fresh disk (so the system closure substitutes/builds on disk instead of the RAM-backed live store), thennixos-install.To install a newer config than the one baked in:
install <host> --ref main.
Get the target into a NixOS installer / rescue / kexec environment reachable over SSH, then run from this repo on your own machine:
install <host> root@<ip>Official kexec installer, if the host only offers a non-NixOS rescue system:
curl -L https://github.com/nix-community/nixos-images/releases/latest/download/nixos-kexec-installer-noninteractive-x86_64-linux.tar.gz | tar -xzf- -C /root
/root/kexec/runCapture a hardware profile from the booted target, commit it, then install. For facter hosts:
install <host> root@<ip> --facter # writes hosts/<host>/facter.json, then stops
git add hosts/<host>/facter.json && git commit -m "<host>: facter"
install <host> root@<ip>For hardware-configuration.nix hosts:
ssh root@<ip> nixos-generate-config --no-filesystems --show-hardware-config > hosts/<host>/hardware-configuration.nix
install <host> root@<ip>Hosts bake users.users.*.initialHashedPassword/hashedPasswordFile, so first login works
without a mkpasswd step.
Why local installs no longer run out of space: the live ISO's
/nix/storeis a RAM-backed tmpfs overlay (~50% of RAM), far too small for a desktop closure.installnever realizes the closure there — afterdiskoit runsnixos-install --file … --attr …, which builds with--store /mnt, so the whole closure is built/substituted straight onto the target disk (pulling fromkor.cachix.organd the installer's own store).
Secrets (sops/age) are not provisioned during install. A new host's age identity is derived from its SSH host key (generated on first boot), so after first boot add the host's key to
.sops.yaml, re-encryptsecrets/, thendeploy.
deploy <host> # rebuild + switch NixOS and home-manager locally
deploy <host> root@<ip> # ... over SSH (nilla os/home switch --target)Whole-fleet / tag-based deploys go through Colmena (see hive.nix):
colmena apply --on @<tag> # e.g. @laptop, @workstationCI (.github/workflows/build.yml) builds every host + the installer
ISO and pushes to kor.cachix.org, so installs and deploys substitute instead of building.