Projects / ongoing
The Homelab
A UniFi network running a NixOS fleet from one flake: workstation, services host, media, a libvirt security lab, a staging/CI box that moonlights as a Minecraft server, and an off-site Linode node fronting it, all deployed with a single colmena apply. One Ubuntu NAS stays off NixOS on purpose.
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
The trusted network is where the fleet lives:
| Host | Role |
|---|---|
desktop | NixOS workstation and gaming rig, also the Colmena deploy controller |
mgmt | Internal DNS, a private step-ca CA, the homelab SIEM, and Forgejo |
media | NixOS media host: the media stack run declaratively; storage over NFS |
playground | NixOS + libvirt security lab: Kali, Parrot, and a malware-analysis bench |
hacktop | Staging, the self-hosted CI runner, and the fleet’s Minecraft server |
nas | Ubuntu 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 doesn’t stop at the house. cloud1 is 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 has already lived one full lifecycle:
stood up, torn down when it had no job, and rebuilt from the same code the
day it got one. That round trip is the part worth noticing — when the
infrastructure is code, decommissioning costs nothing and coming back is a
terraform apply and a colmena apply. 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.
A public game, no open home ports
The job that brought cloud1 back: play.briggsbastian.com, an All
the Mons modpack server (Minecraft 1.21.1, NeoForge, 373 mods) I run for
my friends. It lives on hacktop — the box with the RAM headroom — as a
fully declarative nix-minecraft service, the modpack’s server files
pinned into the Nix store like any other dependency. Publishing it never touches the home
firewall: hacktop dials out to cloud1 over WireGuard and holds the
tunnel open with keepalives, and nftables on the Linode DNATs public
:25565 traffic down the tunnel. No inbound rule at home, the home IP
never published, and every drive-by scan on the internet lands on the
cloud box instead of the house — all for ~10–25 ms of added latency. Why
I like this shape, and where it stops scaling, is a
garden note.
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/mkEnableOptionrather 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.