Add text library function (format-json-file)

This commit is contained in:
niten 2022-02-01 10:49:35 -08:00
parent f49233c0ac
commit 125d2d3d57
5 changed files with 893 additions and 0 deletions

View File

@ -0,0 +1,272 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fudo.adguard-dns-proxy;
hostname = config.instance.hostname;
get-basename = filename:
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
format-json-file = filename: pkgs.stdenv.mkDerivation {
name = "formatted-${get-basename filename}";
phases = [ "installPhase" ];
buildInputs = with pkgs; [ python ];
installPhase = "python -mjson.tool ${filename} > $out";
};
admin-passwd-file =
pkgs.lib.passwd.stablerandom-passwd-file
"adguard-dns-proxy-admin"
config.instance.build-seed;
filterOpts = {
options = with types; {
enable = mkOption {
type = bool;
description = "Enable this filter on DNS traffic.";
default = true;
};
name = mkOption {
type = str;
description = "Name of this filter.";
};
url = mkOption {
type = str;
description = "URL to the filter itself.";
default = true;
};
};
};
generate-config = { dns,
http,
filters,
verbose,
upstream-dns,
bootstrap-dns,
blocked-hosts,
enable-dnssec,
local-domain-name,
... }: {
bind_host = http.listen-ip;
bind_port = http.listen-port;
users = [
{
name = "admin";
password = pkgs.lib.passwd.bcrypt-passwd
"adguard-dns-proxy-admin"
admin-passwd-file;
}
];
auth_attempts = 5;
block_auth_min = 30;
web_session_ttl = 720;
dns = {
bind_hosts = dns.listen-ips;
port = dns.listen-port;
upstream_dns = upstream-dns;
bootstrap_dns = bootstrap-dns;
blocking_mode = "default";
blocked_hosts = blocked-hosts;
enable_dnssec = enable-dnssec;
local_domain_name = local-domain-name;
};
tls.enabled = false;
filters = imap1 (i: filter: {
enabled = true;
name = filter.name;
url = filter.url;
}) filters;
dhcp.enabled = false;
clients = [];
verbose = verbose;
schema_version = 10;
};
generate-config-file = opts:
format-json-file (pkgs.writeText "adguard-dns-proxy-config.yaml"
(builtins.toJSON (generate-config opts)));
in {
options.fudo.adguard-dns-proxy = with types; {
enable = mkEnableOption "Enable AdGuardHome DNS proxy.";
dns = {
listen-ips = mkOption {
type = listOf str;
description = "IP on which to listen for incoming DNS requests.";
default = [ "0.0.0.0" ];
};
listen-port = mkOption {
type = port;
description = "Port on which to listen for DNS queries.";
default = 53;
};
};
http = {
listen-ip = mkOption {
type = str;
description = "IP on which to listen for incoming HTTP requests.";
};
listen-port = mkOption {
type = port;
description = "Port on which to listen for incoming HTTP queries.";
default = 8053;
};
};
filters = mkOption {
type = listOf (submodule filterOpts);
description = "List of filters to apply to DNS traffic.";
default = [
{
name = "AdGuard DNS filter";
url = "https://adguardteam.github.io/AdGuardSDNSFilter/Filters/filter.txt";
}
{
name = "AdAway Default Blocklist";
url = "https://adaway.org/hosts.txt";
}
{
name = "MalwareDomainList.com Hosts List";
url = "https://www.malwaredomainlist.com/hostslist/hosts.txt";
}
{
name = "OISD.NL Blocklist";
url = "https://abp.oisd.nl/";
}
];
};
blocked-hosts = mkOption {
type = listOf str;
description = "List of hosts to explicitly block.";
default = [
"version.bind"
"id.server"
"hostname.bind"
];
};
enable-dnssec = mkOption {
type = bool;
description = "Enable DNSSEC";
default = true;
};
upstream-dns = mkOption {
type = listOf str;
description = ''
List of upstream DNS services to use.
See https://github.com/AdguardTeam/dnsproxy for correct formatting.
'';
default = [
"https://1.1.1.1/dns-query"
"https://1.0.0.1/dns-query"
# These 11 addrs send the network, so the response can prefer closer answers
"https://9.9.9.11/dns-query"
"https://149.112.112.11/dns-query"
"https://2620:fe::11/dns-query"
"https://2620:fe::fe:11/dns-query"
];
};
bootstrap-dns = mkOption {
type = listOf str;
description = "List of DNS servers used to bootstrap DNS-over-HTTPS.";
default = [
"1.1.1.1"
"1.0.0.1"
"9.9.9.9"
"149.112.112.112"
"2620:fe::10"
"2620:fe::fe:10"
];
};
allowed-networks = mkOption {
type = nullOr (listOf str);
description = "Optional list of networks with which this job may communicate.";
default = null;
};
user = mkOption {
type = str;
description = "User as which this job will run.";
default = "adguard-dns-proxy";
};
local-domain-name = mkOption {
type = str;
description = "Local domain name.";
};
verbose = mkEnableOption "Keep verbose logs.";
};
config = mkIf cfg.enable (let
upgrade-perms = cfg.dns.listen-port <= 1024 || cfg.http.listen-port <= 1024;
in {
users = mkIf upgrade-perms {
users.${cfg.user} = {
isSystemUser = true;
group = cfg.user;
};
groups.${cfg.user} = {
members = [ cfg.user ];
};
};
fudo = {
secrets.host-secrets.${hostname} = {
adguard-dns-proxy-admin-password = {
source-file = admin-passwd-file;
target-file = "/run/adguard-dns-proxy/admin.passwd";
user = "root";
};
};
system.services.adguard-dns-proxy = let
cfg-path = "/run/adguard-dns-proxy/config.yaml";
in {
description = "DNS Proxy for ad filtering and DNS-over-HTTPS lookups.";
wantedBy = [ "default.target" ];
after = [ "syslog.target" ];
requires = [ "network.target" ];
privateNetwork = false;
requiredCapabilities = optional upgrade-perms "CAP_NET_BIND_SERVICE";
restartWhen = "always";
addressFamilies = null;
networkWhitelist = cfg.allowed-networks;
user = mkIf upgrade-perms cfg.user;
runtimeDirectory = "adguard-dns-proxy";
stateDirectory = "adguard-dns-proxy";
preStart = ''
cp ${generate-config-file cfg} ${cfg-path};
chown $USER ${cfg-path};
chmod u+w ${cfg-path};
'';
execStart = let
args = [
"--no-check-update"
"--work-dir /var/lib/adguard-dns-proxy"
"--pidfile /run/adguard-dns-proxy/adguard-dns-proxy.pid"
"--host ${cfg.http.listen-ip}"
"--port ${toString cfg.http.listen-port}"
"--config ${cfg-path}"
];
arg-string = concatStringsSep " " args;
in "${pkgs.adguardhome}/bin/adguardhome ${arg-string}";
};
};
});
}

