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