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