nixpkgs only carries Zitadel 2.71, which predates the login-v2 split and
cannot take a v3/v4 database (its migrations are forward-only), so the
instance running on the old Debian VPS could never have moved onto it.
authentik-nix ships 2026.5.4 and tracks upstream closely.
The authentik-nix input deliberately does not follow our nixpkgs, per
upstream's warning that overriding it breaks their pinned python
dependency set. That costs a second nixpkgs in the lock, so add
nix-community's Cachix to common.nix -- without it the closure is ~400
local derivations (npm, rust, python). The laptop that runs
scripts/deploy needs the same two lines in /etc/nix/nix.custom.conf.
Authentik's own module creates the database and orders its units against
postgresql.target, and recent versions need no redis, so the wiring is
just the module plus a secret. Pin postgresql explicitly so that editing
system.stateVersion can never silently demand a pg_upgrade of the
identity store.
Secret ownership is not uniform and the difference matters: authentik
and caddy take a systemd EnvironmentFile, which PID 1 reads as root
before dropping privileges, so root:root 0400 is correct. Headplane
opens its secret paths itself while already running as the headscale
user, so those three need an explicit owner or they fail to start.
Also on neptun:
- Pass Caddy's ACME account email through the same EnvironmentFile
mechanism and reference it with the Caddyfile {$VAR} placeholder.
services.caddy.email would render the address into the world-readable
store.
- Stop accepting MagicDNS from our own control server. headscale pushes
override_local_dns, so joining the tailnet would point neptun's
resolv.conf at a MagicDNS served by the tailscaled neptun itself hosts
-- a tailscaled failure would then also take out DNS, ACME renewal and
finally the certs for the control server every other node needs in
order to recover.
- Give headplane a writable DNS extra-records file. Its view of
headscale's config stays read-only, which is the right outcome for a
declarative box; records are data rather than config.
- Require a password for sudo. Deploys become interactive, but darman's
key is otherwise the only thing between the public internet and root.
- Enable zram (8 GB, and disko leaves no room for a swap device), and let
tailscaled-autoconnect retry instead of failing permanently when the
control server isn't up yet on a first boot.
networking.hosts still carries a PLACEHOLDER address for jupiter --
replace it from `headscale nodes list` once jupiter first enrols.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
146 lines
5.8 KiB
Nix
146 lines
5.8 KiB
Nix
{
|
|
description = "Homelab NixOS configuration";
|
|
|
|
inputs = {
|
|
nixpkgs.url = "github:NixOS/nixpkgs/nixos-26.05";
|
|
disko = {
|
|
url = "github:nix-community/disko";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
sops-nix = {
|
|
url = "github:Mic92/sops-nix";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
nixos-images = {
|
|
url = "github:nix-community/nixos-images";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
mediamanager-nix = {
|
|
url = "github:strangeglyph/mediamanager-nix";
|
|
inputs.nixpkgs.follows = "nixpkgs";
|
|
};
|
|
# Deliberately NOT `inputs.nixpkgs.follows` — upstream states overriding it
|
|
# breaks their pinned python dependency set. Costs a second nixpkgs in the
|
|
# lock; builds come prebuilt from nix-community's Cachix.
|
|
authentik-nix.url = "github:nix-community/authentik-nix";
|
|
};
|
|
|
|
outputs = { self, nixpkgs, disko, sops-nix, nixos-images, mediamanager-nix, authentik-nix, ... }@inputs:
|
|
let
|
|
system = "x86_64-linux";
|
|
in
|
|
{
|
|
nixosConfigurations = {
|
|
# Real host — install on the ZimaBlade.
|
|
# disko owns the OS-disk partitioning + filesystems (see disk-config.nix).
|
|
jupiter = nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
specialArgs = { inherit inputs; };
|
|
modules = [
|
|
disko.nixosModules.disko
|
|
sops-nix.nixosModules.sops
|
|
./hosts/jupiter/configuration.nix
|
|
];
|
|
};
|
|
|
|
# netcup VPS — public reverse proxy + tailnet node.
|
|
neptun = nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
specialArgs = { inherit inputs; };
|
|
modules = [
|
|
disko.nixosModules.disko
|
|
sops-nix.nixosModules.sops
|
|
./hosts/neptun/configuration.nix
|
|
];
|
|
};
|
|
|
|
# mercury — Raspberry Pi 3B+ (aarch64), DNS/DHCP. Boots from an SD image:
|
|
# nix build .#nixosConfigurations.mercury.config.system.build.sdImage
|
|
# (aarch64 build — needs binfmt/qemu on this x86 host, or a remote/aarch64
|
|
# builder; substitutes most paths from cache.nixos.org.)
|
|
mercury = nixpkgs.lib.nixosSystem {
|
|
system = "aarch64-linux";
|
|
specialArgs = { inherit inputs; };
|
|
modules = [
|
|
(nixpkgs + "/nixos/modules/installer/sd-card/sd-image-aarch64.nix")
|
|
sops-nix.nixosModules.sops
|
|
./hosts/mercury/configuration.nix
|
|
];
|
|
};
|
|
|
|
# x86_64 QEMU VM to runtime-test mercury's DNS/DHCP stack (pihole +
|
|
# unbound) before flashing the aarch64 SD. Build + run:
|
|
# nix build .#nixosConfigurations.mercury-vm.config.system.build.vm
|
|
# ./result/bin/run-mercury-vm-vm
|
|
mercury-vm = nixpkgs.lib.nixosSystem {
|
|
inherit system; # x86_64-linux, fast to build/boot with KVM
|
|
modules = [
|
|
(nixpkgs + "/nixos/modules/virtualisation/qemu-vm.nix")
|
|
./common.nix
|
|
./services/network/unbound.nix
|
|
./services/network/pihole.nix
|
|
({ lib, ... }: {
|
|
networking.hostName = "mercury-vm";
|
|
networking.nameservers = [ "1.1.1.1" "9.9.9.9" ]; # host resolver (not pihole)
|
|
users.users.darman.initialPassword = "test";
|
|
users.users.root.initialPassword = "test";
|
|
services.openssh.settings.PasswordAuthentication = lib.mkForce true;
|
|
virtualisation.graphics = false;
|
|
virtualisation.memorySize = 2048;
|
|
virtualisation.forwardPorts = [
|
|
{ from = "host"; host.port = 2223; guest.port = 22; }
|
|
{ from = "host"; host.port = 8081; guest.port = 80; }
|
|
];
|
|
system.stateVersion = "26.05";
|
|
})
|
|
];
|
|
};
|
|
|
|
# VirtualBox test image. Build the OVA with:
|
|
# nix build .#nixosConfigurations.jupiter-vbox.config.system.build.virtualBoxOVA
|
|
# NOTE: no disko here — the virtualbox-image module supplies the disk.
|
|
jupiter-vbox = nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
specialArgs = { inherit inputs; };
|
|
modules = [ ./hosts/jupiter/vm.nix ];
|
|
};
|
|
|
|
# Custom kexec installer with our SSH key baked in, for headless install
|
|
# onto a box with a read-only root (ZimaOS) where nixos-anywhere can't
|
|
# ssh-copy-id. Build the tarball:
|
|
# nix build .#nixosConfigurations.kexec.config.system.build.kexecInstallerTarball
|
|
# then scp it to the target's writable /tmp and run kexec/run (see README).
|
|
kexec = nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
modules = [
|
|
nixos-images.nixosModules.kexec-installer
|
|
({ ... }: {
|
|
users.users.root.openssh.authorizedKeys.keys = [
|
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZpkPVhzi1zG5JI9hWyUgdyvNIQbp4ts4jw3idpMhhN erik@laptop"
|
|
];
|
|
})
|
|
];
|
|
};
|
|
|
|
# Bootable USB recovery installer with our SSH key + sshd + DHCP.
|
|
# Build the ISO:
|
|
# nix build .#nixosConfigurations.installer-iso.config.system.build.isoImage
|
|
# dd it to a USB stick, boot the ZimaBlade from it, SSH in, ./deploy install.
|
|
installer-iso = nixpkgs.lib.nixosSystem {
|
|
inherit system;
|
|
modules = [
|
|
(nixpkgs + "/nixos/modules/installer/cd-dvd/installation-cd-minimal.nix")
|
|
({ ... }: {
|
|
services.openssh.enable = true;
|
|
services.openssh.settings.PermitRootLogin = "prohibit-password";
|
|
users.users.root.openssh.authorizedKeys.keys = [
|
|
"ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGZpkPVhzi1zG5JI9hWyUgdyvNIQbp4ts4jw3idpMhhN erik@laptop"
|
|
];
|
|
networking.hostName = "jupiter-installer";
|
|
})
|
|
];
|
|
};
|
|
};
|
|
};
|
|
}
|