NixOS configuration for a homelab fleet (workstations, servers, a 3D-printer Pi, and
offsite Oracle nodes). Built on flake-parts +
import-tree following the
dendritic pattern — every .nix file is a
top-level module, options replace specialArgs, no aggregator boilerplate.
| Host | Role | CPU | RAM | GPU | Storage | IP |
|---|---|---|---|---|---|---|
| pathfinder | Desktop | i7-8750H | 32 GB | GTX 1060 Max-Q + UHD 630 (PRIME sync) | SATA · LUKS+btrfs | 192.168.10.215 |
| endeavour | Laptop · primary workstation | Core Ultra 7 155H | 32 GB | Intel Arc | NVMe · LUKS+btrfs · FIDO2 | Tailscale (roaming) |
| orion | HTPC · build server · dev sandbox | Ryzen 9 5950X | 128 GB | Radeon RX 9070 XT | NVMe btrfs · 2×SATA SSD btrfs | 192.168.10.220 |
| discovery | Home server | i5-4670 | 32 GB | Quadro P2000 | 2×SSD btrfs RAID1 · 3.6TB HDD | 192.168.10.210 |
| kepler | NAS / lab cluster | Ryzen 5 3600 | 96 GB | RTX 3070 LHR | M.2 btrfs · ZFS RAIDZ1 | 192.168.10.230 |
| archinaut | 3D-printer host (Klipper) | RPi 3B+ | 1 GB | — | microSD | 192.168.10.225 (WiFi) |
| homeassistant | HAOS appliance (VM) | — | — | — | discovery-hosted | 192.168.10.115 |
| vanguard | Offsite resilience node | Oracle VM (x86), 2 vCPU | 1 GB | — | 50 GB boot | 163.176.206.86 (ephemeral) / Tailscale |
| voyager | Offsite backup receiver | Oracle VM (x86), 2 vCPU | 1 GB | — | 50 GB boot | 147.15.7.254 (ephemeral) / Tailscale |
| telstar | Public-project host (staged, not provisioned) | Ampere A1, 2 OCPU | 12 GB | — | 50 GB boot | — |
SSH runs on port 2222 on NixOS hosts. Vanguard is live; Telstar remains
blocked on Oracle A1 capacity. Orion also hosts gemini, a NixOS-container
dev sandbox (ssh gemini, VS Code Remote-SSH) that shares its CPU/RAM.
Host addressing is a single source of truth: modules/meta.nix fleet.hosts
(ip/mac/role), published as the flake.fleet output and pinned to fleet.json
(just fleet-json; drift-guarded by just fleet-check). The IPs above, the
justfile ip_* recipes, and the homelab-iac DHCP reservations all derive from
it — change an IP once in meta.nix, regenerate, and consumers follow.
- Window manager: Hyprland + Quickshell bar + SDDM
- Shell: Zsh (vi-mode, zsh-abbr) + Starship + Atuin
- Secrets: sops-nix (age encryption)
- Disk layout: disko (LUKS+btrfs on desktops/laptop, RAID1 on discovery, ZFS on kepler)
- Containers: Docker / Podman + Compose (discovery, orion); systemd-nspawn dev sandbox (orion
gemini) - Monitoring: Grafana Alloy
- VPN: Tailscale
- Binary cache: nix-serve on orion (LAN, port 5000)
- Distributed builds: laptop → orion (ssh-ng, 16 jobs)
flake.nix # entry point: flake-parts + import-tree ./modules
modules/
configurations.nix # produces nixosConfigurations from configurations.nixos.*
meta.nix # readOnly options: username, email, configPath
systems.nix # supported systems
hosts/
pathfinder/ # per-host: default.nix, hardware.nix, networking.nix, …
orion/
discovery/
kepler/
laptop/
profiles/
base.nix # all hosts: security, networking, services, packages
desktop.nix # GUI hosts: Hyprland, fonts, audio, dev tools
server.nix # headless hosts: orchestration
security/ # apparmor, audit, fail2ban, pam, sudo, …
networking/ # firewall, openssh, resolved, tailscale
desktop/ # hyprland, sddm, quickshell, rofi, …
services/ # sops, first-boot, distributed-builds, nix-cache, …
shell/ terminal/ dev/ …
secrets/sops/secrets.yaml # age-encrypted: passwords, SSH keys, Tailscale authkeys
config/ # non-nix assets: QML, keyboard layouts, themes
just build # build current host without activating (offloads remotely)
just switch # build and activate current host
just upgrade # flake update + switch
just switch-orion # remote deploy to orion
just switch-all # parallel deploy to discovery + orion + pathfinder
just verify orion 192.168.10.220 # post-deploy health check
just dry # dry-build current host
just check # lint + fmt-check + dry-build all hosts
just lint # statix
just fmt # alejandra
just sops # edit secrets/sops/secrets.yamlBoot the target from a NixOS ISO, then from any machine in the fleet:
# LUKS hosts (pathfinder, laptop)
just nixos-anywhere <host> <ip>
# Non-LUKS hosts (orion, discovery, kepler — dedicated scripts)
just deploy-orion
just deploy-discovery
just deploy-keplernixos-anywhere will: partition via disko, install NixOS, stage the age key for first-boot
sops decryption, and optionally generate _hw-generated.nix.
# Clone the repo onto the ISO environment, then:
just bootstrap <host>Prompts for the LUKS password, partitions with disko, installs, and stages the age key.
Secrets are encrypted with sops-nix using age keys derived from SSH ed25519 keys.
# Derive your age private key from your SSH key
just age-private # writes ~/.config/sops/age/keys.txt
# Print your age public key (add to .sops.yaml for a new machine)
just age-public
# Edit the secrets file
just sopsThe .sops.yaml lists which age keys can decrypt secrets/sops/secrets.yaml. Add a new
host's key before provisioning it so sops-nix can decrypt secrets on first boot.
To copy the age key to an already-running host:
just rsync-sops <ip> 2222- Create
modules/hosts/<name>/with at minimum:default.nix— declaresconfigurations.nixos.<name>.modulehardware.nix— imports_hw-generated.nix+ GPU/microcodenetworking.nix— static IP, hostName, /etc/hosts_hw-generated.nix— auto-generated (nixos-anywhere does this; or runnixos-generate-config)
- Register addressing in
modules/meta.nixfleet.hosts.<name>(ip/mac/role), thenjust fleet-jsonto regeneratefleet.json(thejustfileip_*recipes + homelab-iac DHCP reservation derive from it). - Add the host's age public key to
.sops.yamland re-encrypt:just sops - Add any host-specific secrets to
secrets/sops/secrets.yaml - Run
just dry <name>to validate before deploying
Laptop offloads heavy builds to orion automatically. Other hosts can opt in:
# in the host's default.nix module body
nix.distributedBuildsOrion.enable = true;Requires /root/.ssh/nix-builder on the client and the corresponding public key in
modules/hosts/orion/default.nix → users.users.erik.openssh.authorizedKeys.keys.
orion runs nix-serve on http://192.168.10.220:5000. All hosts have it configured as
a high-priority substituter. The cache is warmed nightly at 03:00 by building all host
closures. Signing key is managed by sops-nix.
# Generate a new cache signing keypair (run on orion)
just cache-keygenHosts with system.autoUpgrade.enable = true get an automatic rollback guard: after each
unattended upgrade, upgrade-health-check verifies that sshd is still active. If not,
it rolls back the system profile and re-activates the previous generation, preventing a
bad upgrade from silently locking out remote access.
No configuration needed — the check activates automatically alongside autoUpgrade.
direnv allow # or: nix developProvides: alejandra, statix, just.