{ config, pkgs, lib, ... }: with lib; let cfg = config.fudo.backplane.dns; powerdns-conf-dir = "${cfg.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 = with types; { enable = mkEnableOption "Enable backplane dynamic DNS server."; port = mkOption { type = port; description = "Port on which to serve authoritative DNS requests."; default = 53; }; listen-v4-addresses = mkOption { type = listOf str; description = "IPv4 addresses on which to listen for dns requests."; default = [ "0.0.0.0" ]; }; listen-v6-addresses = mkOption { type = listOf str; description = "IPv6 addresses on which to listen for dns requests."; example = [ "[abcd::1]" ]; default = [ ]; }; required-services = mkOption { type = listOf str; description = "A list of services required before the DNS server can start."; }; user = mkOption { type = str; description = "User as which to run DNS backplane listener service."; default = "backplane-dns"; }; group = mkOption { type = str; description = "Group as which to run DNS backplane listener service."; default = "backplane-dns"; }; database = mkOption { type = submodule databaseOpts; description = "Database settings for the DNS server."; }; powerdns-home = mkOption { type = str; description = "Directory at which to store powerdns configuration and state."; default = "/run/backplane-dns/powerdns"; }; backplane = mkOption { type = 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; home = cfg.powerdns-home; createHome = true; }; }; groups = { "${cfg.group}" = { members = [ cfg.user ]; }; backplane-powerdns = { members = [ "backplane-powerdns" ]; }; }; }; fudo.system.services = { backplane-powerdns-config-generator = { description = "Generate postgres configuration for backplane DNS server."; requires = cfg.required-services; type = "oneshot"; restartIfChanged = true; partOf = [ "backplane-dns.target" ]; readWritePaths = [ powerdns-conf-dir ]; 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-powerdns = let pdns-config-dir = 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}/ ''; in { description = "Backplane PowerDNS name server"; requires = [ "postgresql.service" "backplane-powerdns-config-generator.service" ]; after = [ "network.target" ]; path = with pkgs; [ powerdns postgresql ]; execStart = "pdns_server --setuid=backplane-powerdns --setgid=backplane-powerdns --chroot=${cfg.powerdns-home} --socket-dir=/ --daemon=no --guardian=no --disable-syslog --write-pid=no --config-dir=${pdns-config-dir}"; }; backplane-dns = { description = "Fudo DNS Backplane Server"; restartIfChanged = true; path = with pkgs; [ backplane-dns-server ]; execStart = "launch-backplane-dns.sh"; pidFile = "/run/backplane-dns.$USERNAME.pid"; user = cfg.user; group = cfg.group; partOf = [ "backplane-dns.target" ]; requires = [ "postgresql.service" ]; environment = { 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.lib.fudo.lisp.lisp-source-registry pkgs.backplane-dns-server; }; }; }; systemd = { targets = { backplane-dns = { description = "Fudo DNS backplane services."; wantedBy = [ "multi-user.target" ]; requries = cfg.required-services ++ [ "postgresql.service" ]; }; }; }; }; }