View File

@ -0,0 +1,131 @@
{ config, lib, pkgs, ... } @ toplevel:
with lib;
let
cfg = config.fudo.backplane.dns;
backplane-dns-home = "${config.instance.service-home}/backplane-dns";
in {
options.fudo.backplane.dns = with types; {
enable = mkEnableOption "Enable DNS backplane service.";
required-services = mkOption {
type = listOf str;
description = "List of systemd units on which the DNS backplane job depends.";
default = [ ];
};
backplane-server = mkOption {
type = str;
description = "Backplane XMPP server hostname.";
default = toplevel.config.fudo.backplane.backplane-hostname;
};
user = mkOption {
type = str;
description = "User as which to run the backplane dns service.";
default = "backplane-dns";
};
group = mkOption {
type = str;
description = "Group as which to run the backplane dns service.";
default = "backplane-dns";
};
database = {
host = mkOption {
type = str;
description = "Hostname or IP of the PostgreSQL server.";
};
database = mkOption {
type = str;
description = "Database to use for DNS backplane.";
default = "backplane_dns";
};
username = mkOption {
type = str;
description = "Database user for DNS backplane.";
default = "backplane_dns";
};
password-file = mkOption {
type = str;
description = "File containing password for DNS backplane database user.";
};
ssl-mode = mkOption {
type = enum ["no" "yes" "full" "try" "require"];
description = "SSL connection mode.";
default = "require";
};
};
backplane-role = {
role = mkOption {
type = str;
description = "Backplane XMPP role name for DNS backplane job.";
default = "service-dns";
};
password-file = mkOption {
type = str;
description = "Password file for backplane XMPP for DNS backplane job.";
};
};
};
config = mkIf cfg.enable {
users = {
users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
home = backplane-dns-home;
createHome = true;
};
groups.${cfg.group} = {
members = [ cfg.user ];
};
};
fudo.system.services = {
backplane-dns = {
description = "Fudo DNS Backplane Server";
restartIfChanged = true;
path = with pkgs; [ backplane-dns-server ];
execStart = "${pkgs.backplane-dns-server}/bin/launch-backplane-dns.sh";
#pidFile = "/run/backplane/dns.pid";
partOf = [ "backplane-dns.target" ];
wantedBy = [ "multi-user.target" ];
requires = cfg.required-services;
user = cfg.user;
group = cfg.group;
memoryDenyWriteExecute = false; # Needed becuz Lisp
readWritePaths = [ backplane-dns-home ];
privateNetwork = false;
addressFamilies = [ "AF_INET" "AF_INET6" ];
environment = {
FUDO_DNS_BACKPLANE_XMPP_HOSTNAME = cfg.backplane-server;
FUDO_DNS_BACKPLANE_XMPP_USERNAME = cfg.backplane-role.role;
FUDO_DNS_BACKPLANE_XMPP_PASSWORD_FILE = cfg.backplane-role.password-file;
FUDO_DNS_BACKPLANE_DATABASE_HOSTNAME = cfg.database.host;
FUDO_DNS_BACKPLANE_DATABASE_NAME = cfg.database.database;
FUDO_DNS_BACKPLANE_DATABASE_USERNAME =
cfg.database.username;
FUDO_DNS_BACKPLANE_DATABASE_PASSWORD_FILE =
cfg.database.password-file;
FUDO_DNS_BACKPLANE_DATABASE_USE_SSL = cfg.database.ssl-mode;
CL_SOURCE_REGISTRY =
pkgs.lib.lisp.lisp-source-registry pkgs.backplane-dns-server;
LD_LIBRARY_PATH = "${pkgs.openssl.out}/lib";
};
};
};
};
}

