Headscale is the tailnet control server every host's services/tailscale.nix already points at (--login-server=https://vpn.mgaction.town). MagicDNS base_domain "hosts.mgaction.town" matches the "jupiter.hosts.mgaction.town" names already used in this repo's Caddy vhosts. Headplane is its web UI, running as headscale's own user (native process integration, no container). No OIDC wired up - log in with a headscale API key generated on the box. Both proxied through Caddy; headscale's vhost needs flush_interval -1 since its node-update endpoint is a long-poll. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
homelab
Flake-based NixOS config. Host: jupiter (ZimaBlade, NAS + services).
Structure
flake.nix # inputs + nixosConfigurations (jupiter, neptun, kexec, ...)
common.nix # shared base: user, ssh, nix, firewall, timezone
services/ # one reusable module per service
samba.nix avahi.nix audiobookshelf.nix containers.nix caddy.nix tailscale.nix
hosts/
jupiter/ # ZimaBlade NAS
configuration.nix # host bits + imports common + the services it runs
disk-config.nix # disko: eMMC partitions
hardware-configuration.nix
secrets.nix # sops-nix wiring
vm.nix # VirtualBox test image (jupiter-vbox)
neptun/ # netcup public reverse proxy + tailnet node
configuration.nix disk-config.nix hardware-configuration.nix secrets.nix
secrets/ # age-encrypted sops files (jupiter.yaml, neptun.yaml)
scripts/ # deploy, edit_secrets
Hosts compose by importing common.nix + whichever services/* modules they
run. Each service module opens its own firewall ports.
Test in VirtualBox (no hardware needed)
nix build .#nixosConfigurations.jupiter-vbox.config.system.build.virtualBoxOVA
VBoxManage import result/*.ova --vsys 0 --vmname jupiter-vbox
VBoxManage startvm jupiter-vbox --type headless
Login darman / test. Forward ports with VBoxManage modifyvm ... --natpf1.
First install on the ZimaBlade — nixos-anywhere + disko
Wipes the OS disk and installs the flake over SSH. No USB needed if the box already runs Linux (ZimaOS) reachable by root SSH — nixos-anywhere kexecs into an installer, partitions via disko, installs.
⚠️ The OS disk in
disk-config.nixis WIPED. Setdeviceto the OS disk ONLY (by-id). Back up / physically identify the NAS data disk first — it must NOT appear in disko.lsblk -o NAME,SERIAL,SIZE,MODELto identify.
- Set the real OS disk id in
hosts/jupiter/disk-config.nix(ls -l /dev/disk/by-id), and the data-disk mount inconfiguration.nix. - Add your login SSH pubkey to
users.users.darman.openssh.authorizedKeys.keys. - Set the real samba password:
export SOPS_AGE_KEY_FILE=~/.config/sops/age/keys.txt nix shell nixpkgs#sops -c sops secrets/jupiter.yaml # edit, commit - Stage the pre-generated host key so sops can decrypt on boot #1
(private key lives off-repo in
~/.config/homelab/jupiter/):install -Dm600 ~/.config/homelab/jupiter/ssh_host_ed25519_key \ /tmp/extra/etc/ssh/ssh_host_ed25519_key install -Dm644 ~/.config/homelab/jupiter/ssh_host_ed25519_key.pub \ /tmp/extra/etc/ssh/ssh_host_ed25519_key.pub - Run from your laptop:
nix run github:nix-community/nixos-anywhere -- \ --flake .#jupiter \ --extra-files /tmp/extra \ --generate-hardware-config nixos-generate-config ./hosts/jupiter/hardware-configuration.nix \ --target-host root@<zimablade-ip>--extra-filesplants the host key before first boot (its age identity is already a recipient in.sops.yaml, so/run/secrets/samba_passworddecrypts on boot #1).--generate-hardware-configpulls the target's real kernel modules into the placeholder. Commit the result. Reboot into NixOS.
Manual alternative (USB ISO): boot installer, disko the disk, then
nixos-install --flake .#jupiter.
Deploy (the ./deploy wrapper)
All arguments mandatory — no default host, no default config.
./deploy kexec <host> # headless kexec into a RAM installer (RO-root box)
./deploy install <config> <host> # first install; wipes OS disk, ships host key
./deploy switch <config> <host> # rebuild + activate on a running host
./deploy boot|test <config> <host> # stage for next boot / activate without boot entry
<config> is a nixosConfigurations name (jupiter, neptun). Its pre-generated
SSH host key lives at ~/.config/homelab/<config>/ssh_host_ed25519_key.
Examples:
./deploy switch jupiter jupiter.sol
./deploy install neptun 159.195.64.117
Rollback: nixos-rebuild switch --rollback on the host, or pick a prior
generation at boot.
Adding a service
Copy the whoami block in oci-containers.containers, swap image/ports/volumes.
Native NixOS module exists for many apps (Nextcloud, Jellyfin, Grafana...) —
prefer services.<app> over a container when available. Add a caddy
virtualHosts block to expose it.
Notes
- Backend is Podman with
dockerCompat—dockerCLI works, no daemon. - Samba keeps its own password DB.
services.sambanever sets it; a systemd oneshot (samba-smbpasswd) provisions it. Host reads the password from/run/secrets/samba_password(sops-nix); the VM falls back to plaintext/etc/samba/smb-password. - Secrets:
secrets/jupiter.yamlis age-encrypted (safe to commit) to two recipients in.sops.yaml— the admin key (edit on laptop,~/.config/sops/age/keys.txt) and the jupiter host key (derived from its SSH host key viassh-to-age, decrypts at runtime). Private keys live off-repo and are gitignored. Rotate/add recipients withsops updatekeys. - Data disk: plain
fileSystems."/mnt/data"in configuration.nix — kept out of disko so it is never formatted. Reference byby-id/by-uuid. system.stateVersion=26.05, install-time schema. Do NOT bump on upgrades.- Terraform is not used: a single bare-metal box has no provider API. disko + nixos-anywhere cover provisioning natively.