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