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 (lib.mdDoc ''
54 ncdns, a Go daemon to bridge Namecoin to DNS.
55 To resolve .bit domains set `services.namecoind.enable = true;`
56 and an RPC username/password
57 '');
58
59 address = mkOption {
60 type = types.str;
61 default = "[::1]";
62 description = lib.mdDoc ''
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 = lib.mdDoc ''
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 defaultText = literalExpression "config.networking.hostName";
80 example = "example.com";
81 description = lib.mdDoc ''
82 The hostname of this ncdns instance, which defaults to the machine
83 hostname. If specified, ncdns lists the hostname as an NS record at
84 the zone apex:
85 ```
86 bit. IN NS ns1.example.com.
87 ```
88 If unset ncdns will generate an internal pseudo-hostname under the
89 zone, which will resolve to the value of
90 {option}`services.ncdns.identity.address`.
91 If you are only using ncdns locally you can ignore this.
92 '';
93 };
94
95 identity.hostmaster = mkOption {
96 type = types.str;
97 default = "";
98 example = "root@example.com";
99 description = lib.mdDoc ''
100 An email address for the SOA record at the bit zone.
101 If you are only using ncdns locally you can ignore this.
102 '';
103 };
104
105 identity.address = mkOption {
106 type = types.str;
107 default = "127.127.127.127";
108 description = lib.mdDoc ''
109 The IP address the hostname specified in
110 {option}`services.ncdns.identity.hostname` should resolve to.
111 If you are only using ncdns locally you can ignore this.
112 '';
113 };
114
115 dnssec.enable = mkEnableOption (lib.mdDoc ''
116 DNSSEC support in ncdns. This will generate KSK and ZSK keypairs
117 (unless provided via the options
118 {option}`services.ncdns.dnssec.publicKey`,
119 {option}`services.ncdns.dnssec.privateKey` etc.) and add a trust
120 anchor to recursive resolvers
121 '');
122
123 dnssec.keys.public = mkOption {
124 type = types.path;
125 default = defaultFiles.public;
126 description = lib.mdDoc ''
127 Path to the file containing the KSK public key.
128 The key can be generated using the `dnssec-keygen`
129 command, provided by the package `bind` as follows:
130 ```
131 $ dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
132 ```
133 '';
134 };
135
136 dnssec.keys.private = mkOption {
137 type = types.path;
138 default = defaultFiles.private;
139 description = lib.mdDoc ''
140 Path to the file containing the KSK private key.
141 '';
142 };
143
144 dnssec.keys.zonePublic = mkOption {
145 type = types.path;
146 default = defaultFiles.zonePublic;
147 description = lib.mdDoc ''
148 Path to the file containing the ZSK public key.
149 The key can be generated using the `dnssec-keygen`
150 command, provided by the package `bind` as follows:
151 ```
152 $ dnssec-keygen -a RSASHA256 -3 -b 2048 bit
153 ```
154 '';
155 };
156
157 dnssec.keys.zonePrivate = mkOption {
158 type = types.path;
159 default = defaultFiles.zonePrivate;
160 description = lib.mdDoc ''
161 Path to the file containing the ZSK private key.
162 '';
163 };
164
165 settings = mkOption {
166 type = configType;
167 default = { };
168 example = literalExpression ''
169 { # enable webserver
170 ncdns.httplistenaddr = ":8202";
171
172 # synchronize TLS certs
173 certstore.nss = true;
174 # note: all paths are relative to the config file
175 certstore.nsscertdir = "../../var/lib/ncdns";
176 certstore.nssdbdir = "../../home/alice/.pki/nssdb";
177 }
178 '';
179 description = lib.mdDoc ''
180 ncdns settings. Use this option to configure ncds
181 settings not exposed in a NixOS option or to bypass one.
182 See the example ncdns.conf file at <https://github.com/namecoin/ncdns/blob/master/_doc/ncdns.conf.example>
183 for the available options.
184 '';
185 };
186
187 };
188
189 services.pdns-recursor.resolveNamecoin = mkOption {
190 type = types.bool;
191 default = false;
192 description = lib.mdDoc ''
193 Resolve `.bit` top-level domains using ncdns and namecoin.
194 '';
195 };
196
197 };
198
199
200 ###### implementation
201
202 config = mkIf cfg.enable {
203
204 services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
205 forwardZonesRecurse.bit = "${cfg.address}:${toString cfg.port}";
206 luaConfig =
207 if cfg.dnssec.enable
208 then ''readTrustAnchorsFromFile("${cfg.dnssec.keys.public}")''
209 else ''addNTA("bit", "namecoin DNSSEC disabled")'';
210 };
211
212 # Avoid pdns-recursor not finding the DNSSEC keys
213 systemd.services.pdns-recursor = mkIf cfgs.pdns-recursor.resolveNamecoin {
214 after = [ "ncdns.service" ];
215 wants = [ "ncdns.service" ];
216 };
217
218 services.ncdns.settings = mkDefaultAttrs {
219 ncdns =
220 { # Namecoin RPC
221 namecoinrpcaddress =
222 "${cfgs.namecoind.rpc.address}:${toString cfgs.namecoind.rpc.port}";
223 namecoinrpcusername = cfgs.namecoind.rpc.user;
224 namecoinrpcpassword = cfgs.namecoind.rpc.password;
225
226 # Identity
227 selfname = cfg.identity.hostname;
228 hostmaster = cfg.identity.hostmaster;
229 selfip = cfg.identity.address;
230
231 # Other
232 bind = "${cfg.address}:${toString cfg.port}";
233 }
234 // optionalAttrs cfg.dnssec.enable
235 { # DNSSEC
236 publickey = "../.." + cfg.dnssec.keys.public;
237 privatekey = "../.." + cfg.dnssec.keys.private;
238 zonepublickey = "../.." + cfg.dnssec.keys.zonePublic;
239 zoneprivatekey = "../.." + cfg.dnssec.keys.zonePrivate;
240 };
241
242 # Daemon
243 service.daemon = true;
244 xlog.journal = true;
245 };
246
247 users.users.ncdns = {
248 isSystemUser = true;
249 group = "ncdns";
250 description = "ncdns daemon user";
251 };
252 users.groups.ncdns = {};
253
254 systemd.services.ncdns = {
255 description = "ncdns daemon";
256 after = [ "namecoind.service" ];
257 wantedBy = [ "multi-user.target" ];
258
259 serviceConfig = {
260 User = "ncdns";
261 StateDirectory = "ncdns";
262 Restart = "on-failure";
263 ExecStart = "${pkgs.ncdns}/bin/ncdns -conf=${configFile}";
264 };
265
266 preStart = optionalString (cfg.dnssec.enable && needsKeygen) ''
267 cd ${dataDir}
268 if [ ! -e bit.key ]; then
269 ${pkgs.bind}/bin/dnssec-keygen -a RSASHA256 -3 -b 2048 bit
270 mv Kbit.*.key bit-zone.key
271 mv Kbit.*.private bit-zone.private
272 ${pkgs.bind}/bin/dnssec-keygen -a RSASHA256 -3 -b 2048 -f KSK bit
273 mv Kbit.*.key bit.key
274 mv Kbit.*.private bit.private
275 fi
276 '';
277 };
278
279 };
280
281 meta.maintainers = with lib.maintainers; [ rnhmjoj ];
282
283}