feat(networking): declare static IPs for all NixOS-managed hosts in flake

Adds static IP configuration to every NixOS host in the flake that
has a fixed LAN address, and centralises all network primitives
(IPs, gateway, prefix length, interface names) in variables.nix so
there is one place to update if any of them change.

variables.nix additions:
- lanGateway / lanPrefixLength — LAN gateway and /24 prefix, replacing
  every hardcoded 192.168.2.254 / 24 across host files
- lxcLanInterface / vmLanInterface / vmStorageInterface — NIC names for
  LXC containers (eth0), Proxmox VMs (ens18), and the HA storage NIC
  (ens19), used as attribute keys so changing the name is a one-line edit
- haStoragePrefixLength — /29 for the storage subnet, mirrors haStorageCidr
- Per-host IP variables: nixCacheIp (.224), tailscaleRouterIp (.222),
  torRelayIp (.221), serverIp (.226), dockerIp (.225)

host.nix changes:
- tailscale-router, tor-relay, nix-cache, pxe-boot: useDHCP = false,
  static address on eth0 (lxcLanInterface), struct-form defaultGateway
  (required when using systemd-networkd which LXC containers use)
- server, docker: useDHCP = false, static address on ens18 (vmLanInterface),
  struct-form defaultGateway (works for both scripted networking and networkd)
- ha-server-1, ha-server-2: replace hardcoded 192.168.2.254 / 24 / 29
  with the new variables; no functional change for these hosts

