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 `services.namecoind.enable = true;`
56 and an RPC username/password
57 '';
58
59 address = mkOption {
60 type = types.str;
61 default = "[::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 defaultText = literalExpression "config.networking.hostName";
80 example = "example.com";
81 description = ''
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 = ''
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 = ''
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 ''
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 = ''
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 = ''
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 = ''
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 = ''
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 = ''
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 = ''
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}