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