{ lib, config, pkgs, ... }: let cfg = config.modules.services.dhcp-dns-sync; ownAddress = ( lib.elemAt (lib.splitString "/" config.systemd.network.networks."30-${cfg.interface}".networkConfig.Address ) 0 ); 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"; }; 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 # Directory needs to be group-writable for unbound group systemd.tmpfiles.rules = [ "d /var/lib/unbound 0775 unbound unbound -" "f ${cfg.unboundConfigPath} 0644 dhcp-dns-sync unbound -" ]; # Extend Unbound configuration to include generated file services.unbound.settings = { server = { local-zone = [ "${cfg.domain}. static" ]; include = cfg.unboundConfigPath; local-data = [ ''"apu.home.arpa. IN A ${ownAddress}"'' ]; local-data-ptr = [ ''"${ownAddress} apu.home.arpa."'' ]; }; }; # 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 = "unbound"; # Allow access to networkctl via D-Bus SupplementaryGroups = [ "systemd-network" ]; # Read/write paths ReadWritePaths = [ "/var/lib/unbound" ]; ExecStart = lib.concatStringsSep " " [ (lib.getExe dhcp-leases-to-unbound) "-i ${cfg.interface}" "-d ${cfg.domain}" "-o ${cfg.unboundConfigPath}" "--networkctl ${lib.getExe' pkgs.systemd "networkctl"}" "--unbound-control ${lib.getExe' pkgs.unbound "unbound-control"}" ]; }; }; # Systemd timer systemd.timers.dhcp-dns-sync = { description = "Periodic DHCP to DNS sync"; wantedBy = [ "timers.target" ]; timerConfig = { OnBootSec = "10s"; OnUnitActiveSec = cfg.interval; }; }; }; }