1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.i2pd;
8
9 homeDir = "/var/lib/i2pd";
10
11 mkEndpointOpt = name: addr: port: {
12 enable = mkEnableOption name;
13 name = mkOption {
14 type = types.str;
15 default = name;
16 description = "The endpoint name.";
17 };
18 address = mkOption {
19 type = types.str;
20 default = addr;
21 description = "Bind address for ${name} endpoint. Default: " + addr;
22 };
23 port = mkOption {
24 type = types.int;
25 default = port;
26 description = "Bind port for ${name} endoint. Default: " + toString port;
27 };
28 };
29
30 mkKeyedEndpointOpt = name: addr: port: keyFile:
31 (mkEndpointOpt name addr port) // {
32 keys = mkOption {
33 type = types.str;
34 default = "";
35 description = ''
36 File to persist ${lib.toUpper name} keys.
37 '';
38 };
39 };
40
41 commonTunOpts = let
42 i2cpOpts = {
43 length = mkOption {
44 type = types.int;
45 description = "Guaranteed minimum hops.";
46 default = 3;
47 };
48 quantity = mkOption {
49 type = types.int;
50 description = "Number of simultaneous tunnels.";
51 default = 5;
52 };
53 };
54 in name: {
55 outbound = i2cpOpts;
56 inbound = i2cpOpts;
57 crypto.tagsToSend = mkOption {
58 type = types.int;
59 description = "Number of ElGamal/AES tags to send.";
60 default = 40;
61 };
62 destination = mkOption {
63 type = types.str;
64 description = "Remote endpoint, I2P hostname or b32.i2p address.";
65 };
66 keys = mkOption {
67 type = types.str;
68 default = name + "-keys.dat";
69 description = "Keyset used for tunnel identity.";
70 };
71 } // mkEndpointOpt name "127.0.0.1" 0;
72
73 i2pdConf = pkgs.writeText "i2pd.conf" ''
74 # DO NOT EDIT -- this file has been generated automatically.
75 loglevel = ${cfg.logLevel}
76
77 ipv4 = ${boolToString cfg.enableIPv4}
78 ipv6 = ${boolToString cfg.enableIPv6}
79 notransit = ${boolToString cfg.notransit}
80 floodfill = ${boolToString cfg.floodfill}
81 netid = ${toString cfg.netid}
82 ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" }
83 ${if isNull cfg.port then "" else "port = ${toString cfg.port}"}
84
85 [limits]
86 transittunnels = ${toString cfg.limits.transittunnels}
87
88 [upnp]
89 enabled = ${boolToString cfg.upnp.enable}
90 name = ${cfg.upnp.name}
91
92 [precomputation]
93 elgamal = ${boolToString cfg.precomputation.elgamal}
94
95 [reseed]
96 verify = ${boolToString cfg.reseed.verify}
97 file = ${cfg.reseed.file}
98 urls = ${builtins.concatStringsSep "," cfg.reseed.urls}
99
100 [addressbook]
101 defaulturl = ${cfg.addressbook.defaulturl}
102 subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions}
103
104 ${flip concatMapStrings
105 (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto)
106 (proto: ''
107 [${proto.name}]
108 enabled = ${boolToString proto.enable}
109 address = ${proto.address}
110 port = ${toString proto.port}
111 ${if proto ? keys then "keys = ${proto.keys}" else ""}
112 ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""}
113 ${if proto ? user then "user = ${proto.user}" else ""}
114 ${if proto ? pass then "pass = ${proto.pass}" else ""}
115 ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""}
116 ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""}
117 '')
118 }
119 '';
120
121 i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" ''
122 # DO NOT EDIT -- this file has been generated automatically.
123 ${flip concatMapStrings
124 (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
125 (tun: ''
126 [${tun.name}]
127 type = client
128 destination = ${tun.destination}
129 destinationport = ${toString tun.destinationPort}
130 keys = ${tun.keys}
131 address = ${tun.address}
132 port = ${toString tun.port}
133 inbound.length = ${toString tun.inbound.length}
134 outbound.length = ${toString tun.outbound.length}
135 inbound.quantity = ${toString tun.inbound.quantity}
136 outbound.quantity = ${toString tun.outbound.quantity}
137 crypto.tagsToSend = ${toString tun.crypto.tagsToSend}
138 '')
139 }
140 ${flip concatMapStrings
141 (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
142 (tun: ''
143 [${tun.name}]
144 type = server
145 destination = ${tun.destination}
146 keys = ${tun.keys}
147 host = ${tun.address}
148 port = ${toString tun.port}
149 inport = ${toString tun.inPort}
150 accesslist = ${builtins.concatStringsSep "," tun.accessList}
151 '')
152 }
153 '';
154
155 i2pdSh = pkgs.writeScriptBin "i2pd" ''
156 #!/bin/sh
157 exec ${pkgs.i2pd}/bin/i2pd \
158 ${if isNull cfg.address then "" else "--host="+cfg.address} \
159 --conf=${i2pdConf} \
160 --tunconf=${i2pdTunnelConf}
161 '';
162
163in
164
165{
166
167 ###### interface
168
169 options = {
170
171 services.i2pd = {
172
173 enable = mkOption {
174 type = types.bool;
175 default = false;
176 description = ''
177 Enables I2Pd as a running service upon activation.
178 Please read http://i2pd.readthedocs.io/en/latest/ for further
179 configuration help.
180 '';
181 };
182
183 logLevel = mkOption {
184 type = types.enum ["debug" "info" "warn" "error"];
185 default = "error";
186 description = ''
187 The log level. <command>i2pd</command> defaults to "info"
188 but that generates copious amounts of log messages.
189
190 We default to "error" which is similar to the default log
191 level of <command>tor</command>.
192 '';
193 };
194
195 address = mkOption {
196 type = with types; nullOr str;
197 default = null;
198 description = ''
199 Your external IP or hostname.
200 '';
201 };
202
203 notransit = mkOption {
204 type = types.bool;
205 default = false;
206 description = ''
207 Tells the router to not accept transit tunnels during startup.
208 '';
209 };
210
211 floodfill = mkOption {
212 type = types.bool;
213 default = false;
214 description = ''
215 If the router is declared to be unreachable and needs introduction nodes.
216 '';
217 };
218
219 netid = mkOption {
220 type = types.int;
221 default = 2;
222 description = ''
223 I2P overlay netid.
224 '';
225 };
226
227 bandwidth = mkOption {
228 type = with types; nullOr int;
229 default = null;
230 description = ''
231 Set a router bandwidth limit integer in KBps.
232 If not set, <command>i2pd</command> defaults to 32KBps.
233 '';
234 };
235
236 port = mkOption {
237 type = with types; nullOr int;
238 default = null;
239 description = ''
240 I2P listen port. If no one is given the router will pick between 9111 and 30777.
241 '';
242 };
243
244 enableIPv4 = mkOption {
245 type = types.bool;
246 default = true;
247 description = ''
248 Enables IPv4 connectivity. Enabled by default.
249 '';
250 };
251
252 enableIPv6 = mkOption {
253 type = types.bool;
254 default = false;
255 description = ''
256 Enables IPv6 connectivity. Disabled by default.
257 '';
258 };
259
260 nat = mkOption {
261 type = types.bool;
262 default = true;
263 description = ''
264 Assume router is NATed. Enabled by default.
265 '';
266 };
267
268 upnp = {
269 enable = mkOption {
270 type = types.bool;
271 default = false;
272 description = ''
273 Enables UPnP.
274 '';
275 };
276
277 name = mkOption {
278 type = types.str;
279 default = "I2Pd";
280 description = ''
281 Name i2pd appears in UPnP forwardings list.
282 '';
283 };
284 };
285
286 precomputation.elgamal = mkOption {
287 type = types.bool;
288 default = true;
289 description = ''
290 Whenever to use precomputated tables for ElGamal.
291 <command>i2pd</command> defaults to <literal>false</literal>
292 to save 64M of memory (and looses some performance).
293
294 We default to <literal>true</literal> as that is what most
295 users want anyway.
296 '';
297 };
298
299 reseed = {
300 verify = mkOption {
301 type = types.bool;
302 default = false;
303 description = ''
304 Request SU3 signature verification
305 '';
306 };
307
308 file = mkOption {
309 type = types.str;
310 default = "";
311 description = ''
312 Full path to SU3 file to reseed from
313 '';
314 };
315
316 urls = mkOption {
317 type = with types; listOf str;
318 default = [
319 "https://reseed.i2p-project.de/"
320 "https://i2p.mooo.com/netDb/"
321 "https://netdb.i2p2.no/"
322 "https://us.reseed.i2p2.no:444/"
323 "https://uk.reseed.i2p2.no:444/"
324 "https://i2p.manas.ca:8443/"
325 ];
326 description = ''
327 Reseed URLs
328 '';
329 };
330 };
331
332 addressbook = {
333 defaulturl = mkOption {
334 type = types.str;
335 default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
336 description = ''
337 AddressBook subscription URL for initial setup
338 '';
339 };
340 subscriptions = mkOption {
341 type = with types; listOf str;
342 default = [
343 "http://inr.i2p/export/alive-hosts.txt"
344 "http://i2p-projekt.i2p/hosts.txt"
345 "http://stats.i2p/cgi-bin/newhosts.txt"
346 ];
347 description = ''
348 AddressBook subscription URLs
349 '';
350 };
351 };
352
353 limits.transittunnels = mkOption {
354 type = types.int;
355 default = 2500;
356 description = ''
357 Maximum number of active transit sessions
358 '';
359 };
360
361 proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
362 auth = mkOption {
363 type = types.bool;
364 default = false;
365 description = ''
366 Enable authentication for webconsole.
367 '';
368 };
369 user = mkOption {
370 type = types.str;
371 default = "i2pd";
372 description = ''
373 Username for webconsole access
374 '';
375 };
376 pass = mkOption {
377 type = types.str;
378 default = "i2pd";
379 description = ''
380 Password for webconsole access.
381 '';
382 };
383 };
384
385 proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "";
386 proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "")
387 // {
388 outproxy = mkOption {
389 type = types.str;
390 default = "127.0.0.1";
391 description = "Upstream outproxy bind address.";
392 };
393 outproxyPort = mkOption {
394 type = types.int;
395 default = 4444;
396 description = "Upstream outproxy bind port.";
397 };
398 };
399
400 proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656;
401 proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827;
402 proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654;
403 proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650;
404
405 outTunnels = mkOption {
406 default = {};
407 type = with types; loaOf (submodule (
408 { name, ... }: {
409 options = {
410 destinationPort = mkOption {
411 type = types.int;
412 default = 0;
413 description = "Connect to particular port at destination.";
414 };
415 } // commonTunOpts name;
416 config = {
417 name = mkDefault name;
418 };
419 }
420 ));
421 description = ''
422 Connect to someone as a client and establish a local accept endpoint
423 '';
424 };
425
426 inTunnels = mkOption {
427 default = {};
428 type = with types; loaOf (submodule (
429 { name, ... }: {
430 options = {
431 inPort = mkOption {
432 type = types.int;
433 default = 0;
434 description = "Service port. Default to the tunnel's listen port.";
435 };
436 accessList = mkOption {
437 type = with types; listOf str;
438 default = [];
439 description = "I2P nodes that are allowed to connect to this service.";
440 };
441 } // commonTunOpts name;
442 config = {
443 name = mkDefault name;
444 };
445 }
446 ));
447 description = ''
448 Serve something on I2P network at port and delegate requests to address inPort.
449 '';
450 };
451 };
452 };
453
454
455 ###### implementation
456
457 config = mkIf cfg.enable {
458
459 users.users.i2pd = {
460 group = "i2pd";
461 description = "I2Pd User";
462 home = homeDir;
463 createHome = true;
464 uid = config.ids.uids.i2pd;
465 };
466
467 users.groups.i2pd.gid = config.ids.gids.i2pd;
468
469 systemd.services.i2pd = {
470 description = "Minimal I2P router";
471 after = [ "network.target" ];
472 wantedBy = [ "multi-user.target" ];
473 serviceConfig =
474 {
475 User = "i2pd";
476 WorkingDirectory = homeDir;
477 Restart = "on-abort";
478 ExecStart = "${i2pdSh}/bin/i2pd";
479 };
480 };
481 };
482}