dns-automation #15

Merged
torjus merged 4 commits from dns-automation into master 2026-02-04 21:02:24 +00:00
14 changed files with 296 additions and 103 deletions
Showing only changes of commit cee1b264cd - Show all commits

View File

@@ -11,6 +11,8 @@
../../common/vm ../../common/vm
]; ];
homelab.dns.cnames = [ "ldap" ];
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
# Use the systemd-boot EFI boot loader. # Use the systemd-boot EFI boot loader.
boot.loader.grub = { boot.loader.grub = {

View File

@@ -11,6 +11,22 @@
../../common/vm ../../common/vm
]; ];
homelab.dns.cnames = [
"nzbget"
"radarr"
"sonarr"
"ha"
"z2m"
"grafana"
"prometheus"
"alertmanager"
"jelly"
"auth"
"lldap"
"pyroscope"
"pushgw"
];
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
# Use the systemd-boot EFI boot loader. # Use the systemd-boot EFI boot loader.
boot.loader.grub = { boot.loader.grub = {

View File

@@ -11,6 +11,8 @@
../../common/vm ../../common/vm
]; ];
homelab.dns.cnames = [ "nix-cache" "actions1" ];
fileSystems."/nix" = { fileSystems."/nix" = {
device = "/dev/disk/by-label/nixcache"; device = "/dev/disk/by-label/nixcache";
fsType = "xfs"; fsType = "xfs";

View File

@@ -8,6 +8,9 @@
../../system ../../system
]; ];
# Template host - exclude from DNS zone generation
homelab.dns.enable = false;
boot.loader.grub.enable = true; boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/sda"; boot.loader.grub.device = "/dev/sda";

View File

@@ -13,6 +13,9 @@
../../common/vm ../../common/vm
]; ];
# Test VM - exclude from DNS zone generation
homelab.dns.enable = false;
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
boot.loader.grub.enable = true; boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/vda"; boot.loader.grub.device = "/dev/vda";

View File

@@ -14,6 +14,8 @@
../../services/vault ../../services/vault
]; ];
homelab.dns.cnames = [ "vault" ];
nixpkgs.config.allowUnfree = true; nixpkgs.config.allowUnfree = true;
boot.loader.grub.enable = true; boot.loader.grub.enable = true;
boot.loader.grub.device = "/dev/vda"; boot.loader.grub.device = "/dev/vda";

160
lib/dns-zone.nix Normal file
View File

@@ -0,0 +1,160 @@
{ lib }:
let
# Pad string on the right to reach a fixed width
rightPad = width: str:
let
len = builtins.stringLength str;
padding = if len >= width then "" else lib.strings.replicate (width - len) " ";
in
str + padding;
# Extract IP address from CIDR notation (e.g., "10.69.13.5/24" -> "10.69.13.5")
extractIP = address:
let
parts = lib.splitString "/" address;
in
builtins.head parts;
# Check if a network interface name looks like a VPN/tunnel interface
isVpnInterface = ifaceName:
lib.hasPrefix "wg" ifaceName ||
lib.hasPrefix "tun" ifaceName ||
lib.hasPrefix "tap" ifaceName ||
lib.hasPrefix "vti" ifaceName;
# Extract DNS information from a single host configuration
# Returns null if host should not be included in DNS
extractHostDNS = name: hostConfig:
let
cfg = hostConfig.config;
# Handle cases where homelab module might not be imported
dnsConfig = (cfg.homelab or { }).dns or { enable = true; cnames = [ ]; };
hostname = cfg.networking.hostName;
networks = cfg.systemd.network.networks or { };
# Filter out VPN interfaces and find networks with static addresses
# Check matchConfig.Name instead of network unit name (which can have prefixes like "40-")
physicalNetworks = lib.filterAttrs
(netName: netCfg:
let
ifaceName = netCfg.matchConfig.Name or "";
in
!(isVpnInterface ifaceName) && (netCfg.address or [ ]) != [ ])
networks;
# Get addresses from physical networks only
networkAddresses = lib.flatten (
lib.mapAttrsToList
(netName: netCfg: netCfg.address or [ ])
physicalNetworks
);
# Get the first address, if any
firstAddress = if networkAddresses != [ ] then builtins.head networkAddresses else null;
# Check if host uses DHCP (no static address)
usesDHCP = firstAddress == null ||
lib.any
(netName: (networks.${netName}.networkConfig.DHCP or "no") != "no")
(lib.attrNames networks);
in
if !(dnsConfig.enable or true) || firstAddress == null then
null
else
{
inherit hostname;
ip = extractIP firstAddress;
cnames = dnsConfig.cnames or [ ];
};
# Generate A record line
generateARecord = hostname: ip:
"${rightPad 20 hostname}IN A ${ip}";
# Generate CNAME record line
generateCNAME = alias: target:
"${rightPad 20 alias}IN CNAME ${target}";
# Generate zone file from flake configurations and external hosts
generateZone =
{ self
, externalHosts
, serial
, domain ? "home.2rjus.net"
, ttl ? 1800
, refresh ? 3600
, retry ? 900
, expire ? 1209600
, minTtl ? 120
, nameservers ? [ "ns1" "ns2" "ns3" ]
, adminEmail ? "admin.test.2rjus.net"
}:
let
# Extract DNS info from all flake hosts
nixosConfigs = self.nixosConfigurations or { };
hostDNSList = lib.filter (x: x != null) (
lib.mapAttrsToList extractHostDNS nixosConfigs
);
# Sort hosts by IP for consistent output
sortedHosts = lib.sort (a: b: a.ip < b.ip) hostDNSList;
# Generate A records for flake hosts
flakeARecords = lib.concatMapStringsSep "\n" (host:
generateARecord host.hostname host.ip
) sortedHosts;
# Generate CNAMEs for flake hosts
flakeCNAMEs = lib.concatMapStringsSep "\n" (host:
lib.concatMapStringsSep "\n" (cname:
generateCNAME cname host.hostname
) host.cnames
) (lib.filter (h: h.cnames != [ ]) sortedHosts);
# Generate A records for external hosts
externalARecords = lib.concatStringsSep "\n" (
lib.mapAttrsToList (name: ip:
generateARecord name ip
) (externalHosts.aRecords or { })
);
# Generate CNAMEs for external hosts
externalCNAMEs = lib.concatStringsSep "\n" (
lib.mapAttrsToList (alias: target:
generateCNAME alias target
) (externalHosts.cnames or { })
);
# NS records
nsRecords = lib.concatMapStringsSep "\n" (ns:
" IN NS ${ns}.${domain}."
) nameservers;
# SOA record
soa = ''
$ORIGIN ${domain}.
$TTL ${toString ttl}
@ IN SOA ns1.${domain}. ${adminEmail}. (
${toString serial} ; serial number
${toString refresh} ; refresh
${toString retry} ; retry
${toString expire} ; expire
${toString minTtl} ; ttl
)'';
in
lib.concatStringsSep "\n\n" (lib.filter (s: s != "") [
soa
nsRecords
"; Flake-managed hosts (auto-generated)"
flakeARecords
(if flakeCNAMEs != "" then "; Flake-managed CNAMEs\n${flakeCNAMEs}" else "")
"; External hosts (not managed by this flake)"
externalARecords
(if externalCNAMEs != "" then "; External CNAMEs\n${externalCNAMEs}" else "")
""
]);
in
{
inherit extractIP extractHostDNS generateARecord generateCNAME generateZone;
}

