Files

116 lines
3.8 KiB
Nix

{ config, pkgs, lib, ... }:
let
cfg = config.services.dns;
dnsPkg = if cfg.package != null then cfg.package else pkgs.technitium-dns-server;
# Build the config.json for Technitium DNS Server.
# The server reads this file on startup from its config directory.
configJson = {
WebServicePort = cfg.webPort;
DNSListenerPort = cfg.dnsPort;
Recursion = cfg.recursion;
Forwarders = cfg.forwarders;
Log = false;
CachePrefetch = false;
AllowTtlOverride = true;
} // lib.optionalAttrs (cfg.adminPasswordFile != null) {
# Password hash will be set by the activation script on first run
# using the value from adminPasswordFile.
} // lib.optionalAttrs (cfg.listenAddresses != [ ]) {
ListenAddresses = cfg.listenAddresses;
} // lib.optionalAttrs (cfg.allowZoneTransfer != [ ]) {
AllowZoneTransfer = cfg.allowZoneTransfer;
} // cfg.extraConfig;
configFile = pkgs.writeText "technitium-dns-config.json"
(builtins.toJSON configJson);
in
{
imports = [
./options.nix
];
config = lib.mkIf cfg.enable {
environment.systemPackages = [ dnsPkg ];
# Create the config directory and deploy initial config.json
systemd.tmpfiles.rules = [
"d ${cfg.configDir} 0750 dns dns - -"
];
systemd.services.technitium-dns-server = {
description = "Technitium DNS Server";
wantedBy = [ "multi-user.target" ];
after = [ "network.target" ];
wants = [ "network-online.target" ];
# Generate a password hash if adminPasswordFile is provided.
# The server is stopped on first run if no password hash exists,
# so we pre-seed the config with the hashed password.
preStart = ''
if [ -f "${cfg.configDir}/config.json" ]; then
# Config already exists, do not overwrite
true
else
install -m 0640 ${configFile} ${cfg.configDir}/config.json
${lib.optionalString (cfg.adminPasswordFile != null) ''
if [ -f "${cfg.adminPasswordFile}" ]; then
# .NET-compatible SHA256 hash of the password
PASSWORD=$(cat "${cfg.adminPasswordFile}" | tr -d '\n')
HASH=$(echo -n "$PASSWORD" | ${pkgs.openssl}/bin/openssl dgst -sha256 -hex | cut -d' ' -f2)
${pkgs.jq}/bin/jq \
".AdminPassword = \"$HASH\" | .Pbkdf2Iterations = 600000" \
${cfg.configDir}/config.json > ${cfg.configDir}/config.json.tmp
mv ${cfg.configDir}/config.json.tmp ${cfg.configDir}/config.json
fi
''}
fi
'';
serviceConfig = {
Type = "simple";
ExecStart = "${dnsPkg}/bin/technitium-dns-server ${cfg.configDir}";
User = "dns";
Group = "dns";
Restart = "on-failure";
RestartSec = "5s";
LimitNOFILE = 1048576;
# Protect the system
ProtectSystem = "full";
ProtectHome = true;
PrivateTmp = true;
NoNewPrivileges = true;
ReadWritePaths = [ cfg.configDir ];
};
};
# Create the dns system user and group
users.users.dns = {
description = "Technitium DNS Server daemon user";
group = "dns";
isSystemUser = true;
home = cfg.configDir;
createHome = true;
};
users.groups.dns = { };
# Open firewall ports for DNS (UDP/TCP 53) and optionally the web interface
networking.firewall = lib.mkMerge [
{
allowedTCPPorts = [ cfg.dnsPort ];
allowedUDPPorts = [ cfg.dnsPort ];
}
# Allow web admin access only if listenAddresses restricts it to localhost
(lib.mkIf (cfg.listenAddresses == [ ] || builtins.elem "127.0.0.1" cfg.listenAddresses) {
allowedTCPPorts = [ cfg.webPort ];
})
];
# Ensure DNS resolution is available locally before starting
networking.nameservers = lib.mkAfter [ "127.0.0.1" ];
};
}