1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6
7 cfg = config.services.i2pd;
8
9 homeDir = "/var/lib/i2pd";
10
11 strOpt = k: v: k + " = " + v;
12 boolOpt = k: v: k + " = " + boolToString v;
13 intOpt = k: v: k + " = " + toString v;
14 lstOpt = k: xs: k + " = " + concatStringsSep "," xs;
15 optionalNullString = o: s: optional (s != null) (strOpt o s);
16 optionalNullBool = o: b: optional (b != null) (boolOpt o b);
17 optionalNullInt = o: i: optional (i != null) (intOpt o i);
18 optionalEmptyList = o: l: optional ([] != l) (lstOpt o l);
19
20 mkEnableTrueOption = name: mkEnableOption name // { default = true; };
21
22 mkEndpointOpt = name: addr: port: {
23 enable = mkEnableOption name;
24 name = mkOption {
25 type = types.str;
26 default = name;
27 description = "The endpoint name.";
28 };
29 address = mkOption {
30 type = types.str;
31 default = addr;
32 description = "Bind address for ${name} endpoint.";
33 };
34 port = mkOption {
35 type = types.int;
36 default = port;
37 description = "Bind port for ${name} endoint.";
38 };
39 };
40
41 i2cpOpts = name: {
42 length = mkOption {
43 type = types.int;
44 description = "Guaranteed minimum hops for ${name} tunnels.";
45 default = 3;
46 };
47 quantity = mkOption {
48 type = types.int;
49 description = "Number of simultaneous ${name} tunnels.";
50 default = 5;
51 };
52 };
53
54 mkKeyedEndpointOpt = name: addr: port: keyloc:
55 (mkEndpointOpt name addr port) // {
56 keys = mkOption {
57 type = with types; nullOr str;
58 default = keyloc;
59 description = ''
60 File to persist ${lib.toUpper name} keys.
61 '';
62 };
63 inbound = i2cpOpts name;
64 outbound = i2cpOpts name;
65 latency.min = mkOption {
66 type = with types; nullOr int;
67 description = "Min latency for tunnels.";
68 default = null;
69 };
70 latency.max = mkOption {
71 type = with types; nullOr int;
72 description = "Max latency for tunnels.";
73 default = null;
74 };
75 };
76
77 commonTunOpts = name: {
78 outbound = i2cpOpts name;
79 inbound = i2cpOpts name;
80 crypto.tagsToSend = mkOption {
81 type = types.int;
82 description = "Number of ElGamal/AES tags to send.";
83 default = 40;
84 };
85 destination = mkOption {
86 type = types.str;
87 description = "Remote endpoint, I2P hostname or b32.i2p address.";
88 };
89 keys = mkOption {
90 type = types.str;
91 default = name + "-keys.dat";
92 description = "Keyset used for tunnel identity.";
93 };
94 } // mkEndpointOpt name "127.0.0.1" 0;
95
96 sec = name: "\n[" + name + "]";
97 notice = "# DO NOT EDIT -- this file has been generated automatically.";
98 i2pdConf = let
99 opts = [
100 notice
101 (strOpt "loglevel" cfg.logLevel)
102 (boolOpt "logclftime" cfg.logCLFTime)
103 (boolOpt "ipv4" cfg.enableIPv4)
104 (boolOpt "ipv6" cfg.enableIPv6)
105 (boolOpt "notransit" cfg.notransit)
106 (boolOpt "floodfill" cfg.floodfill)
107 (intOpt "netid" cfg.netid)
108 ] ++ (optionalNullInt "bandwidth" cfg.bandwidth)
109 ++ (optionalNullInt "port" cfg.port)
110 ++ (optionalNullString "family" cfg.family)
111 ++ (optionalNullString "datadir" cfg.dataDir)
112 ++ (optionalNullInt "share" cfg.share)
113 ++ (optionalNullBool "ssu" cfg.ssu)
114 ++ (optionalNullBool "ntcp" cfg.ntcp)
115 ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
116 ++ (optionalNullString "ifname" cfg.ifname)
117 ++ (optionalNullString "ifname4" cfg.ifname4)
118 ++ (optionalNullString "ifname6" cfg.ifname6)
119 ++ [
120 (sec "limits")
121 (intOpt "transittunnels" cfg.limits.transittunnels)
122 (intOpt "coresize" cfg.limits.coreSize)
123 (intOpt "openfiles" cfg.limits.openFiles)
124 (intOpt "ntcphard" cfg.limits.ntcpHard)
125 (intOpt "ntcpsoft" cfg.limits.ntcpSoft)
126 (intOpt "ntcpthreads" cfg.limits.ntcpThreads)
127 (sec "upnp")
128 (boolOpt "enabled" cfg.upnp.enable)
129 (sec "precomputation")
130 (boolOpt "elgamal" cfg.precomputation.elgamal)
131 (sec "reseed")
132 (boolOpt "verify" cfg.reseed.verify)
133 ] ++ (optionalNullString "file" cfg.reseed.file)
134 ++ (optionalEmptyList "urls" cfg.reseed.urls)
135 ++ (optionalNullString "floodfill" cfg.reseed.floodfill)
136 ++ (optionalNullString "zipfile" cfg.reseed.zipfile)
137 ++ (optionalNullString "proxy" cfg.reseed.proxy)
138 ++ [
139 (sec "trust")
140 (boolOpt "enabled" cfg.trust.enable)
141 (boolOpt "hidden" cfg.trust.hidden)
142 ] ++ (optionalEmptyList "routers" cfg.trust.routers)
143 ++ (optionalNullString "family" cfg.trust.family)
144 ++ [
145 (sec "websockets")
146 (boolOpt "enabled" cfg.websocket.enable)
147 (strOpt "address" cfg.websocket.address)
148 (intOpt "port" cfg.websocket.port)
149 (sec "exploratory")
150 (intOpt "inbound.length" cfg.exploratory.inbound.length)
151 (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
152 (intOpt "outbound.length" cfg.exploratory.outbound.length)
153 (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
154 (sec "ntcp2")
155 (boolOpt "enabled" cfg.ntcp2.enable)
156 (boolOpt "published" cfg.ntcp2.published)
157 (intOpt "port" cfg.ntcp2.port)
158 (sec "addressbook")
159 (strOpt "defaulturl" cfg.addressbook.defaulturl)
160 ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
161 ++ (flip map
162 (collect (proto: proto ? port && proto ? address) cfg.proto)
163 (proto: let protoOpts = [
164 (sec proto.name)
165 (boolOpt "enabled" proto.enable)
166 (strOpt "address" proto.address)
167 (intOpt "port" proto.port)
168 ] ++ (if proto ? keys then optionalNullString "keys" proto.keys else [])
169 ++ (if proto ? auth then optionalNullBool "auth" proto.auth else [])
170 ++ (if proto ? user then optionalNullString "user" proto.user else [])
171 ++ (if proto ? pass then optionalNullString "pass" proto.pass else [])
172 ++ (if proto ? strictHeaders then optionalNullBool "strictheaders" proto.strictHeaders else [])
173 ++ (if proto ? hostname then optionalNullString "hostname" proto.hostname else [])
174 ++ (if proto ? outproxy then optionalNullString "outproxy" proto.outproxy else [])
175 ++ (if proto ? outproxyPort then optionalNullInt "outproxyport" proto.outproxyPort else [])
176 ++ (if proto ? outproxyEnable then optionalNullBool "outproxy.enabled" proto.outproxyEnable else []);
177 in (concatStringsSep "\n" protoOpts)
178 ));
179 in
180 pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
181
182 tunnelConf = let opts = [
183 notice
184 (flip map
185 (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
186 (tun: let outTunOpts = [
187 (sec tun.name)
188 "type = client"
189 (intOpt "port" tun.port)
190 (strOpt "destination" tun.destination)
191 ] ++ (if tun ? destinationPort then optionalNullInt "destinationport" tun.destinationPort else [])
192 ++ (if tun ? keys then
193 optionalNullString "keys" tun.keys else [])
194 ++ (if tun ? address then
195 optionalNullString "address" tun.address else [])
196 ++ (if tun ? inbound.length then
197 optionalNullInt "inbound.length" tun.inbound.length else [])
198 ++ (if tun ? inbound.quantity then
199 optionalNullInt "inbound.quantity" tun.inbound.quantity else [])
200 ++ (if tun ? outbound.length then
201 optionalNullInt "outbound.length" tun.outbound.length else [])
202 ++ (if tun ? outbound.quantity then
203 optionalNullInt "outbound.quantity" tun.outbound.quantity else [])
204 ++ (if tun ? crypto.tagsToSend then
205 optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend else []);
206 in concatStringsSep "\n" outTunOpts))
207 (flip map
208 (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
209 (tun: let inTunOpts = [
210 (sec tun.name)
211 "type = server"
212 (intOpt "port" tun.port)
213 (strOpt "host" tun.address)
214 ] ++ (if tun ? destination then
215 optionalNullString "destination" tun.destination else [])
216 ++ (if tun ? keys then
217 optionalNullString "keys" tun.keys else [])
218 ++ (if tun ? inPort then
219 optionalNullInt "inport" tun.inPort else [])
220 ++ (if tun ? accessList then
221 optionalEmptyList "accesslist" tun.accessList else []);
222 in concatStringsSep "\n" inTunOpts))];
223 in pkgs.writeText "i2pd-tunnels.conf" opts;
224
225 i2pdSh = pkgs.writeScriptBin "i2pd" ''
226 #!/bin/sh
227 exec ${pkgs.i2pd}/bin/i2pd \
228 ${if cfg.address == null then "" else "--host="+cfg.address} \
229 --service \
230 --conf=${i2pdConf} \
231 --tunconf=${tunnelConf}
232 '';
233
234in
235
236{
237
238 imports = [
239 (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
240 ];
241
242 ###### interface
243
244 options = {
245
246 services.i2pd = {
247
248 enable = mkEnableOption "I2Pd daemon" // {
249 description = ''
250 Enables I2Pd as a running service upon activation.
251 Please read http://i2pd.readthedocs.io/en/latest/ for further
252 configuration help.
253 '';
254 };
255
256 logLevel = mkOption {
257 type = types.enum ["debug" "info" "warn" "error"];
258 default = "error";
259 description = ''
260 The log level. <command>i2pd</command> defaults to "info"
261 but that generates copious amounts of log messages.
262
263 We default to "error" which is similar to the default log
264 level of <command>tor</command>.
265 '';
266 };
267
268 logCLFTime = mkEnableOption "Full CLF-formatted date and time to log";
269
270 address = mkOption {
271 type = with types; nullOr str;
272 default = null;
273 description = ''
274 Your external IP or hostname.
275 '';
276 };
277
278 family = mkOption {
279 type = with types; nullOr str;
280 default = null;
281 description = ''
282 Specify a family the router belongs to.
283 '';
284 };
285
286 dataDir = mkOption {
287 type = with types; nullOr str;
288 default = null;
289 description = ''
290 Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
291 '';
292 };
293
294 share = mkOption {
295 type = types.int;
296 default = 100;
297 description = ''
298 Limit of transit traffic from max bandwidth in percents.
299 '';
300 };
301
302 ifname = mkOption {
303 type = with types; nullOr str;
304 default = null;
305 description = ''
306 Network interface to bind to.
307 '';
308 };
309
310 ifname4 = mkOption {
311 type = with types; nullOr str;
312 default = null;
313 description = ''
314 IPv4 interface to bind to.
315 '';
316 };
317
318 ifname6 = mkOption {
319 type = with types; nullOr str;
320 default = null;
321 description = ''
322 IPv6 interface to bind to.
323 '';
324 };
325
326 ntcpProxy = mkOption {
327 type = with types; nullOr str;
328 default = null;
329 description = ''
330 Proxy URL for NTCP transport.
331 '';
332 };
333
334 ntcp = mkEnableTrueOption "ntcp";
335 ssu = mkEnableTrueOption "ssu";
336
337 notransit = mkEnableOption "notransit" // {
338 description = ''
339 Tells the router to not accept transit tunnels during startup.
340 '';
341 };
342
343 floodfill = mkEnableOption "floodfill" // {
344 description = ''
345 If the router is declared to be unreachable and needs introduction nodes.
346 '';
347 };
348
349 netid = mkOption {
350 type = types.int;
351 default = 2;
352 description = ''
353 I2P overlay netid.
354 '';
355 };
356
357 bandwidth = mkOption {
358 type = with types; nullOr int;
359 default = null;
360 description = ''
361 Set a router bandwidth limit integer in KBps.
362 If not set, <command>i2pd</command> defaults to 32KBps.
363 '';
364 };
365
366 port = mkOption {
367 type = with types; nullOr int;
368 default = null;
369 description = ''
370 I2P listen port. If no one is given the router will pick between 9111 and 30777.
371 '';
372 };
373
374 enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
375 enableIPv6 = mkEnableOption "IPv6 connectivity";
376 nat = mkEnableTrueOption "NAT bypass";
377
378 upnp.enable = mkEnableOption "UPnP service discovery";
379 upnp.name = mkOption {
380 type = types.str;
381 default = "I2Pd";
382 description = ''
383 Name i2pd appears in UPnP forwardings list.
384 '';
385 };
386
387 precomputation.elgamal = mkEnableTrueOption "Precomputed ElGamal tables" // {
388 description = ''
389 Whenever to use precomputated tables for ElGamal.
390 <command>i2pd</command> defaults to <literal>false</literal>
391 to save 64M of memory (and looses some performance).
392
393 We default to <literal>true</literal> as that is what most
394 users want anyway.
395 '';
396 };
397
398 reseed.verify = mkEnableOption "SU3 signature verification";
399
400 reseed.file = mkOption {
401 type = with types; nullOr str;
402 default = null;
403 description = ''
404 Full path to SU3 file to reseed from.
405 '';
406 };
407
408 reseed.urls = mkOption {
409 type = with types; listOf str;
410 default = [];
411 description = ''
412 Reseed URLs.
413 '';
414 };
415
416 reseed.floodfill = mkOption {
417 type = with types; nullOr str;
418 default = null;
419 description = ''
420 Path to router info of floodfill to reseed from.
421 '';
422 };
423
424 reseed.zipfile = mkOption {
425 type = with types; nullOr str;
426 default = null;
427 description = ''
428 Path to local .zip file to reseed from.
429 '';
430 };
431
432 reseed.proxy = mkOption {
433 type = with types; nullOr str;
434 default = null;
435 description = ''
436 URL for reseed proxy, supports http/socks.
437 '';
438 };
439
440 addressbook.defaulturl = mkOption {
441 type = types.str;
442 default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
443 description = ''
444 AddressBook subscription URL for initial setup
445 '';
446 };
447 addressbook.subscriptions = mkOption {
448 type = with types; listOf str;
449 default = [
450 "http://inr.i2p/export/alive-hosts.txt"
451 "http://i2p-projekt.i2p/hosts.txt"
452 "http://stats.i2p/cgi-bin/newhosts.txt"
453 ];
454 description = ''
455 AddressBook subscription URLs
456 '';
457 };
458
459 trust.enable = mkEnableOption "Explicit trust options";
460
461 trust.family = mkOption {
462 type = with types; nullOr str;
463 default = null;
464 description = ''
465 Router Familiy to trust for first hops.
466 '';
467 };
468
469 trust.routers = mkOption {
470 type = with types; listOf str;
471 default = [];
472 description = ''
473 Only connect to the listed routers.
474 '';
475 };
476
477 trust.hidden = mkEnableOption "Router concealment";
478
479 websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
480
481 exploratory.inbound = i2cpOpts "exploratory";
482 exploratory.outbound = i2cpOpts "exploratory";
483
484 ntcp2.enable = mkEnableTrueOption "NTCP2.";
485 ntcp2.published = mkEnableOption "NTCP2 publication";
486 ntcp2.port = mkOption {
487 type = types.int;
488 default = 0;
489 description = ''
490 Port to listen for incoming NTCP2 connections (0=auto).
491 '';
492 };
493
494 limits.transittunnels = mkOption {
495 type = types.int;
496 default = 2500;
497 description = ''
498 Maximum number of active transit sessions.
499 '';
500 };
501
502 limits.coreSize = mkOption {
503 type = types.int;
504 default = 0;
505 description = ''
506 Maximum size of corefile in Kb (0 - use system limit).
507 '';
508 };
509
510 limits.openFiles = mkOption {
511 type = types.int;
512 default = 0;
513 description = ''
514 Maximum number of open files (0 - use system default).
515 '';
516 };
517
518 limits.ntcpHard = mkOption {
519 type = types.int;
520 default = 0;
521 description = ''
522 Maximum number of active transit sessions.
523 '';
524 };
525
526 limits.ntcpSoft = mkOption {
527 type = types.int;
528 default = 0;
529 description = ''
530 Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
531 '';
532 };
533
534 limits.ntcpThreads = mkOption {
535 type = types.int;
536 default = 1;
537 description = ''
538 Maximum number of threads used by NTCP DH worker.
539 '';
540 };
541
542 proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
543
544 auth = mkEnableOption "Webconsole authentication";
545
546 user = mkOption {
547 type = types.str;
548 default = "i2pd";
549 description = ''
550 Username for webconsole access
551 '';
552 };
553
554 pass = mkOption {
555 type = types.str;
556 default = "i2pd";
557 description = ''
558 Password for webconsole access.
559 '';
560 };
561
562 strictHeaders = mkOption {
563 type = with types; nullOr bool;
564 default = null;
565 description = ''
566 Enable strict host checking on WebUI.
567 '';
568 };
569
570 hostname = mkOption {
571 type = with types; nullOr str;
572 default = null;
573 description = ''
574 Expected hostname for WebUI.
575 '';
576 };
577 };
578
579 proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
580 // {
581 outproxy = mkOption {
582 type = with types; nullOr str;
583 default = null;
584 description = "Upstream outproxy bind address.";
585 };
586 };
587 proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
588 // {
589 outproxyEnable = mkEnableOption "SOCKS outproxy";
590 outproxy = mkOption {
591 type = types.str;
592 default = "127.0.0.1";
593 description = "Upstream outproxy bind address.";
594 };
595 outproxyPort = mkOption {
596 type = types.int;
597 default = 4444;
598 description = "Upstream outproxy bind port.";
599 };
600 };
601
602 proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656;
603 proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827;
604 proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654;
605 proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650;
606
607 outTunnels = mkOption {
608 default = {};
609 type = with types; attrsOf (submodule (
610 { name, ... }: {
611 options = {
612 destinationPort = mkOption {
613 type = with types; nullOr int;
614 default = null;
615 description = "Connect to particular port at destination.";
616 };
617 } // commonTunOpts name;
618 config = {
619 name = mkDefault name;
620 };
621 }
622 ));
623 description = ''
624 Connect to someone as a client and establish a local accept endpoint
625 '';
626 };
627
628 inTunnels = mkOption {
629 default = {};
630 type = with types; attrsOf (submodule (
631 { name, ... }: {
632 options = {
633 inPort = mkOption {
634 type = types.int;
635 default = 0;
636 description = "Service port. Default to the tunnel's listen port.";
637 };
638 accessList = mkOption {
639 type = with types; listOf str;
640 default = [];
641 description = "I2P nodes that are allowed to connect to this service.";
642 };
643 } // commonTunOpts name;
644 config = {
645 name = mkDefault name;
646 };
647 }
648 ));
649 description = ''
650 Serve something on I2P network at port and delegate requests to address inPort.
651 '';
652 };
653 };
654 };
655
656
657 ###### implementation
658
659 config = mkIf cfg.enable {
660
661 users.users.i2pd = {
662 group = "i2pd";
663 description = "I2Pd User";
664 home = homeDir;
665 createHome = true;
666 uid = config.ids.uids.i2pd;
667 };
668
669 users.groups.i2pd.gid = config.ids.gids.i2pd;
670
671 systemd.services.i2pd = {
672 description = "Minimal I2P router";
673 after = [ "network.target" ];
674 wantedBy = [ "multi-user.target" ];
675 serviceConfig =
676 {
677 User = "i2pd";
678 WorkingDirectory = homeDir;
679 Restart = "on-abort";
680 ExecStart = "${i2pdSh}/bin/i2pd";
681 };
682 };
683 };
684}