View File

@@ -0,0 +1,6 @@
{ ... }:
{
imports = [
./dns.nix
];
}

20
modules/homelab/dns.nix Normal file
View File

@@ -0,0 +1,20 @@
{ config, lib, ... }:
let
cfg = config.homelab.dns;
in
{
options.homelab.dns = {
enable = lib.mkOption {
type = lib.types.bool;
default = true;
description = "Include this host in DNS zone generation";
};
cnames = lib.mkOption {
type = lib.types.listOf lib.types.str;
default = [ ];
description = "CNAME records pointing to this host";
example = [ "web" "api" ];
};
};
}

View File

@@ -0,0 +1,52 @@
# DNS records for hosts not managed by this flake
# These are manually maintained and combined with auto-generated records
{
aRecords = {
# 8_k8s
"kube-blue1" = "10.69.8.150";
"kube-blue2" = "10.69.8.151";
"kube-blue3" = "10.69.8.152";
"kube-blue4" = "10.69.8.153";
"kube-blue5" = "10.69.8.154";
"kube-blue6" = "10.69.8.155";
"kube-blue7" = "10.69.8.156";
"kube-blue8" = "10.69.8.157";
"kube-blue9" = "10.69.8.158";
"kube-blue10" = "10.69.8.159";
# 10
"gw" = "10.69.10.1";
# 12_CORE
"virt-mini1" = "10.69.12.11";
"nas" = "10.69.12.50";
"nzbget-jail" = "10.69.12.51";
"restic" = "10.69.12.52";
"radarr-jail" = "10.69.12.53";
"sonarr-jail" = "10.69.12.54";
"bazarr" = "10.69.12.55";
"mpnzb" = "10.69.12.57";
"pve1" = "10.69.12.75";
"inc1" = "10.69.12.80";
"inc2" = "10.69.12.81";
# 22_WLAN
"unifi-ctrl" = "10.69.22.5";
# 30
"gunter" = "10.69.30.105";
# 31
"media" = "10.69.31.50";
# 99_MGMT
"sw1" = "10.69.99.2";
"testing" = "10.69.33.33";
};
cnames = {
# k8s services
"rook" = "kube-blue4";
"git" = "kube-blue5";
};
}

View File

@@ -1,4 +1,16 @@
{ ... }: { self, lib, ... }:
let
dnsLib = import ../../lib/dns-zone.nix { inherit lib; };
externalHosts = import ./external-hosts.nix;
# Generate zone from flake hosts + external hosts
# Use lastModified from git commit as serial number
zoneData = dnsLib.generateZone {
inherit self externalHosts;
serial = self.sourceInfo.lastModified;
domain = "home.2rjus.net";
};
in
{ {
sops.secrets.ns_xfer_key = { sops.secrets.ns_xfer_key = {
path = "/etc/nsd/xfer.key"; path = "/etc/nsd/xfer.key";
@@ -26,7 +38,7 @@
"home.2rjus.net" = { "home.2rjus.net" = {
provideXFR = [ "10.69.13.6 xferkey" ]; provideXFR = [ "10.69.13.6 xferkey" ];
notify = [ "10.69.13.6@8053 xferkey" ]; notify = [ "10.69.13.6@8053 xferkey" ];
data = builtins.readFile ./zones-home-2rjus-net.conf; data = zoneData;
}; };
}; };
}; };

