186 lines
5.8 KiB
Nix
186 lines
5.8 KiB
Nix
{ lib, pkgs, config, ... }:
|
|
let
|
|
inherit (builtins) isList;
|
|
inherit (lib) mkMerge mkIf mkDefault mapAttrsToList concatMapStringsSep concatStringsSep;
|
|
inherit (lib.my) mkBoolOpt' mkOpt';
|
|
|
|
# Yoinked from nixos/modules/services/networking/pdns-recursor.nix
|
|
oneOrMore = type: with lib.types; either type (listOf type);
|
|
valueType = with lib.types; oneOf [ int str bool path ];
|
|
configType = with lib.types; attrsOf (nullOr (oneOrMore valueType));
|
|
|
|
toBool = val: if val then "yes" else "no";
|
|
serialize = val: with lib.types;
|
|
if str.check val then val
|
|
else if int.check val then toString val
|
|
else if path.check val then toString val
|
|
else if bool.check val then toBool val
|
|
else if isList val then (concatMapStringsSep "," serialize val)
|
|
else "";
|
|
settingsToLines = s: concatStringsSep "\n" (mapAttrsToList (k: v: "${k}=${serialize v}") s);
|
|
|
|
bindList = l: "{ ${concatStringsSep "; " l} }";
|
|
bindAlsoNotify = with lib.types; mkOpt' (listOf str) [ ] "List of additional address to send DNS NOTIFY messages to.";
|
|
bindZoneOpts = with lib.types; { name, config, ... }: {
|
|
options = {
|
|
type = mkOpt' (enum [ "master" "slave" "native" ]) "native" "Zone type.";
|
|
masters = mkOpt' (listOf str) [ ] "List of masters to retrieve data from (as slave).";
|
|
also-notify = bindAlsoNotify;
|
|
|
|
template = mkBoolOpt' true "Whether to run the zone contents through a template for post-processing.";
|
|
text = mkOpt' (nullOr lines) null "Inline content of the zone file.";
|
|
path = mkOpt' path null "Path to zone file.";
|
|
};
|
|
|
|
config.path = mkIf (config.text != null) (pkgs.writeText "${name}.zone" config.text);
|
|
};
|
|
namedZone = n: o: ''
|
|
zone "${n}" IN {
|
|
file "/run/pdns/bind-zones/${n}.zone";
|
|
type ${o.type};
|
|
masters ${bindList o.masters};
|
|
also-notify ${bindList o.also-notify};
|
|
};
|
|
'';
|
|
|
|
loadZonesCommon = pkgs.writeShellScript "pdns-bind-load-common.sh" ''
|
|
loadZones() {
|
|
for z in /etc/pdns/bind-zones/*.zone; do
|
|
zoneName="$(echo "$z" | ${pkgs.gnused}/bin/sed -rn 's|/etc/pdns/bind-zones/(.*)\.zone|\1|p')"
|
|
|
|
zDat="/var/lib/pdns/bind-zones/"$zoneName".dat"
|
|
newZonePath="$(readlink -f "$z")"
|
|
if [ ! -e "$zDat" ]; then
|
|
echo "zonePath=\"$newZonePath\"" > "$zDat"
|
|
echo "serial=$(date +%Y%m%d00)" >> "$zDat"
|
|
fi
|
|
source "$zDat"
|
|
|
|
subSerial() {
|
|
${pkgs.gnused}/bin/sed "s/@@SERIAL@@/$serial/g" < "$z" > /run/pdns/bind-zones/"$zoneName".zone
|
|
}
|
|
# Zone in /run won't have changed if it didn't exist
|
|
if [ "$newZonePath" != "$zonePath" ]; then
|
|
echo "$zoneName has changed; incrementing serial..."
|
|
((serial++))
|
|
echo "zonePath=\"$newZonePath\"" > "$zDat"
|
|
echo "serial=$serial" >> "$zDat"
|
|
|
|
subSerial
|
|
if [ "$1" = reload ]; then
|
|
echo "Reloading $zoneName"
|
|
${pkgs.pdns}/bin/pdns_control bind-reload-now "$zoneName"
|
|
fi
|
|
elif [ "$1" != reload ]; then
|
|
subSerial
|
|
fi
|
|
done
|
|
}
|
|
'';
|
|
|
|
cfg = config.my.pdns;
|
|
|
|
namedConf = pkgs.writeText "pdns-named.conf" ''
|
|
options {
|
|
directory "/run/pdns/bind-zones";
|
|
also-notify ${bindList cfg.auth.bind.options.also-notify};
|
|
};
|
|
|
|
${concatStringsSep "\n" (mapAttrsToList namedZone cfg.auth.bind.zones)}
|
|
'';
|
|
|
|
templateZone = n: s: pkgs.runCommand "${n}.zone" {
|
|
passAsFile = [ "script" ];
|
|
script = ''
|
|
import re
|
|
import ipaddress
|
|
import sys
|
|
|
|
def ptr(m):
|
|
ip = ipaddress.ip_address(m.group(1))
|
|
return '.'.join(ip.reverse_pointer.split('.')[:int(m.group(2))])
|
|
ex = re.compile(r'@@PTR:(.+):(\d+)@@')
|
|
|
|
for line in sys.stdin:
|
|
print(ex.sub(ptr, line), end=''')
|
|
'';
|
|
} ''
|
|
${pkgs.python310}/bin/python "$scriptPath" < "${s}" > "$out"
|
|
'';
|
|
zones = pkgs.linkFarm "pdns-bind-zones" (mapAttrsToList (n: o: rec {
|
|
name = "${n}.zone";
|
|
path = if o.template then templateZone n o.path else o.path;
|
|
}) cfg.auth.bind.zones);
|
|
in
|
|
{
|
|
options.my.pdns = with lib.types; {
|
|
auth = {
|
|
enable = mkBoolOpt' false "Whether to enable PowerDNS authoritative nameserver.";
|
|
settings = mkOpt' configType { } "Authoritative server settings.";
|
|
|
|
bind = {
|
|
options = {
|
|
also-notify = bindAlsoNotify;
|
|
};
|
|
zones = mkOpt' (attrsOf (submodule bindZoneOpts)) { } "BIND-style zones definitions.";
|
|
};
|
|
};
|
|
};
|
|
|
|
config = mkMerge [
|
|
(mkIf cfg.auth.enable {
|
|
my = {
|
|
tmproot.persistence.config.directories = [ "/var/lib/pdns" ];
|
|
pdns.auth.settings = {
|
|
launch = [ "bind" ];
|
|
socket-dir = "/run/pdns";
|
|
bind-config = namedConf;
|
|
expand-alias = mkDefault true;
|
|
};
|
|
};
|
|
|
|
environment = {
|
|
# For pdns_control etc
|
|
systemPackages = with pkgs; [
|
|
pdns
|
|
];
|
|
|
|
etc."pdns/bind-zones".source = "${zones}/*";
|
|
};
|
|
|
|
systemd.services.pdns = {
|
|
preStart = ''
|
|
source ${loadZonesCommon}
|
|
|
|
mkdir /run/pdns/bind-zones
|
|
mkdir -p /var/lib/pdns/bind-zones
|
|
loadZones start
|
|
'';
|
|
postStart = ''
|
|
chmod -R g+w /run/pdns /var/lib/pdns
|
|
'';
|
|
|
|
# pdns reloads existing zones, so the only trigger will be if the zone files themselves change. If any new zones
|
|
# are added or removed, named.conf will change, in turn changing the overall pdns settings and causing pdns to
|
|
# get fully restarted
|
|
reload = ''
|
|
source ${loadZonesCommon}
|
|
|
|
loadZones reload
|
|
'';
|
|
|
|
reloadTriggers = [ zones ];
|
|
serviceConfig = {
|
|
RuntimeDirectory = "pdns";
|
|
StateDirectory = "pdns";
|
|
};
|
|
};
|
|
|
|
services.powerdns = {
|
|
enable = true;
|
|
extraConfig = settingsToLines cfg.auth.settings;
|
|
};
|
|
})
|
|
];
|
|
}
|