{ config, pkgs, lib, ... }: with lib; let cfg = config.fudo.backplane.dns; dns = import ../../../lib/dns.nix { inherit lib; }; 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."; }; }; }; 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."; }; }; }; lisp-libs = []; launchScript = pkgs.writeText "launch-backplane-dns.lisp" '' (load (merge-pathnames "quicklisp/setup.lisp" (user-homedir-pathname))) (ql:quickload :backplane-dns) (backplane-dns:start-listener-with-env) (loop (sleep 600)) ''; 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-addresses = mkOption { type = with types; listOf str; description = "IP addresses on which to listen for dns requests."; default = [ "0.0.0.0" ]; }; 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-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" ]; # 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 = { ExecStartPre = "${pkgs.lispPackages.quicklisp}/bin/quicklisp init"; ExecStart = "${pkgs.sbcl}/bin/sbcl --load ${launchScript}"; Restart = "on-failure"; PIDFile = "/run/backplane-dns.$USERNAME.pid"; User = cfg.user; Group = cfg.group; }; 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 = lib.concatStringsSep ":" (map (pkg: "${pkg}//") (lisp-libs ++ [pkgs.backplane-dns])); }; requires = cfg.required-services; partOf = [ "backplane-dns.target" ]; wantedBy = [ "multi-user.target" ]; }; }; }; }; }