Projects / ongoing

The Homelab

A UniFi network running a NixOS fleet from one flake: workstation, services host, media, a libvirt security lab, and a staging/CI box, all deployed with a single colmena apply. One Ubuntu NAS stays off NixOS on purpose.

Active
  • NixOS
  • Colmena
  • sops-nix
  • Terraform
  • libvirt
  • UniFi

UniFi runs the network; NixOS runs everything on it. The Proxmox migration is finished. Every server was wiped to NixOS and now runs on bare metal, with no hypervisor underneath. What used to be a pile of standalone machines is now one flake, one colmena apply: the whole estate described in a single repo, deployed from one workstation, with secrets in sops-nix and fresh installs handled by disko + nixos-anywhere. There is no wiki for the lab. This page is the closest thing it has to current documentation; for the declarative hosts, the config is the documentation.

Base topology

internet unifi gateway unifi switching trusted lan iot guest desktop mgmt media hacktop nas · ubuntu tv tv homepod guest devices
Hover a node to inspect it; click playground to expand its security-lab VMs. Green ticks are online status.

The trusted network is where the fleet lives:

HostRole
desktopNixOS workstation and gaming rig, also the Colmena deploy controller
mgmtInternal DNS, a private step-ca CA, the homelab SIEM, and Forgejo
mediaNixOS media host: the media stack run declaratively; storage over NFS
playgroundNixOS + libvirt security lab: Kali, Parrot, and a malware-analysis bench
hacktopStaging and the self-hosted CI runner; nothing reaches production unbuilt
nasUbuntu 24.04 LTS: bulk storage over NFS, kept off NixOS on purpose

Click playground in the map to expand its security-lab VMs. Segmentation is UniFi policy at the gateway: the IoT network holds the smart TVs and a HomePod that can reach the internet but not the lan, and visitors land on a guest network with internet access and nothing else.

A handful of shared NixOS modules do the load-bearing work: a common baseline (key-only SSH, nftables, a hardened deploy user, sops wiring, and a journal shipper), a module that trusts an internal step-ca certificate authority and points security.acme at its private ACME endpoint, and a SIEM-agent module that enrolls any host by being switched on. Wiring NixOS to a private CA is uncommon; the payoff is that every internal service gets a real, auto-renewing certificate without a public name ever leaving the house.

Secrets are sops-nix, and each host decrypts its own with no key distribution at all: its age identity is derived from its existing SSH host key. Colmena pushes closures through a dedicated deploy user, a Nix trusted-user with scoped NOPASSWD sudo for only the activation commands, so a stolen deploy key can re-activate a build but can’t open a root shell.

Off-site, same flake

The estate hasn’t always stopped at the house. cloud1 was a Linode node, Terraform-provisioned and installed greenfield with nixos-anywhere, then folded into the same flake and Colmena hive as the LAN boxes — one repo managing the house and the cloud. It’s currently torn down, and that’s the part worth noticing: because the leg is code, decommissioning it cost nothing and bringing it back is a terraform apply and a colmena apply away. The SIEM went declarative too: the fleet moved off Wazuh onto a leaner Loki/Alloy stack (siem-lite) — Alloy ships every host’s journal to Loki on mgmt, and Alertmanager pushes to a self-hosted ntfy topic. That cutover was gated, because mgmt carries the LAN’s DNS and PKI and a bad deploy there is felt everywhere.

The security lab

playground is a libvirt host carrying the offensive and reverse-engineering VMs, reachable through a clientless, in-browser Guacamole session: Kali and ParrotOS are live for attacking the lab, with REMnux and a Windows FLARE bench (TPM 2.0 emulated via swtpm under OVMF) next in for pulling samples apart. It’s the range for purple-team work: attack the fleet from the Kali box, confirm the SIEM caught it, and where it didn’t, write the rule that does. On NixOS the cleanup is one command: redeploy the compromised host to a known-good state. The loop is attack, detect, rebuild.

What it proves

  • Declarative where it counts. Every NixOS host is rebuildable from its configuration rather than from memory, and rolled back just as cheaply, since generations are free. The one undocumented machine in the lab is the one that isn’t NixOS yet.
  • A real deploy story. One repo, shared modules written with the module system (mkOption/mkIf/mkEnableOption rather than copy-paste), sops-managed secrets, and a staging host (hacktop) that builds and verifies a config, and now runs the CI that gates it, before anything is promoted to production. That staging-then-promote loop is the change-management process.
  • A mixed estate on purpose. The NAS stays Ubuntu, the one box I keep off Nix on purpose, because “I ported service X from Ansible to a NixOS module and here’s what each got right” is a better story than “I use Nix for everything.”

The whole estate is where I practice the SecDevOps habits I’m building in the open. Running notes are in the thought garden, and the evidence is on the ops page.