Merge #361424: refactor lib.packagesFromDirectoryRecursive (v2)
[NixPkgs.git] / nixos / modules / services / networking / kresd.nix
blobc93b591701a73bb6b3fa8ee88acfd5df9944802e
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   cfg = config.services.kresd;
10   # Convert systemd-style address specification to kresd config line(s).
11   # On Nix level we don't attempt to precisely validate the address specifications.
12   # The optional IPv6 scope spec comes *after* port, perhaps surprisingly.
13   mkListen =
14     kind: addr:
15     let
16       al_v4 = builtins.match "([0-9.]+):([0-9]+)($)" addr;
17       al_v6 = builtins.match "\\[(.+)]:([0-9]+)(%.*|$)" addr;
18       al_portOnly = builtins.match "(^)([0-9]+)" addr;
19       al =
20         lib.findFirst (a: a != null) (throw "services.kresd.*: incorrect address specification '${addr}'")
21           [
22             al_v4
23             al_v6
24             al_portOnly
25           ];
26       port = lib.elemAt al 1;
27       addrSpec =
28         if al_portOnly == null then "'${lib.head al}${lib.elemAt al 2}'" else "{'::', '0.0.0.0'}";
29     in
30     # freebind is set for compatibility with earlier kresd services;
31     # it could be configurable, for example.
32     ''
33       net.listen(${addrSpec}, ${port}, { kind = '${kind}', freebind = true })
34     '';
36   configFile = pkgs.writeText "kresd.conf" (
37     ""
38     + lib.concatMapStrings (mkListen "dns") cfg.listenPlain
39     + lib.concatMapStrings (mkListen "tls") cfg.listenTLS
40     + lib.concatMapStrings (mkListen "doh2") cfg.listenDoH
41     + cfg.extraConfig
42   );
45   meta.maintainers = [
46     lib.maintainers.vcunat # upstream developer
47   ];
49   imports = [
50     (lib.mkChangedOptionModule [ "services" "kresd" "interfaces" ] [ "services" "kresd" "listenPlain" ]
51       (
52         config:
53         let
54           value = lib.getAttrFromPath [ "services" "kresd" "interfaces" ] config;
55         in
56         map (iface: if lib.elem ":" (lib.stringToCharacters iface) then "[${iface}]:53" else "${iface}:53") # Syntax depends on being IPv6 or IPv4.
57           value
58       )
59     )
60     (lib.mkRemovedOptionModule [ "services" "kresd" "cacheDir" ] "Please use (bind-)mounting instead.")
61   ];
63   ###### interface
64   options.services.kresd = {
65     enable = lib.mkOption {
66       type = lib.types.bool;
67       default = false;
68       description = ''
69         Whether to enable knot-resolver domain name server.
70         DNSSEC validation is turned on by default.
71         You can run `sudo nc -U /run/knot-resolver/control/1`
72         and give commands interactively to kresd@1.service.
73       '';
74     };
75     package = lib.mkPackageOption pkgs "knot-resolver" {
76       example = "knot-resolver.override { extraFeatures = true; }";
77     };
78     extraConfig = lib.mkOption {
79       type = lib.types.lines;
80       default = "";
81       description = ''
82         Extra lines to be added verbatim to the generated configuration file.
83         See upstream documentation <https://www.knot-resolver.cz/documentation/stable/config-overview.html> for more details.
84       '';
85     };
86     listenPlain = lib.mkOption {
87       type = with lib.types; listOf str;
88       default = [
89         "[::1]:53"
90         "127.0.0.1:53"
91       ];
92       example = [ "53" ];
93       description = ''
94         What addresses and ports the server should listen on.
95         For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
96       '';
97     };
98     listenTLS = lib.mkOption {
99       type = with lib.types; listOf str;
100       default = [ ];
101       example = [
102         "198.51.100.1:853"
103         "[2001:db8::1]:853"
104         "853"
105       ];
106       description = ''
107         Addresses and ports on which kresd should provide DNS over TLS (see RFC 7858).
108         For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
109       '';
110     };
111     listenDoH = lib.mkOption {
112       type = with lib.types; listOf str;
113       default = [ ];
114       example = [
115         "198.51.100.1:443"
116         "[2001:db8::1]:443"
117         "443"
118       ];
119       description = ''
120         Addresses and ports on which kresd should provide DNS over HTTPS/2 (see RFC 8484).
121         For detailed syntax see ListenStream in {manpage}`systemd.socket(5)`.
122       '';
123     };
124     instances = lib.mkOption {
125       type = lib.types.ints.unsigned;
126       default = 1;
127       description = ''
128         The number of instances to start.  They will be called kresd@{1,2,...}.service.
129         Knot Resolver uses no threads, so this is the way to scale.
130         You can dynamically start/stop them at will, so this is just system default.
131       '';
132     };
133     # TODO: perhaps options for more common stuff like cache size or forwarding
134   };
136   ###### implementation
137   config = lib.mkIf cfg.enable {
138     environment.etc."knot-resolver/kresd.conf".source = configFile; # not required
140     networking.resolvconf.useLocalResolver = lib.mkDefault true;
142     users.users.knot-resolver = {
143       isSystemUser = true;
144       group = "knot-resolver";
145       description = "Knot-resolver daemon user";
146     };
147     users.groups.knot-resolver.gid = null;
149     systemd.packages = [ cfg.package ]; # the units are patched inside the package a bit
151     systemd.targets.kresd = {
152       # configure units started by default
153       wantedBy = [ "multi-user.target" ];
154       wants = [
155         "kres-cache-gc.service"
156       ] ++ map (i: "kresd@${toString i}.service") (lib.range 1 cfg.instances);
157     };
158     systemd.services."kresd@".serviceConfig = {
159       ExecStart =
160         "${cfg.package}/bin/kresd --noninteractive "
161         + "-c ${cfg.package}/lib/knot-resolver/distro-preconfig.lua -c ${configFile}";
162       # Ensure /run/knot-resolver exists
163       RuntimeDirectory = "knot-resolver";
164       RuntimeDirectoryMode = "0770";
165       # Ensure /var/lib/knot-resolver exists
166       StateDirectory = "knot-resolver";
167       StateDirectoryMode = "0770";
168       # Ensure /var/cache/knot-resolver exists
169       CacheDirectory = "knot-resolver";
170       CacheDirectoryMode = "0770";
171     };
172     # We don't mind running stop phase from wrong version.  It seems less racy.
173     systemd.services."kresd@".stopIfChanged = false;
174   };