View File

@@ -1,4 +1,16 @@
{ ... }: { self, lib, ... }:
let
dnsLib = import ../../lib/dns-zone.nix { inherit lib; };
externalHosts = import ./external-hosts.nix;
# Generate zone from flake hosts + external hosts
# Used as initial zone data before first AXFR completes
zoneData = dnsLib.generateZone {
inherit self externalHosts;
serial = self.sourceInfo.lastModified;
domain = "home.2rjus.net";
};
in
{ {
sops.secrets.ns_xfer_key = { sops.secrets.ns_xfer_key = {
path = "/etc/nsd/xfer.key"; path = "/etc/nsd/xfer.key";
@@ -24,7 +36,7 @@
"home.2rjus.net" = { "home.2rjus.net" = {
allowNotify = [ "10.69.13.5 xferkey" ]; allowNotify = [ "10.69.13.5 xferkey" ];
requestXFR = [ "AXFR 10.69.13.5@8053 xferkey" ]; requestXFR = [ "AXFR 10.69.13.5@8053 xferkey" ];
data = builtins.readFile ./zones-home-2rjus-net.conf; data = zoneData;
}; };
}; };
}; };

View File

@@ -1,99 +0,0 @@
$ORIGIN home.2rjus.net.
$TTL 1800
@ IN SOA ns1.home.2rjus.net. admin.test.2rjus.net. (
2066 ; serial number
3600 ; refresh
900 ; retry
1209600 ; expire
120 ; ttl
)
IN NS ns1.home.2rjus.net.
IN NS ns2.home.2rjus.net.
IN NS ns3.home.2rjus.net.
; 8_k8s
kube-blue1 IN A 10.69.8.150
kube-blue2 IN A 10.69.8.151
kube-blue3 IN A 10.69.8.152
kube-blue4 IN A 10.69.8.153
rook IN CNAME kube-blue4
kube-blue5 IN A 10.69.8.154
git IN CNAME kube-blue5
kube-blue6 IN A 10.69.8.155
kube-blue7 IN A 10.69.8.156
kube-blue8 IN A 10.69.8.157
kube-blue9 IN A 10.69.8.158
kube-blue10 IN A 10.69.8.159
; 10
gw IN A 10.69.10.1
; 12_CORE
virt-mini1 IN A 10.69.12.11
nas IN A 10.69.12.50
nzbget-jail IN A 10.69.12.51
restic IN A 10.69.12.52
radarr-jail IN A 10.69.12.53
sonarr-jail IN A 10.69.12.54
bazarr IN A 10.69.12.55
mpnzb IN A 10.69.12.57
pve1 IN A 10.69.12.75
inc1 IN A 10.69.12.80
inc2 IN A 10.69.12.81
media1 IN A 10.69.12.82
; 13_SVC
ns1 IN A 10.69.13.5
ns2 IN A 10.69.13.6
ns3 IN A 10.69.13.7
ns4 IN A 10.69.13.8
ha1 IN A 10.69.13.9
nixos-test1 IN A 10.69.13.10
http-proxy IN A 10.69.13.11
ca IN A 10.69.13.12
monitoring01 IN A 10.69.13.13
jelly01 IN A 10.69.13.14
nix-cache01 IN A 10.69.13.15
nix-cache IN CNAME nix-cache01
actions1 IN CNAME nix-cache01
pgdb1 IN A 10.69.13.16
nats1 IN A 10.69.13.17
auth01 IN A 10.69.13.18
vault01 IN A 10.69.13.19
vault IN CNAME vault01
vaulttest01 IN A 10.69.13.150
; http-proxy cnames
nzbget IN CNAME http-proxy
radarr IN CNAME http-proxy
sonarr IN CNAME http-proxy
ha IN CNAME http-proxy
z2m IN CNAME http-proxy
grafana IN CNAME http-proxy
prometheus IN CNAME http-proxy
alertmanager IN CNAME http-proxy
jelly IN CNAME http-proxy
auth IN CNAME http-proxy
lldap IN CNAME http-proxy
pyroscope IN CNAME http-proxy
pushgw IN CNAME http-proxy
ldap IN CNAME auth01
; 22_WLAN
unifi-ctrl IN A 10.69.22.5
; 30
gunter IN A 10.69.30.105
; 31
media IN A 10.69.31.50
; 99_MGMT
sw1 IN A 10.69.99.2
testing IN A 10.69.33.33

View File

@@ -11,5 +11,7 @@
./sops.nix ./sops.nix
./sshd.nix ./sshd.nix
./vault-secrets.nix ./vault-secrets.nix
../modules/homelab
]; ];
} }