apu: setup dynamic dns record based on DHCP leases
Some checks failed
Build Images / build (push) Failing after 1m21s
Check / check (push) Successful in 3m36s

This commit is contained in:
Joakim Repomaa
2026-02-21 13:05:22 +02:00
parent 6690b5c1ea
commit 13e119a6c3
5 changed files with 324 additions and 1 deletions

View File

@@ -0,0 +1,130 @@
{
lib,
config,
pkgs,
...
}:
let
cfg = config.modules.services.dhcp-dns-sync;
dhcp-leases-to-unbound =
pkgs.runCommand "dhcp-leases-to-unbound"
{
code = ./dhcp-leases-to-unbound.cr;
nativeBuildInputs = [ pkgs.crystal ];
meta.mainProgram = "dhcp-leases-to-unbound";
}
''
mkdir -p $out/bin
crystal build $code --release -o $out/bin/dhcp-leases-to-unbound
'';
in
{
options.modules.services.dhcp-dns-sync = {
enable = lib.mkEnableOption "Enable DHCP to DNS synchronization";
interface = lib.mkOption {
type = lib.types.str;
default = "koti";
description = "Network interface to monitor for DHCP leases";
};
domain = lib.mkOption {
type = lib.types.str;
default = "home.arpa";
description = "Domain suffix for DHCP hostnames";
};
unboundConfigPath = lib.mkOption {
type = lib.types.str;
default = "/var/lib/unbound/dhcp-hosts.conf";
description = "Path to write Unbound include file";
};
leasesJsonPath = lib.mkOption {
type = lib.types.str;
default = "/var/lib/router/leases.json";
description = "Path to write leases JSON file";
};
interval = lib.mkOption {
type = lib.types.str;
default = "30s";
description = "Interval for checking DHCP lease updates";
};
};
config = lib.mkIf cfg.enable {
# Create user and group for the service
users.users.dhcp-dns-sync = {
isSystemUser = true;
group = "dhcp-dns-sync";
description = "DHCP DNS sync service user";
};
users.groups.dhcp-dns-sync = { };
# Ensure directories and files exist with proper permissions
systemd.tmpfiles.rules = [
"d /var/lib/unbound 0755 unbound unbound -"
"d /var/lib/router 0755 dhcp-dns-sync dhcp-dns-sync -"
"f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync dhcp-dns-sync -"
];
# Extend Unbound configuration to include generated file
services.unbound.settings = {
server = {
local-zone = [ "${cfg.domain}. static" ];
include = cfg.unboundConfigPath;
};
};
# Make sure Unbound control is enabled
services.unbound.settings.remote-control.control-enable = true;
# Systemd service
systemd.services.dhcp-dns-sync = {
description = "Sync DHCP leases to Unbound DNS";
after = [
"systemd-networkd.service"
"unbound.service"
];
requires = [ "unbound.service" ];
wants = [ "unbound-control.socket" ];
serviceConfig = {
Type = "oneshot";
User = "dhcp-dns-sync";
Group = "dhcp-dns-sync";
# Allow access to networkctl via D-Bus
SupplementaryGroups = [ "systemd-network" ];
# Read/write paths
ReadWritePaths = [
"/var/lib/unbound"
"/var/lib/router"
];
# Execute paths
ExecPaths = [ "/run/current-system/sw/bin" ];
};
script = ''
${lib.getExe dhcp-leases-to-unbound} \
-i ${cfg.interface} \
-d ${cfg.domain} \
-o ${cfg.unboundConfigPath} \
--leases-json ${cfg.leasesJsonPath} \
--networkctl ${lib.getExe' pkgs.systemd "networkctl"}
'';
};
# Systemd timer
systemd.timers.dhcp-dns-sync = {
description = "Periodic DHCP to DNS sync";
wantedBy = [ "timers.target" ];
timerConfig = {
OnBootSec = "10s";
OnUnitActiveSec = cfg.interval;
};
};
};
}