Initial framework: reusable modules, lib, pkgs, overlays, scripts, sample environment
This commit is contained in:
@@ -0,0 +1,116 @@
|
||||
{ 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" ];
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
{ config, lib, ... }:
|
||||
|
||||
let
|
||||
inherit (lib) types mkOption;
|
||||
in
|
||||
|
||||
{
|
||||
options.services.dns = {
|
||||
enable = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Enable the Technitium DNS Server";
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = null;
|
||||
description = "Technitium DNS Server package to use. Defaults to pkgs.technitium-dns-server.";
|
||||
};
|
||||
|
||||
webPort = mkOption {
|
||||
type = types.port;
|
||||
default = 5380;
|
||||
description = "HTTP port for the Technitium web administration interface";
|
||||
};
|
||||
|
||||
dnsPort = mkOption {
|
||||
type = types.port;
|
||||
default = 53;
|
||||
description = "DNS server port (both TCP and UDP)";
|
||||
};
|
||||
|
||||
recursion = mkOption {
|
||||
type = types.enum [ "AllowOnlyForPrivateNetworks" "AllowAll" "DenyAll" ];
|
||||
default = "AllowOnlyForPrivateNetworks";
|
||||
description = "Recursion policy for DNS queries";
|
||||
};
|
||||
|
||||
forwarders = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "Upstream DNS forwarders (e.g. [ \"1.1.1.1\" \"8.8.8.8\" ]). Empty means use root hints";
|
||||
};
|
||||
|
||||
configDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/etc/dns";
|
||||
description = "Directory for persistent Technitium DNS configuration and zone data";
|
||||
};
|
||||
|
||||
adminPasswordFile = mkOption {
|
||||
type = types.nullOr types.str;
|
||||
default = null;
|
||||
description = ''
|
||||
Path to a file containing the admin password for the web interface.
|
||||
If not set, the default credentials (admin/admin) are used.
|
||||
Use agenix or sops-nix to provide this file securely.
|
||||
'';
|
||||
};
|
||||
|
||||
listenAddresses = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "IP addresses to listen on. Empty means listen on all interfaces";
|
||||
};
|
||||
|
||||
allowZoneTransfer = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [ ];
|
||||
description = "IP addresses or subnets allowed to request zone transfers (AXFR/IXFR)";
|
||||
};
|
||||
|
||||
extraConfig = mkOption {
|
||||
type = types.attrsOf types.anything;
|
||||
default = { };
|
||||
description = "Additional Technitium DNS configuration options as an attribute set";
|
||||
};
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user