Skip to content

Repository files navigation

desktop-nixos

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.

Hosts

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.

Stack

  • 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)

Layout

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

Day-to-day

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.yaml

Bootstrapping a new host

Remote (recommended) — nixos-anywhere

Boot 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-kepler

nixos-anywhere will: partition via disko, install NixOS, stage the age key for first-boot sops decryption, and optionally generate _hw-generated.nix.

Local — from NixOS ISO on the target itself

# 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 setup

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 sops

The .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

Adding a host

  1. Create modules/hosts/<name>/ with at minimum:
    • default.nix — declares configurations.nixos.<name>.module
    • hardware.nix — imports _hw-generated.nix + GPU/microcode
    • networking.nix — static IP, hostName, /etc/hosts
    • _hw-generated.nix — auto-generated (nixos-anywhere does this; or run nixos-generate-config)
  2. Register addressing in modules/meta.nix fleet.hosts.<name> (ip/mac/role), then just fleet-json to regenerate fleet.json (the justfile ip_* recipes + homelab-iac DHCP reservation derive from it).
  3. Add the host's age public key to .sops.yaml and re-encrypt: just sops
  4. Add any host-specific secrets to secrets/sops/secrets.yaml
  5. Run just dry <name> to validate before deploying

Distributed builds

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.nixusers.users.erik.openssh.authorizedKeys.keys.

Binary cache

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-keygen

Auto-upgrade health check

Hosts 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.

Dev shell

direnv allow   # or: nix develop

Provides: alejandra, statix, just.

About

Dendritic NixOS flake — fleet system config + SSOT (hosts/roles/ingress/services)

Topics

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages