{ config, pkgs, lib, ... }: with lib; let cfg = config.fudo.backplane.dns; lisp-pkgs = with pkgs.localLispPackages; [ arrows backplane-dns backplane-server cl-sasl cl-xmpp ip-utils alexandria babel bordeaux-threads cffi cl-base64 cl-json cl-postgres cl-ppcre cl-unicode cl_plus_ssl closer-mop closure-common cxml flexi-streams global-vars introspect-environment ironclad iterate lisp-namespace md5 nibbles postmodern puri s-sql split-sequence trivia trivia_dot_balland2006 trivia_dot_level0 trivia_dot_level1 trivia_dot_level2 trivia_dot_trivial trivial-cltl2 trivial-features trivial-garbage trivial-gray-streams type-i uax-15 usocket ]; backup-directory = "/var/lib/fudo/backplane/dns"; powerdns-home = "/var/lib/powerdns"; powerdns-conf-dir = "${powerdns-home}/conf.d"; backplaneOpts = { ... }: { options = { host = mkOption { type = types.str; description = "Hostname of the backplane jabber server."; }; role = mkOption { type = types.str; description = "Backplane XMPP role name for the DNS server."; default = "service-dns"; }; password-file = mkOption { type = types.str; description = "File containing XMPP password for backplane role."; }; database = mkOption { type = with types; submodule databaseOpts; description = "Database settings for backplane server."; }; cl-wrapper-package = mkOption { type = types.package; description = "Common Lisp wrapper package to use."; default = pkgs.lispPackages.clwrapper; }; }; }; databaseOpts = { ... }: { options = { host = mkOption { type = types.str; description = "Hostname or IP of the PostgreSQL server."; }; database = mkOption { type = types.str; description = "Database to use for DNS backplane."; default = "backplane_dns"; }; username = mkOption { type = types.str; description = "Database user for DNS backplane."; default = "backplane_dns"; }; password-file = mkOption { type = types.str; description = "File containing password for database user."; }; }; }; in { options.fudo.backplane.dns = { enable = mkEnableOption "Enable backplane dynamic DNS server."; port = mkOption { type = types.port; description = "Port on which to serve authoritative DNS requests."; default = 53; }; listen-v4-addresses = mkOption { type = with types; listOf str; description = "IPv4 addresses on which to listen for dns requests."; default = [ "0.0.0.0" ]; }; listen-v6-addresses = mkOption { type = with types; listOf str; description = "IPv6 addresses on which to listen for dns requests."; example = [ "[abcd::1]" ]; default = [ ]; }; required-services = mkOption { type = with types; listOf str; description = "A list of services required before the DNS server can start."; }; user = mkOption { type = types.str; description = "User as which to run DNS backplane listener service."; default = "backplane-dns"; }; group = mkOption { type = types.str; description = "Group as which to run DNS backplane listener service."; default = "backplane-dns"; }; database = mkOption { type = with types; submodule databaseOpts; description = "Database settings for the DNS server."; }; backplane = mkOption { type = with types; submodule backplaneOpts; description = "Backplane Jabber settings for the DNS server."; }; }; config = mkIf cfg.enable { users = { users = { "${cfg.user}" = { isSystemUser = true; group = cfg.group; createHome = true; home = "/var/home/${cfg.user}"; }; backplane-powerdns = { isSystemUser = true; }; }; groups = { "${cfg.group}" = { members = [ cfg.user ]; }; backplane-powerdns = { members = [ "backplane-powerdns" ]; }; }; }; systemd = { targets = { backplane-dns = { description = "Fudo DNS backplane services."; wantedBy = [ "multi-user.target" ]; }; }; services = { backplane-powerdns = let configDir = pkgs.writeTextDir "pdns.conf" '' local-address=${lib.concatStringsSep ", " cfg.listen-v4-addresses} local-ipv6=${lib.concatStringsSep ", " cfg.listen-v6-addresses} local-port=${toString cfg.port} launch= include-dir=${powerdns-conf-dir}/ ''; psql-user = config.services.postgresql.superUser; in { unitConfig.Documentation = "man:pdns_server(1) man:pdns_control(1)"; description = "Backplane PowerDNS name server"; requires = [ "postgresql.service" "backplane-dns-config-generator.service" "backplane-dns.target" ]; after = [ "network.target" "postgresql.service" ]; wantedBy = [ "multi-user.target" ]; path = with pkgs; [ postgresql ]; serviceConfig = { Restart = "on-failure"; RestartSec = "10"; StartLimitInterval = "0"; PrivateDevices = true; # CapabilityBoundingSet="CAP_CHOWN CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT"; # NoNewPrivileges=true; ExecStartPre = "${pkgs.coreutils}/bin/mkdir -p ${powerdns-home}"; ExecStart = "${pkgs.powerdns}/bin/pdns_server --setuid=backplane-powerdns --setgid=backplane-powerdns --chroot=${powerdns-home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${configDir}"; ProtectSystem = "full"; # ProtectHome=true; RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6"; }; }; backplane-dns-config-generator = { description = "Generate postgres configuration for backplane DNS server."; requiredBy = [ "backplane-powerdns.service" ]; requires = cfg.required-services; serviceConfig.Type = "oneshot"; restartIfChanged = true; partOf = [ "backplane-dns.target" ]; preStart = '' mkdir -p ${powerdns-conf-dir} chown backplane-powerdns:backplane-powerdns ${powerdns-conf-dir} ''; # This builds the config in a bash script, to avoid storing the password # in the nix store at any point script = '' if [ ! -d ${powerdns-conf-dir} ]; then mkdir ${powerdns-conf-dir} fi TMPDIR=$(${pkgs.coreutils}/bin/mktemp -d -t pdns-XXXXXXXXXX) TMPCONF=$TMPDIR/pdns.local.gpgsql.conf if [ ! -f ${cfg.database.password-file} ]; then echo "${cfg.database.password-file} does not exist!" exit 1 fi touch $TMPCONF chown backplane-powerdns:backplane-powerdns $TMPCONF chmod go-rwx $TMPCONF PASSWORD=$(cat ${cfg.database.password-file}) echo "launch+=gpgsql" >> $TMPCONF echo "gpgsql-host=${cfg.database.host}" >> $TMPCONF echo "gpgsql-dbname=${cfg.database.database}" >> $TMPCONF echo "gpgsql-user=${cfg.database.username}" >> $TMPCONF echo "gpgsql-password=$PASSWORD" >> $TMPCONF echo "gpgsql-dnssec=yes" >> $TMPCONF mv $TMPCONF ${powerdns-conf-dir}/pdns.local.gpgsql.conf rm -rf $TMPDIR exit 0 ''; }; backplane-dns = { description = "Fudo DNS Backplane Server"; restartIfChanged = true; serviceConfig = { ExecStart = "${pkgs.backplane-dns-server}/bin/launch-backplane-dns.sh"; Restart = "on-failure"; PIDFile = "/run/backplane-dns.$USERNAME.pid"; User = cfg.user; Group = cfg.group; StandardOutput = "journal"; }; environment = { # LD_LIBRARY_PATH = "${pkgs.openssl_1_1.out}/lib"; FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane.host; FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane.role; FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane.password-file; FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.backplane.database.host; FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.backplane.database.database; FUDO_DNS_BACKPLANE_DATABASE_USERNAME = cfg.backplane.database.username; FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE = cfg.backplane.database.password-file; # CL_SOURCE_REGISTRY = "${pkgs.localLispPackages.backplane-dns}//"; CL_SOURCE_REGISTRY = lib.concatStringsSep ":" (map (pkg: "${pkg}//") lisp-pkgs); }; requires = cfg.required-services; partOf = [ "backplane-dns.target" ]; wantedBy = [ "multi-user.target" ]; }; }; }; }; }