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 notice
127 (strOpt "loglevel" cfg.logLevel)
128 (boolOpt "logclftime" cfg.logCLFTime)
129 (boolOpt "ipv4" cfg.enableIPv4)
130 (boolOpt "ipv6" cfg.enableIPv6)
131 (boolOpt "notransit" cfg.notransit)
132 (boolOpt "floodfill" cfg.floodfill)
133 (intOpt "netid" cfg.netid)
134 ]
135 ++ (optionalNullInt "bandwidth" cfg.bandwidth)
136 ++ (optionalNullInt "port" cfg.port)
137 ++ (optionalNullString "family" cfg.family)
138 ++ (optionalNullString "datadir" cfg.dataDir)
139 ++ (optionalNullInt "share" cfg.share)
140 ++ (optionalNullBool "ssu" cfg.ssu)
141 ++ (optionalNullBool "ntcp" cfg.ntcp)
142 ++ (optionalNullString "ntcpproxy" cfg.ntcpProxy)
143 ++ (optionalNullString "ifname" cfg.ifname)
144 ++ (optionalNullString "ifname4" cfg.ifname4)
145 ++ (optionalNullString "ifname6" cfg.ifname6)
146 ++ [
147 (sec "limits")
148 (intOpt "transittunnels" cfg.limits.transittunnels)
149 (intOpt "coresize" cfg.limits.coreSize)
150 (intOpt "openfiles" cfg.limits.openFiles)
151 (intOpt "ntcphard" cfg.limits.ntcpHard)
152 (intOpt "ntcpsoft" cfg.limits.ntcpSoft)
153 (intOpt "ntcpthreads" cfg.limits.ntcpThreads)
154 (sec "upnp")
155 (boolOpt "enabled" cfg.upnp.enable)
156 (sec "precomputation")
157 (boolOpt "elgamal" cfg.precomputation.elgamal)
158 (sec "reseed")
159 (boolOpt "verify" cfg.reseed.verify)
160 ]
161 ++ (optionalNullString "file" cfg.reseed.file)
162 ++ (optionalEmptyList "urls" cfg.reseed.urls)
163 ++ (optionalNullString "floodfill" cfg.reseed.floodfill)
164 ++ (optionalNullString "zipfile" cfg.reseed.zipfile)
165 ++ (optionalNullString "proxy" cfg.reseed.proxy)
166 ++ [
167 (sec "trust")
168 (boolOpt "enabled" cfg.trust.enable)
169 (boolOpt "hidden" cfg.trust.hidden)
170 ]
171 ++ (optionalEmptyList "routers" cfg.trust.routers)
172 ++ (optionalNullString "family" cfg.trust.family)
173 ++ [
174 (sec "websockets")
175 (boolOpt "enabled" cfg.websocket.enable)
176 (strOpt "address" cfg.websocket.address)
177 (intOpt "port" cfg.websocket.port)
178 (sec "exploratory")
179 (intOpt "inbound.length" cfg.exploratory.inbound.length)
180 (intOpt "inbound.quantity" cfg.exploratory.inbound.quantity)
181 (intOpt "outbound.length" cfg.exploratory.outbound.length)
182 (intOpt "outbound.quantity" cfg.exploratory.outbound.quantity)
183 (sec "ntcp2")
184 (boolOpt "enabled" cfg.ntcp2.enable)
185 (boolOpt "published" cfg.ntcp2.published)
186 (intOpt "port" cfg.ntcp2.port)
187 (sec "ssu2")
188 (boolOpt "enabled" cfg.ssu2.enable)
189 (boolOpt "published" cfg.ssu2.published)
190 (intOpt "port" cfg.ssu2.port)
191 (sec "addressbook")
192 (strOpt "defaulturl" cfg.addressbook.defaulturl)
193 ]
194 ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
195 ++ [
196 (sec "meshnets")
197 (boolOpt "yggdrasil" cfg.yggdrasil.enable)
198 ]
199 ++ (optionalNullString "yggaddress" cfg.yggdrasil.address)
200 ++ (lib.flip map (lib.collect (proto: proto ? port && proto ? address) cfg.proto) (
201 proto:
202 let
203 protoOpts = [
204 (sec proto.name)
205 (boolOpt "enabled" proto.enable)
206 (strOpt "address" proto.address)
207 (intOpt "port" proto.port)
208 ]
209 ++ (optionals (proto ? keys) (optionalNullString "keys" proto.keys))
210 ++ (optionals (proto ? auth) (optionalNullBool "auth" proto.auth))
211 ++ (optionals (proto ? user) (optionalNullString "user" proto.user))
212 ++ (optionals (proto ? pass) (optionalNullString "pass" proto.pass))
213 ++ (optionals (proto ? strictHeaders) (optionalNullBool "strictheaders" proto.strictHeaders))
214 ++ (optionals (proto ? hostname) (optionalNullString "hostname" proto.hostname))
215 ++ (optionals (proto ? outproxy) (optionalNullString "outproxy" proto.outproxy))
216 ++ (optionals (proto ? outproxyPort) (optionalNullInt "outproxyport" proto.outproxyPort))
217 ++ (optionals (proto ? outproxyEnable) (optionalNullBool "outproxy.enabled" proto.outproxyEnable));
218 in
219 (lib.concatStringsSep "\n" protoOpts)
220 ));
221 in
222 pkgs.writeText "i2pd.conf" (lib.concatStringsSep "\n" opts);
223
224 tunnelConf =
225 let
226 mkOutTunnel =
227 tun:
228 let
229 outTunOpts = [
230 (sec tun.name)
231 "type = client"
232 (intOpt "port" tun.port)
233 (strOpt "destination" tun.destination)
234 ]
235 ++ (optionals (tun ? destinationPort) (optionalNullInt "destinationport" tun.destinationPort))
236 ++ (optionals (tun ? keys) (optionalNullString "keys" tun.keys))
237 ++ (optionals (tun ? address) (optionalNullString "address" tun.address))
238 ++ (optionals (tun ? inbound.length) (optionalNullInt "inbound.length" tun.inbound.length))
239 ++ (optionals (tun ? inbound.quantity) (optionalNullInt "inbound.quantity" tun.inbound.quantity))
240 ++ (optionals (tun ? outbound.length) (optionalNullInt "outbound.length" tun.outbound.length))
241 ++ (optionals (tun ? outbound.quantity) (optionalNullInt "outbound.quantity" tun.outbound.quantity))
242 ++ (optionals (tun ? crypto.tagsToSend) (
243 optionalNullInt "crypto.tagstosend" tun.crypto.tagsToSend
244 ));
245 in
246 lib.concatStringsSep "\n" outTunOpts;
247
248 mkInTunnel =
249 tun:
250 let
251 inTunOpts = [
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 port;
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 ssu2 = {
548 enable = mkEnableTrueOption "SSU2";
549 published = mkEnableOption "SSU2 publication";
550 port = mkOption {
551 type = types.port;
552 default = 0;
553 description = ''
554 Port to listen for incoming SSU2 connections (0=auto).
555 '';
556 };
557 };
558
559 limits.transittunnels = mkOption {
560 type = types.int;
561 default = 2500;
562 description = ''
563 Maximum number of active transit sessions.
564 '';
565 };
566
567 limits.coreSize = mkOption {
568 type = types.int;
569 default = 0;
570 description = ''
571 Maximum size of corefile in Kb (0 - use system limit).
572 '';
573 };
574
575 limits.openFiles = mkOption {
576 type = types.int;
577 default = 0;
578 description = ''
579 Maximum number of open files (0 - use system default).
580 '';
581 };
582
583 limits.ntcpHard = mkOption {
584 type = types.int;
585 default = 0;
586 description = ''
587 Maximum number of active transit sessions.
588 '';
589 };
590
591 limits.ntcpSoft = mkOption {
592 type = types.int;
593 default = 0;
594 description = ''
595 Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
596 '';
597 };
598
599 limits.ntcpThreads = mkOption {
600 type = types.int;
601 default = 1;
602 description = ''
603 Maximum number of threads used by NTCP DH worker.
604 '';
605 };
606
607 yggdrasil.enable = mkEnableOption "Yggdrasil";
608
609 yggdrasil.address = mkOption {
610 type = nullOr str;
611 default = null;
612 description = ''
613 Your local yggdrasil address. Specify it if you want to bind your router to a
614 particular address.
615 '';
616 };
617
618 proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
619
620 auth = mkEnableOption "webconsole authentication";
621
622 user = mkOption {
623 type = types.str;
624 default = "i2pd";
625 description = ''
626 Username for webconsole access
627 '';
628 };
629
630 pass = mkOption {
631 type = types.str;
632 default = "i2pd";
633 description = ''
634 Password for webconsole access.
635 '';
636 };
637
638 strictHeaders = mkOption {
639 type = nullOr bool;
640 default = null;
641 description = ''
642 Enable strict host checking on WebUI.
643 '';
644 };
645
646 hostname = mkOption {
647 type = nullOr str;
648 default = null;
649 description = ''
650 Expected hostname for WebUI.
651 '';
652 };
653 };
654
655 proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat") // {
656 outproxy = mkOption {
657 type = nullOr str;
658 default = null;
659 description = "Upstream outproxy bind address.";
660 };
661 };
662 proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat") // {
663 outproxyEnable = mkEnableOption "SOCKS outproxy";
664 outproxy = mkOption {
665 type = types.str;
666 default = "127.0.0.1";
667 description = "Upstream outproxy bind address.";
668 };
669 outproxyPort = mkOption {
670 type = types.port;
671 default = 4444;
672 description = "Upstream outproxy bind port.";
673 };
674 };
675
676 proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656;
677 proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827;
678 proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654;
679 proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650;
680
681 outTunnels = mkOption {
682 default = { };
683 type = attrsOf (
684 submodule (
685 { name, ... }:
686 {
687 options = {
688 destinationPort = mkOption {
689 type = with types; nullOr port;
690 default = null;
691 description = "Connect to particular port at destination.";
692 };
693 }
694 // commonTunOpts name;
695 config = {
696 name = mkDefault name;
697 };
698 }
699 )
700 );
701 description = ''
702 Connect to someone as a client and establish a local accept endpoint
703 '';
704 };
705
706 inTunnels = mkOption {
707 default = { };
708 type = attrsOf (
709 submodule (
710 { name, ... }:
711 {
712 options = {
713 inPort = mkOption {
714 type = types.port;
715 default = 0;
716 description = "Service port. Default to the tunnel's listen port.";
717 };
718 accessList = mkOption {
719 type = listOf str;
720 default = [ ];
721 description = "I2P nodes that are allowed to connect to this service.";
722 };
723 }
724 // commonTunOpts name;
725 config = {
726 name = mkDefault name;
727 };
728 }
729 )
730 );
731 description = ''
732 Serve something on I2P network at port and delegate requests to address inPort.
733 '';
734 };
735 };
736 };
737
738 ###### implementation
739
740 config = mkIf cfg.enable {
741
742 users.users.i2pd = {
743 group = "i2pd";
744 description = "I2Pd User";
745 home = homeDir;
746 createHome = true;
747 uid = config.ids.uids.i2pd;
748 };
749
750 users.groups.i2pd.gid = config.ids.gids.i2pd;
751
752 systemd.services.i2pd = {
753 description = "Minimal I2P router";
754 after = [ "network.target" ];
755 wantedBy = [ "multi-user.target" ];
756 serviceConfig = {
757 User = "i2pd";
758 WorkingDirectory = homeDir;
759 Restart = "on-abort";
760 ExecStart = "${cfg.package}/bin/i2pd ${i2pdFlags}";
761 };
762 };
763 };
764}