modules/build-types/pxe-boot.nix:
- Domain-controller kickstart template: replace hardcoded 192.168.2.138
  and 192.168.2.254 with vars.domainControllerIp / vars.lanGateway /
  vars.lanPrefixLength / vars.homeDomain so the template stays correct
  if the DC IP or domain is ever changed again

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ULXzafSDwGhmFGnn3LtDSQ
This commit is contained in:
2026-07-27 18:14:31 +10:00
co-authored by Claude Sonnet 4.6
parent e80d195284
commit ee96713a50
10 changed files with 98 additions and 33 deletions
+12 -3
View File
@@ -1,8 +1,17 @@
_: { vars, ... }:
{ {
networking.hostName = "docker"; networking = {
networking.hostId = "007f0200"; hostName = "docker";
hostId = "007f0200";
useDHCP = false;
interfaces.${vars.vmLanInterface}.ipv4.addresses = [{
address = vars.dockerIp;
prefixLength = vars.lanPrefixLength;
}];
defaultGateway = { address = vars.lanGateway; interface = vars.vmLanInterface; };
nameservers = [ vars.domainControllerIp ];
};
boot.zfs.forceImportRoot = false; boot.zfs.forceImportRoot = false;
# Preserved from the pre-refactor `docker` target — stateVersion must never # Preserved from the pre-refactor `docker` target — stateVersion must never
+5 -5
View File
@@ -11,15 +11,15 @@
hostName = vars.haServer1Host; hostName = vars.haServer1Host;
hostId = "3a4b5c6d"; hostId = "3a4b5c6d";
useDHCP = false; useDHCP = false;
interfaces.ens18.ipv4.addresses = [{ interfaces.${vars.vmLanInterface}.ipv4.addresses = [{
address = vars.haServer1Ip; address = vars.haServer1Ip;
prefixLength = 24; prefixLength = vars.lanPrefixLength;
}]; }];
interfaces.ens19.ipv4.addresses = [{ interfaces.${vars.vmStorageInterface}.ipv4.addresses = [{
address = vars.haServer1StorageIp; address = vars.haServer1StorageIp;
prefixLength = 29; prefixLength = vars.haStoragePrefixLength;
}]; }];
defaultGateway = "192.168.2.254"; defaultGateway = { address = vars.lanGateway; interface = vars.vmLanInterface; };
nameservers = [ vars.domainControllerIp ]; nameservers = [ vars.domainControllerIp ];
}; };
+5 -5
View File
@@ -11,15 +11,15 @@
hostName = vars.haServer2Host; hostName = vars.haServer2Host;
hostId = "7e8f9a0b"; hostId = "7e8f9a0b";
useDHCP = false; useDHCP = false;
interfaces.ens18.ipv4.addresses = [{ interfaces.${vars.vmLanInterface}.ipv4.addresses = [{
address = vars.haServer2Ip; address = vars.haServer2Ip;
prefixLength = 24; prefixLength = vars.lanPrefixLength;
}]; }];
interfaces.ens19.ipv4.addresses = [{ interfaces.${vars.vmStorageInterface}.ipv4.addresses = [{
address = vars.haServer2StorageIp; address = vars.haServer2StorageIp;
prefixLength = 29; prefixLength = vars.haStoragePrefixLength;
}]; }];
defaultGateway = "192.168.2.254"; defaultGateway = { address = vars.lanGateway; interface = vars.vmLanInterface; };
nameservers = [ vars.domainControllerIp ]; nameservers = [ vars.domainControllerIp ];
}; };
+10 -1
View File
@@ -8,7 +8,16 @@
}) })
]; ];
networking.hostName = vars.nixCacheHost; networking = {
hostName = vars.nixCacheHost;
useDHCP = false;
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
address = vars.nixCacheIp;
prefixLength = vars.lanPrefixLength;
}];
defaultGateway = { address = vars.lanGateway; interface = vars.lxcLanInterface; };
nameservers = [ vars.domainControllerIp ];
};
services.beszel.agent.environment = { services.beszel.agent.environment = {
#DOCKER_HOST = "tcp://docker-socket-proxy:2375"; #DOCKER_HOST = "tcp://docker-socket-proxy:2375";
+11 -2
View File
@@ -1,7 +1,16 @@
_: { vars, ... }:
{ {
networking.hostName = "pxe-boot"; networking = {
hostName = "pxe-boot";
useDHCP = false;
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
address = vars.pxeServerIp;
prefixLength = vars.lanPrefixLength;
}];
defaultGateway = { address = vars.lanGateway; interface = vars.lxcLanInterface; };
nameservers = [ vars.domainControllerIp ];
};
# Preserved from the pre-refactor `pxe-boot` target — stateVersion must # Preserved from the pre-refactor `pxe-boot` target — stateVersion must
# never be bumped on an already-installed machine. # never be bumped on an already-installed machine.
+11 -2
View File
@@ -8,8 +8,17 @@
}) })
]; ];
networking.hostName = vars.nfsServerHost; networking = {
networking.hostId = "6689f93e"; hostName = vars.nfsServerHost;
hostId = "6689f93e";
useDHCP = false;
interfaces.${vars.vmLanInterface}.ipv4.addresses = [{
address = vars.serverIp;
prefixLength = vars.lanPrefixLength;
}];
defaultGateway = { address = vars.lanGateway; interface = vars.vmLanInterface; };
nameservers = [ vars.domainControllerIp ];
};
services.beszel.agent.environment = { services.beszel.agent.environment = {
#DOCKER_HOST = "tcp://docker-socket-proxy:2375"; #DOCKER_HOST = "tcp://docker-socket-proxy:2375";
+11 -2
View File
@@ -1,4 +1,4 @@
_: { vars, ... }:
{ {
imports = [ imports = [
@@ -8,7 +8,16 @@ _:
}) })
]; ];
networking.hostName = "tailscale-router"; networking = {
hostName = "tailscale-router";
useDHCP = false;
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
address = vars.tailscaleRouterIp;
prefixLength = vars.lanPrefixLength;
}];
defaultGateway = { address = vars.lanGateway; interface = vars.lxcLanInterface; };
nameservers = [ vars.domainControllerIp ];
};
services.beszel.agent.environment = { services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG"; KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
+13 -4
View File
@@ -1,4 +1,4 @@
{ ... }: { vars, ... }:
{ {
imports = [ imports = [
@@ -8,10 +8,19 @@
}) })
]; ];
networking.hostName = "tor-relay"; networking = {
hostName = "tor-relay";
useDHCP = false;
interfaces.${vars.lxcLanInterface}.ipv4.addresses = [{
address = vars.torRelayIp;
prefixLength = vars.lanPrefixLength;
}];
defaultGateway = { address = vars.lanGateway; interface = vars.lxcLanInterface; };
nameservers = [ vars.domainControllerIp ];
};
# No networking.hostId: only ZFS-touching hosts (server, docker) need one # No networking.hostId: only ZFS-touching hosts need one for pool-import
# for pool-import safety, and this host does neither. # safety, and this host does neither.
services.beszel.agent.environment = { services.beszel.agent.environment = {
KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG"; KEY = "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFPR9kwtC4TAeTRu46A7+opZsYpxqkRJ+x/ZyB2GWCeG";
+6 -6
View File
@@ -82,7 +82,7 @@ let
rockyFreeIpaKs = pkgs.writeText "rocky-freeipa.ks" '' rockyFreeIpaKs = pkgs.writeText "rocky-freeipa.ks" ''
#version=RHEL9 #version=RHEL9
# Unattended Rocky Linux 9 + FreeIPA install # Unattended Rocky Linux 9 + FreeIPA install
# Target: domain-controller.sweet.home 192.168.2.138 # Target: domain-controller.${vars.homeDomain} ${vars.domainControllerIp}
url --url=${rockyMirror}/BaseOS/${rockyArch}/os/ url --url=${rockyMirror}/BaseOS/${rockyArch}/os/
repo --name=appstream --baseurl=${rockyMirror}/AppStream/${rockyArch}/os/ repo --name=appstream --baseurl=${rockyMirror}/AppStream/${rockyArch}/os/
@@ -136,10 +136,10 @@ let
[ipv4] [ipv4]
method=manual method=manual
addresses=192.168.2.138/24 addresses=${vars.domainControllerIp}/${toString vars.lanPrefixLength}
gateway=192.168.2.254 gateway=${vars.lanGateway}
dns=192.168.2.253; dns=${vars.domainControllerIp};
dns-search=sweet.home; dns-search=${vars.homeDomain};
[ipv6] [ipv6]
method=auto method=auto
@@ -148,7 +148,7 @@ let
# -- /etc/hosts: FQDN must resolve to the real IP (not loopback) for IPA -- # -- /etc/hosts: FQDN must resolve to the real IP (not loopback) for IPA --
sed -i '/domain-controller/d' /etc/hosts sed -i '/domain-controller/d' /etc/hosts
echo '192.168.2.138 domain-controller.sweet.home domain-controller' >> /etc/hosts echo '${vars.domainControllerIp} domain-controller.${vars.homeDomain} domain-controller' >> /etc/hosts
# -- Generate IPA passwords and store securely -- # -- Generate IPA passwords and store securely --
DM_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24) DM_PASS=$(openssl rand -base64 24 | tr -dc 'A-Za-z0-9' | head -c 24)
+14 -3
View File
@@ -4,9 +4,19 @@
homeDomain = "sweet.home"; # base LAN domain for service subdomains (pve., docker.) homeDomain = "sweet.home"; # base LAN domain for service subdomains (pve., docker.)
tailnetDomain = "tail13f623.ts.net"; # Tailscale MagicDNS suffix tailnetDomain = "tail13f623.ts.net"; # Tailscale MagicDNS suffix
lanCidr = "192.168.2.0/24"; # LAN subnet lanCidr = "192.168.2.0/24"; # LAN subnet
pxeServerIp = "192.168.2.223"; # pxe-boot host's LAN IP lanGateway = "192.168.2.254"; # LAN default gateway (router)
pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP lanPrefixLength = 24; # LAN subnet prefix length (/24 = 255.255.255.0)
domainControllerIp = "192.168.2.253"; # FreeIPA domain controller / primary DNS lxcLanInterface = "eth0"; # LAN NIC name in LXC containers (set by Proxmox --net0 name=eth0)
vmLanInterface = "ens18"; # LAN NIC name in Proxmox VMs (virtio, first NIC)
vmStorageInterface = "ens19"; # storage NIC name in HA server VMs (virtio, second NIC on vmbr1)
pxeServerIp = "192.168.2.223"; # pxe-boot LXC container LAN IP
nixCacheIp = "192.168.2.224"; # nix-cache LXC container LAN IP
tailscaleRouterIp = "192.168.2.222"; # tailscale-router LXC container LAN IP
torRelayIp = "192.168.2.221"; # tor-relay LXC container LAN IP
serverIp = "192.168.2.226"; # server (NFS/ZFS) Proxmox VM LAN IP
dockerIp = "192.168.2.225"; # docker Proxmox VM LAN IP
pbsIp = "192.168.2.244"; # Proxmox Backup Server LAN IP (not NixOS-managed)
domainControllerIp = "192.168.2.253"; # FreeIPA domain controller / primary DNS (not NixOS-managed)
# Cross-host references (LAN hostnames/users other hosts reach over the network) # Cross-host references (LAN hostnames/users other hosts reach over the network)
nixCacheHost = "nix-cache"; # substituter/remote-builder hostname nixCacheHost = "nix-cache"; # substituter/remote-builder hostname
@@ -83,6 +93,7 @@
haServer1StorageIp = "192.168.4.228"; # storage-net IP, node 1 (vmbr1 / ens19) haServer1StorageIp = "192.168.4.228"; # storage-net IP, node 1 (vmbr1 / ens19)
haServer2StorageIp = "192.168.4.227"; # storage-net IP, node 2 (vmbr1 / ens19) haServer2StorageIp = "192.168.4.227"; # storage-net IP, node 2 (vmbr1 / ens19)
haStorageCidr = "192.168.4.0/29"; # storage subnet — internal to pve1 only haStorageCidr = "192.168.4.0/29"; # storage subnet — internal to pve1 only
haStoragePrefixLength = 29; # storage subnet prefix length (/29)
haStorageRoot = "/srv/ha-data"; # XFS-over-DRBD mount point on the Active node haStorageRoot = "/srv/ha-data"; # XFS-over-DRBD mount point on the Active node
haIscsiIqn = "iqn.2026-01.home.sweet:ha-storage"; haIscsiIqn = "iqn.2026-01.home.sweet:ha-storage";