134
lib/fudo/backplane.nix Normal file
View File

@ -0,0 +1,134 @@
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.fudo.backplane;
hostname = config.instance.hostname;
host-secrets = config.fudo.secrets.host-secrets.${hostname};
generate-auth-file = name: files: let
make-entry = name: passwd-file:
''("${name}" . "${readFile passwd-file}")'';
entries = mapAttrsToList make-entry files;
content = concatStringsSep "\n" entries;
in pkgs.writeText "${name}-backplane-auth.scm" "'(${content})";
host-auth-file = generate-auth-file "host"
(mapAttrs (hostname: hostOpts: hostOpts.password-file)
cfg.client-hosts);
service-auth-file = generate-auth-file "service"
(mapAttrs (service: serviceOpts: serviceOpts.password-file)
cfg.services);
clientHostOpts = { name, ... }: {
options = with types; {
password-file = mkOption {
type = path;
description =
"Location (on the build host) of the file containing the host password.";
};
};
};
serviceOpts = { name, ... }: {
options = with types; {
password-file = mkOption {
type = path;
description =
"Location (on the build host) of the file containing the service password.";
};
};
};
in {
options.fudo.backplane = with types; {
enable = mkEnableOption "Enable backplane XMPP server.";
client-hosts = mkOption {
type = attrsOf (submodule clientHostOpts);
description = "List of backplane client options.";
default = {};
};
services = mkOption {
type = attrsOf (submodule serviceOpts);
description = "List of backplane service options.";
default = {};
};
backplane-hostname = mkOption {
type = str;
description = "Hostname of the backplane XMPP server.";
};
};
config = mkIf cfg.enable {
fudo = {
secrets.host-secrets.${hostname} = {
backplane-host-auth = {
source-file = generate-auth-file "host"
(mapAttrs (hostname: hostOpts: hostOpts.password-file)
cfg.client-hosts);
target-file = "/run/backplane/host-passwords.scm";
user = config.fudo.jabber.user;
};
backplane-service-auth = {
source-file = generate-auth-file "service"
(mapAttrs (service: serviceOpts: serviceOpts.password-file)
cfg.services);
target-file = "/run/backplane/service-passwords.scm";
user = config.fudo.jabber.user;
};
};
jabber = {
environment = {
FUDO_HOST_PASSWD_FILE =
host-secrets.backplane-host-auth.target-file;
FUDO_SERVICE_PASSWD_FILE =
host-secrets.backplane-service-auth.target-file;
};
sites.${cfg.backplane-hostname} = {
hostname = cfg.backplane-hostname;
site-config = {
auth_method = "external";
extauth_program =
"${pkgs.guile}/bin/guile -s ${pkgs.backplane-auth}/backplane-auth.scm";
extauth_pool_size = 3;
auth_use_cache = true;
modules = {
mod_adhoc = {};
mod_caps = {};
mod_carboncopy = {};
mod_client_state = {};
mod_configure = {};
mod_disco = {};
mod_fail2ban = {};
mod_last = {};
mod_offline.access_max_user_messages = 5000;
mod_ping = {};
mod_pubsub = {
access_createnode = "pubsub_createnode";
ignore_pep_from_offline = true;
last_item_cache = false;
plugins = [
"flat"
"pep"
];
};
mod_roster = {};
mod_stream_mgmt = {};
mod_time = {};
mod_version = {};
};
};
};
};
};
};
}

