1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfgs = config.services;
7 cfg = cfgs.ncdns;
8
9 dataDir = "/var/lib/ncdns";
10 username = "ncdns";
11
12 valueType = with types; oneOf [ int str bool path ]
13 // { description = "setting type (integer, string, bool or path)"; };
14
15 configType = with types; attrsOf (nullOr (either valueType configType))
16 // { description = ''
17 ncdns.conf configuration type. The format consists of an
18 attribute set of settings. Each setting can be either `null`,
19 a value or an attribute set. The allowed values are integers,
20 strings, booleans or paths.
21 '';
22 };
23
24 configFile = pkgs.runCommand "ncdns.conf"
25 { json = builtins.toJSON cfg.settings;
26 passAsFile = [ "json" ];
27 }
28 "${pkgs.remarshal}/bin/json2toml < $jsonPath > $out";
29
30 defaultFiles = {
31 public = "${dataDir}/bit.key";
32 private = "${dataDir}/bit.private";
33 zonePublic = "${dataDir}/bit-zone.key";
34 zonePrivate = "${dataDir}/bit-zone.private";
35 };
36
37 # if all keys are the default value
38 needsKeygen = all id (flip mapAttrsToList cfg.dnssec.keys
39 (n: v: v == getAttr n defaultFiles));
40
41 mkDefaultAttrs = mapAttrs (n: v: mkDefault v);
42
43in
44
45{
46
47 ###### interface
48
49 options = {
50
51 services.ncdns = {
52
53 enable = mkEnableOption ''
54 ncdns, a Go daemon to bridge Namecoin to DNS.
55 To resolve .bit domains set <literal>services.namecoind.enable = true;</literal>
56 and an RPC username/password
57 '';
58
59 address = mkOption {
60 type = types.str;
61 default = "127.0.0.1";
62 description = ''
63 The IP address the ncdns resolver will bind to. Leave this unchanged
64 if you do not wish to directly expose the resolver.
65 '';
66 };
67
68 port = mkOption {
69 type = types.port;
70 default = 5333;
71 description = ''
72 The port the ncdns resolver will bind to.
73 '';
74 };
75
76 identity.hostname = mkOption {
77 type = types.str;
78 default = config.networking.hostName;
79 example = "example.com";
80 description = ''
81 The hostname of this ncdns instance, which defaults to the machine
82 hostname. If specified, ncdns lists the hostname as an NS record at
83 the zone apex:
84 <programlisting>
85 bit. IN NS ns1.example.com.
86 </programlisting>
87 If unset ncdns will generate an internal psuedo-hostname under the
88 zone, which will resolve to the value of
89 <option>services.ncdns.identity.address</option>.
90 If you are only using ncdns locally you can ignore this.
91 '';
92 };
93
94 identity.hostmaster = mkOption {
95 type = types.str;
96 default = "";
97 example = "root@example.com";
98 description = ''
99 An email address for the SOA record at the bit zone.
100 If you are only using ncdns locally you can ignore this.
101 '';
102 };
103
104 identity.address = mkOption {
105 type = types.str;
106 default = "127.127.127.127";
107 description = ''
108 The IP address the hostname specified in
109 <option>services.ncdns.identity.hostname</option> should resolve to.
110 If you are only using ncdns locally you can ignore this.
111 '';
112 };
113
114 dnssec.enable = mkEnableOption ''
115 DNSSEC support in ncdns. This will generate KSK and ZSK keypairs
116 (unless provided via the options
117 <option>services.ncdns.dnssec.publicKey</option>,
118 <option>services.ncdns.dnssec.privateKey</option> etc.) and add a trust
119 anchor to recursive resolvers
120 '';
121
122 dnssec.keys.public = mkOption {
123 type = types.path;
124 default = defaultFiles.public;
125 description = ''
126 Path to the file containing the KSK public key.
127 The key can be generated using the <literal>dnssec-keygen</literal>
128 command, provided by the package <package>bind</package> as follows:
129 <programlisting>
130 $ dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
131 </programlisting>
132 '';
133 };
134
135 dnssec.keys.private = mkOption {
136 type = types.path;
137 default = defaultFiles.private;
138 description = ''
139 Path to the file containing the KSK private key.
140 '';
141 };
142
143 dnssec.keys.zonePublic = mkOption {
144 type = types.path;
145 default = defaultFiles.zonePublic;
146 description = ''
147 Path to the file containing the ZSK public key.
148 The key can be generated using the <literal>dnssec-keygen</literal>
149 command, provided by the package <package>bind</package> as follows:
150 <programlisting>
151 $ dnssec-keygen -a RSASHA256 -3 -b 2048 bit
152 </programlisting>
153 '';
154 };
155
156 dnssec.keys.zonePrivate = mkOption {
157 type = types.path;
158 default = defaultFiles.zonePrivate;
159 description = ''
160 Path to the file containing the ZSK private key.
161 '';
162 };
163
164 settings = mkOption {
165 type = configType;
166 default = { };
167 example = literalExample ''
168 { # enable webserver
169 ncdns.httplistenaddr = ":8202";
170
171 # synchronize TLS certs
172 certstore.nss = true;
173 # note: all paths are relative to the config file
174 certstore.nsscertdir = "../../var/lib/ncdns";
175 certstore.nssdbdir = "../../home/alice/.pki/nssdb";
176 }
177 '';
178 description = ''
179 ncdns settings. Use this option to configure ncds
180 settings not exposed in a NixOS option or to bypass one.
181 See the example ncdns.conf file at <link xlink:href="
182 https://git.io/JfX7g"/> for the available options.
183 '';
184 };
185
186 };
187
188 services.pdns-recursor.resolveNamecoin = mkOption {
189 type = types.bool;
190 default = false;
191 description = ''
192 Resolve <literal>.bit</literal> top-level domains using ncdns and namecoin.
193 '';
194 };
195
196 };
197
198
199 ###### implementation
200
201 config = mkIf cfg.enable {
202
203 services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
204 forwardZonesRecurse.bit = "127.0.0.1:${toString cfg.port}";
205 luaConfig =
206 if cfg.dnssec.enable
207 then ''readTrustAnchorsFromFile("${cfg.dnssec.keys.public}")''
208 else ''addNTA("bit", "namecoin DNSSEC disabled")'';
209 };
210
211 # Avoid pdns-recursor not finding the DNSSEC keys
212 systemd.services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
213 after = [ "ncdns.service" ];
214 wants = [ "ncdns.service" ];
215 };
216
217 services.ncdns.settings = mkDefaultAttrs {
218 ncdns =
219 { # Namecoin RPC
220 namecoinrpcaddress =
221 "${cfgs.namecoind.rpc.address}:${toString cfgs.namecoind.rpc.port}";
222 namecoinrpcusername = cfgs.namecoind.rpc.user;
223 namecoinrpcpassword = cfgs.namecoind.rpc.password;
224
225 # Identity
226 selfname = cfg.identity.hostname;
227 hostmaster = cfg.identity.hostmaster;
228 selfip = cfg.identity.address;
229
230 # Other
231 bind = "${cfg.address}:${toString cfg.port}";
232 }
233 // optionalAttrs cfg.dnssec.enable
234 { # DNSSEC
235 publickey = "../.." + cfg.dnssec.keys.public;
236 privatekey = "../.." + cfg.dnssec.keys.private;
237 zonepublickey = "../.." + cfg.dnssec.keys.zonePublic;
238 zoneprivatekey = "../.." + cfg.dnssec.keys.zonePrivate;
239 };
240
241 # Daemon
242 service.daemon = true;
243 xlog.journal = true;
244 };
245
246 users.users.ncdns = {
247 isSystemUser = true;
248 description = "ncdns daemon user";
249 };
250
251 systemd.services.ncdns = {
252 description = "ncdns daemon";
253 after = [ "namecoind.service" ];
254 wantedBy = [ "multi-user.target" ];
255
256 serviceConfig = {
257 User = "ncdns";
258 StateDirectory = "ncdns";
259 Restart = "on-failure";
260 ExecStart = "${pkgs.ncdns}/bin/ncdns -conf=${configFile}";
261 };
262
263 preStart = optionalString (cfg.dnssec.enable && needsKeygen) ''
264 cd ${dataDir}
265 if [ ! -e bit.key ]; then
266 ${pkgs.bind}/bin/dnssec-keygen -a RSASHA256 -3 -b 2048 bit
267 mv Kbit.*.key bit-zone.key
268 mv Kbit.*.private bit-zone.private
269 ${pkgs.bind}/bin/dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
270 mv Kbit.*.key bit.key
271 mv Kbit.*.private bit.private
272 fi
273 '';
274 };
275
276 };
277
278 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
279
280}