{ config, lib, pkgs, ... }: let cfg = config.services.trust-dns; toml = pkgs.formats.toml { }; configFile = toml.generate "trust-dns.toml" ( lib.filterAttrsRecursive (_: v: v != null) cfg.settings ); zoneType = lib.types.submodule ({ config, ... }: { options = with lib; { zone = mkOption { type = types.str; description = '' Zone name, like "example.com", "localhost", or "0.0.127.in-addr.arpa". ''; }; zone_type = mkOption { type = types.enum [ "Primary" "Secondary" "Hint" "Forward" ]; default = "Primary"; description = '' One of: - "Primary" (the master, authority for the zone). - "Secondary" (the slave, replicated from the primary). - "Hint" (a cached zone with recursive resolver abilities). - "Forward" (a cached zone where all requests are forwarded to another resolver). For more details about these zone types, consult the documentation for BIND, though note that trust-dns supports only a subset of BIND's zone types: ''; }; file = mkOption { type = types.either types.path types.str; default = "${config.zone}.zone"; defaultText = literalExpression ''"''${config.zone}.zone"''; description = '' Path to the .zone file. If not fully-qualified, this path will be interpreted relative to the `directory` option. If omitted, defaults to the value of the `zone` option suffixed with ".zone". ''; }; }; }); in { meta.maintainers = with lib.maintainers; [ colinsane ]; options = { services.trust-dns = with lib; { enable = mkEnableOption "trust-dns"; package = mkPackageOption pkgs "trust-dns" { extraDescription = '' ::: {.note} The package must provide `meta.mainProgram` which names the server binayr; any other utilities (client, resolver) are not needed. ::: ''; }; quiet = mkOption { type = types.bool; default = false; description = '' Log ERROR level messages only. This option is mutually exclusive with the `debug` option. If neither `quiet` nor `debug` are enabled, logging defaults to the INFO level. ''; }; debug = mkOption { type = types.bool; default = false; description = '' Log DEBUG, INFO, WARN and ERROR messages. This option is mutually exclusive with the `debug` option. If neither `quiet` nor `debug` are enabled, logging defaults to the INFO level. ''; }; settings = mkOption { description = '' Settings for trust-dns. The options enumerated here are not exhaustive. Refer to upstream documentation for all available options: - [Example settings](https://github.com/bluejekyll/trust-dns/blob/main/tests/test-data/test_configs/example.toml) ''; type = types.submodule { freeformType = toml.type; options = { listen_addrs_ipv4 = mkOption { type = types.listOf types.str; default = [ "0.0.0.0" ]; description = '' List of ipv4 addresses on which to listen for DNS queries. ''; }; listen_addrs_ipv6 = mkOption { type = types.listOf types.str; default = lib.optional config.networking.enableIPv6 "::0"; defaultText = literalExpression ''lib.optional config.networking.enableIPv6 "::0"''; description = '' List of ipv6 addresses on which to listen for DNS queries. ''; }; listen_port = mkOption { type = types.port; default = 53; description = '' Port to listen on (applies to all listen addresses). ''; }; directory = mkOption { type = types.str; default = "/var/lib/trust-dns"; description = '' The directory in which trust-dns should look for .zone files, whenever zones aren't specified by absolute path. ''; }; zones = mkOption { description = "List of zones to serve."; default = {}; type = types.listOf (types.coercedTo types.str (zone: { inherit zone; }) zoneType); }; }; }; }; }; }; config = lib.mkIf cfg.enable { systemd.services.trust-dns = { description = "trust-dns Domain Name Server"; unitConfig.Documentation = "https://trust-dns.org/"; serviceConfig = { ExecStart = let flags = (lib.optional cfg.debug "--debug") ++ (lib.optional cfg.quiet "--quiet"); flagsStr = builtins.concatStringsSep " " flags; in '' ${cfg.package}/bin/${cfg.package.meta.mainProgram} --config ${configFile} ${flagsStr} ''; Type = "simple"; Restart = "on-failure"; RestartSec = "10s"; DynamicUser = true; StateDirectory = "trust-dns"; ReadWritePaths = [ cfg.settings.directory ]; AmbientCapabilities = [ "CAP_NET_BIND_SERVICE" ]; CapabilityBoundingSet = [ "CAP_NET_BIND_SERVICE" ]; LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateDevices = true; PrivateMounts = true; PrivateTmp = true; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectSystem = "full"; RemoveIPC = true; RestrictAddressFamilies = [ "AF_INET AF_INET6" ]; RestrictNamespaces = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@privileged" "~@resources" ]; }; after = [ "network.target" ]; wantedBy = [ "multi-user.target" ]; }; }; }