339
lib/fudo/powerdns.nix Normal file
View File

@ -0,0 +1,339 @@
{ config, lib, pkgs, ... } @ toplevel:
# NOTE: To get DNS records:
# pdnsutil --config-dir=... show-zone <domain>
with lib;
let
cfg = config.fudo.powerdns;
hostname = config.instance.hostname;
runtime-dir = "/run/powerdns/conf";
target-gpgsql-config = "${runtime-dir}/pdns.local.gpgsql.conf";
gpgsql-template = pkgs.writeText "pdns.gpgsql.conf.template" ''
launch+=gpgsql
gpgsql-host=${cfg.database.host}
gpgsql-dbname=${cfg.database.database}
gpgsql-user=${cfg.database.user}
gpgsql-password=__PASSWORD__
gpgsql-dnssec=${if cfg.enable-dnssec then "yes" else "no"}
gpgsql-extra-connection-parameters=sslmode=require
'';
pdns-config-dir = pkgs.writeTextDir "pdns.conf" ''
local-address=${concatStringsSep ", " cfg.listen-v4-addresses}
local-ipv6=${concatStringsSep ", " cfg.listen-v6-addresses}
local-port=${toString cfg.port}
launch=
include-dir=${runtime-dir}
'';
make-pgpass-file = user: target-file: let
db = cfg.database;
in pkgs.writeShellScript "genenrate-pgpass-file.sh" ''
touch ${target-file}
chown ${user} ${target-file}
chmod 700 ${target-file}
PASSWORD=$(cat ${db.password-file})
echo "${db.host}:${toString db.port}:${db.database}:${db.user}:__PASSWORD__" | sed "s/__PASSWORD__/$PASSWORD/" > ${target-file}
'';
mkRecord = name: type: content: {
inherit name type content;
};
initialize-domain-sql = domain: let
domain-name = domain.domain;
host-ip = pkgs.lib.network.host-ipv4 config hostname;
ipv6-net = net: (builtins.match ":" net) != null;
ipv4-net = net: !(ipv6-net net);
domain-records = [
(mkRecord domain-name "SOA" "ns1.${domain-name} hostmaster.${domain-name} ${toString config.instance.build-timestamp} 10800 3600 1209600 3600")
(mkRecord "_dmark.${domain-name}" "TXT" ''"v=DMARC1; p=reject; rua=mailto:${domain.admin}; ruf=mailto:${domain.admin}; fo=1;"'')
(mkRecord domain-name "NS" "ns1.${domain-name}")
(mkRecord domain-name "TXT" (let
networks = config.instance.local-networks;
v4-nets = map (net: "ip4:${net}") (filter ipv4-net networks);
v6-nets = map (net: "ip6:${net}") (filter ipv6-net networks);
networks-string = concatStringsSep " " (v4-nets ++ v6-nets);
in ''"v=spf1 mx ${networks-string} -all"''))
(mkRecord "ns1.${domain-name}" "A" host-ip)
] ++
(optional (domain.gssapi-realm != null)
(mkRecord "_kerberos.${domain-name}" "TXT" ''"domain.gssapi-realm"'')) ++
(mapAttrsToList (alias: target: mkRecord alias "CNAME" target)
domain.aliases);
records-strings = map (record:
"INSERT INTO records (domain_id, name, type, content) SELECT id, '${record.name}', '${record.type}', '${record.content}' FROM domains WHERE name='${domain-name}';")
domain-records;
in pkgs.writeText "initialize-${domain-name}.sql" ''
INSERT INTO domains (name, master, type, notified_serial) VALUES ('${domain-name}', '${host-ip}', 'MASTER', '${toString config.instance.build-timestamp}');
${concatStringsSep "\n" records-strings}
'';
initialize-domain-script = domain: let
domain-name = domain.domain;
in pkgs.writeShellScript "initialize-${domain-name}.sh" ''
if [ "$( psql -tAc "SELECT id FROM domains WHERE name='${domain-name}'" )" ]; then
logger "${domain-name} already initialized, skipping"
exit 0
else
logger "initializing ${domain-name} in powerdns database"
psql -f ${initialize-domain-sql domain}
fi
'';
domainOpts = { name, ... }: {
options = with types; {
domain = mkOption {
type = str;
description = "Domain name.";
default = name;
};
admin = mkOption {
type = str;
description = "Administrator email.";
default = "admin@${name}";
};
aliases = mkOption {
type = attrsOf str;
description = "Map of alias to authoritative hostname for this domain.";
default = {};
};
gssapi-realm = mkOption {
type = nullOr str;
description = "GSSAPI realm of this domain.";
default = null;
};
};
};
in {
options.fudo.powerdns = with types; {
enable = mkEnableOption "Enable PowerDNS nameserver.";
port = mkOption {
type = port;
description = "Port on which to listen for DNS requests.";
default = 53;
};
user = mkOption {
type = str;
description = "User as which to run PowerDNS server.";
default = "powerdns";
};
group = mkOption {
type = str;
description = "Group as which to run PowerDNS server.";
default = "powerdns";
};
listen-v4-addresses = mkOption {
type = listOf str;
description = "List of IPv4 addresses on which to listen.";
};
listen-v6-addresses = mkOption {
type = listOf str;
description = "List of IPv6 addresses on which to listen.";
default = [];
};
domains = mkOption {
type = attrsOf (submodule domainOpts);
description = "Domains to be served by this DNS server.";
};
enable-dnssec = mkOption {
type = bool;
description = "Enable DNSSEC for this domain.";
default = true;
};
database = {
host = mkOption {
type = str;
description = "Hostname or IP of the PostgreSQL server.";
};
port = mkOption {
type = port;
description = "Port of the PostgreSQL server.";
default = 5432;
};
database = mkOption {
type = str;
description = "Database to use for DNS backplane.";
default = "backplane_dns";
};
user = mkOption {
type = str;
description = "Database user for DNS backplane.";
default = "backplane_dns";
};
password-file = mkOption {
type = str;
description = "File containing password for database user.";
};
};
};
config = mkIf cfg.enable {
networking.firewall.allowedTCPPorts = [ cfg.port ];
users = {
users.${cfg.user} = {
isSystemUser = true;
group = cfg.group;
};
groups.${cfg.group}.members = [ cfg.user ];
};
fudo.system.services.powerdns-config-generator = {
description = "Generate PostgreSQL config for backplane DNS server.";
type = "oneshot";
restartIfChanged = true;
readWritePaths = [ runtime-dir ];
user = cfg.user;
execStart = let
script = pkgs.writeShellScript "generate-powerdns-config.sh" ''
TARGET=${target-gpgsql-config}
touch $TARGET
chown ${cfg.user}:${cfg.group} $TARGET
chmod 0700 $TARGET
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
sed -e 's/__PASSWORD__/$PASSWORD/' ${gpgsql-template} > $TARGET
'';
in "${script}";
};
systemd = let
pgpass-file = "${runtime-dir}/pgpass";
initialize-jobs = mapAttrs' (_: domain: let
domain-name = domain.domain;
in nameValuePair "powerdns-initialize-${domain-name}" {
description = "Initialize the ${domain-name} domain";
requires = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
after = [ "powerdns-initialize-db.service" "powerdns-generate-pgpass.service" ];
requiredBy = [ "powerdns.service" ];
wantedBy = [ "powerdns.service" ];
before = [ "powerdns.service" ];
environment = {
PGHOST = cfg.database.host;
PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port;
PGSSLMODE= "require";
PGPASSFILE = pgpass-file;
};
path = with pkgs; [ postgresql util-linux ];
serviceConfig = {
ExecStart = initialize-domain-script domain;
};
}) cfg.domains;
in {
tmpfiles.rules = [
"d ${runtime-dir} 0750 ${cfg.user} ${cfg.group} - -"
];
services = initialize-jobs // {
powerdns-generate-pgpass = {
description = "Create pgpass file required for database init.";
serviceConfig = {
ExecStart = make-pgpass-file cfg.user "${runtime-dir}/pgpass";
};
};
powerdns-initialize-db = {
description = "Initialize the powerdns database.";
requiredBy = [ "powerdns.service" ];
before = [ "powerdns.service" ];
requires = [ "powerdns-generate-pgpass.service" ];
after = [ "powerdns-generate-pgpass.service" ];
path = with pkgs; [ postgresql util-linux ];
environment = {
PGHOST = cfg.database.host;
PGUSER = cfg.database.user;
PGDATABASE = cfg.database.database;
PGPORT = toString cfg.database.port;
PGSSLMODE= "require";
PGPASSFILE = pgpass-file;
};
serviceConfig = {
ExecStart = pkgs.writeShellScript "powerdns-initialize-db.sh" ''
if [ "$( psql -tAc "SELECT to_regclass('public.domains')" )" ]; then
logger "database initialized, skipping"
else
logger "initializing powerdns database"
psql -f ${pkgs.powerdns}/share/doc/pdns/schema.pgsql.sql
fi
'';
};
};
powerdns = {
description = "PowerDNS nameserver.";
requires = [ "powerdns-config-generator.service" ];
after = [
"network.target"
"powerdns-config-generator.service"
];
wantedBy = [ "multi-user.target" ];
path = with pkgs; [ powerdns postgresql util-linux ];
serviceConfig = {
ExecStartPre = pkgs.writeShellScript "powerdns-init-config.sh" ''
TARGET=${target-gpgsql-config}
touch $TARGET
chown ${cfg.user}:${cfg.group} $TARGET
chmod 0700 $TARGET
PASSWORD=$( cat ${cfg.database.password-file} | tr -d '\n')
sed -e "s/__PASSWORD__/$PASSWORD/" ${gpgsql-template} > $TARGET
'';
ExecStart = pkgs.writeShellScript "launch-powerdns.sh" (concatStringsSep " " [
"${pkgs.powerdns}/bin/pdns_server"
"--setuid=${cfg.user}"
"--setgid=${cfg.group}"
"--chroot=${runtime-dir}"
"--daemon=no"
"--guardian=no"
"--disable-syslog"
"--write-pid=no"
"--config-dir=${pdns-config-dir}"
]);
ExecStartPost = pkgs.writeShellScript "powerdns-secure-zones.sh"
(concatStringsSep "\n"
(mapAttrsToList
(_: domain: ''
DNSINFO=$(${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} show-zone ${domain.domain})
if [[ "x$DNSINFO" =~ "xNo such zone in the database" ]]; then
logger "zone ${domain.domain} does not exist in powerdns database"
elif [[ "x$DNSINFO" =~ "xZone is not actively secured" ]]; then
logger "securing zone ${domain.domain} in powerdns database"
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
elif [[ "x$DNSINFO" =~ "xNo keys for zone" ]]; then
logger "securing zone ${domain.domain} in powerdns database"
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} secure-zone ${domain.domain}
else
logger "not securing zone ${domain.domain} in powerdns database"
fi
${pkgs.powerdns}/bin/pdnsutil --config-dir=${pdns-config-dir} rectify-zone ${domain.domain}
'')
cfg.domains));
};
};
};
};
};
}

17
lib/lib/text.nix Normal file
View File

@ -0,0 +1,17 @@
{ pkgs, ... }:
with pkgs.lib;
let
get-basename = filename:
head (builtins.match "^[a-zA-Z0-9]+-(.+)$" (baseNameOf filename));
format-json-file = filename: pkgs.stdenv.mkDerivation {
name = "formatted-${get-basename filename}";
phases = [ "installPhase" ];
buildInputs = with pkgs; [ python ];
installPhase = "python -mjson.tool ${filename} > $out";
};
in {
inherit format-json-file;
}