1 { config, lib, pkgs, ... }:
7 cfg = config.services.i2pd;
9 homeDir = "/var/lib/i2pd";
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);
20 mkEnableTrueOption = name: mkEnableOption (lib.mdDoc name) // { default = true; };
22 mkEndpointOpt = name: addr: port: {
23 enable = mkEnableOption (lib.mdDoc name);
27 description = lib.mdDoc "The endpoint name.";
32 description = lib.mdDoc "Bind address for ${name} endpoint.";
37 description = lib.mdDoc "Bind port for ${name} endpoint.";
44 description = lib.mdDoc "Guaranteed minimum hops for ${name} tunnels.";
49 description = lib.mdDoc "Number of simultaneous ${name} tunnels.";
54 mkKeyedEndpointOpt = name: addr: port: keyloc:
55 (mkEndpointOpt name addr port) // {
57 type = with types; nullOr str;
59 description = lib.mdDoc ''
60 File to persist ${lib.toUpper name} keys.
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.";
70 latency.max = mkOption {
71 type = with types; nullOr int;
72 description = lib.mdDoc "Max latency for tunnels.";
77 commonTunOpts = name: {
78 outbound = i2cpOpts name;
79 inbound = i2cpOpts name;
80 crypto.tagsToSend = mkOption {
82 description = lib.mdDoc "Number of ElGamal/AES tags to send.";
85 destination = mkOption {
87 description = lib.mdDoc "Remote endpoint, I2P hostname or b32.i2p address.";
91 default = name + "-keys.dat";
92 description = lib.mdDoc "Keyset used for tunnel identity.";
94 } // mkEndpointOpt name "127.0.0.1" 0;
96 sec = name: "\n[" + name + "]";
97 notice = "# DO NOT EDIT -- this file has been generated automatically.";
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)
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)
128 (boolOpt "enabled" cfg.upnp.enable)
129 (sec "precomputation")
130 (boolOpt "elgamal" cfg.precomputation.elgamal)
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)
140 (boolOpt "enabled" cfg.trust.enable)
141 (boolOpt "hidden" cfg.trust.hidden)
142 ] ++ (optionalEmptyList "routers" cfg.trust.routers)
143 ++ (optionalNullString "family" cfg.trust.family)
146 (boolOpt "enabled" cfg.websocket.enable)
147 (strOpt "address" cfg.websocket.address)
148 (intOpt "port" cfg.websocket.port)
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)
155 (boolOpt "enabled" cfg.ntcp2.enable)
156 (boolOpt "published" cfg.ntcp2.published)
157 (intOpt "port" cfg.ntcp2.port)
159 (strOpt "defaulturl" cfg.addressbook.defaulturl)
160 ] ++ (optionalEmptyList "subscriptions" cfg.addressbook.subscriptions)
163 (boolOpt "yggdrasil" cfg.yggdrasil.enable)
164 ] ++ (optionalNullString "yggaddress" cfg.yggdrasil.address)
166 (collect (proto: proto ? port && proto ? address) cfg.proto)
167 (proto: let protoOpts = [
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)
184 pkgs.writeText "i2pd.conf" (concatStringsSep "\n" opts);
186 tunnelConf = let opts = [
189 (collect (tun: tun ? port && tun ? destination) cfg.outTunnels)
190 (tun: let outTunOpts = [
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))
212 (collect (tun: tun ? port && tun ? address) cfg.inTunnels)
213 (tun: let inTunOpts = [
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;
229 i2pdFlags = concatStringsSep " " (
230 optional (cfg.address != null) ("--host=" + cfg.address) ++ [
232 ("--conf=" + i2pdConf)
233 ("--tunconf=" + tunnelConf)
241 (mkRenamedOptionModule [ "services" "i2pd" "extIp" ] [ "services" "i2pd" "address" ])
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
259 type = types.package;
261 defaultText = literalExpression "pkgs.i2pd";
262 description = lib.mdDoc ''
267 logLevel = mkOption {
268 type = types.enum ["debug" "info" "warn" "error"];
270 description = lib.mdDoc ''
271 The log level. {command}`i2pd` defaults to "info"
272 but that generates copious amounts of log messages.
274 We default to "error" which is similar to the default log
275 level of {command}`tor`.
279 logCLFTime = mkEnableOption (lib.mdDoc "Full CLF-formatted date and time to log");
282 type = with types; nullOr str;
284 description = lib.mdDoc ''
285 Your external IP or hostname.
290 type = with types; nullOr str;
292 description = lib.mdDoc ''
293 Specify a family the router belongs to.
298 type = with types; nullOr str;
300 description = lib.mdDoc ''
301 Alternative path to storage of i2pd data (RI, keys, peer profiles, ...)
308 description = lib.mdDoc ''
309 Limit of transit traffic from max bandwidth in percents.
314 type = with types; nullOr str;
316 description = lib.mdDoc ''
317 Network interface to bind to.
322 type = with types; nullOr str;
324 description = lib.mdDoc ''
325 IPv4 interface to bind to.
330 type = with types; nullOr str;
332 description = lib.mdDoc ''
333 IPv6 interface to bind to.
337 ntcpProxy = mkOption {
338 type = with types; nullOr str;
340 description = lib.mdDoc ''
341 Proxy URL for NTCP transport.
345 ntcp = mkEnableTrueOption "ntcp";
346 ssu = mkEnableTrueOption "ssu";
348 notransit = mkEnableOption (lib.mdDoc "notransit") // {
349 description = lib.mdDoc ''
350 Tells the router to not accept transit tunnels during startup.
354 floodfill = mkEnableOption (lib.mdDoc "floodfill") // {
355 description = lib.mdDoc ''
356 If the router is declared to be unreachable and needs introduction nodes.
363 description = lib.mdDoc ''
368 bandwidth = mkOption {
369 type = with types; nullOr int;
371 description = lib.mdDoc ''
372 Set a router bandwidth limit integer in KBps.
373 If not set, {command}`i2pd` defaults to 32KBps.
378 type = with types; nullOr int;
380 description = lib.mdDoc ''
381 I2P listen port. If no one is given the router will pick between 9111 and 30777.
385 enableIPv4 = mkEnableTrueOption "IPv4 connectivity";
386 enableIPv6 = mkEnableOption (lib.mdDoc "IPv6 connectivity");
387 nat = mkEnableTrueOption "NAT bypass";
389 upnp.enable = mkEnableOption (lib.mdDoc "UPnP service discovery");
390 upnp.name = mkOption {
393 description = lib.mdDoc ''
394 Name i2pd appears in UPnP forwardings list.
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).
404 We default to `true` as that is what most
409 reseed.verify = mkEnableOption (lib.mdDoc "SU3 signature verification");
411 reseed.file = mkOption {
412 type = with types; nullOr str;
414 description = lib.mdDoc ''
415 Full path to SU3 file to reseed from.
419 reseed.urls = mkOption {
420 type = with types; listOf str;
422 description = lib.mdDoc ''
427 reseed.floodfill = mkOption {
428 type = with types; nullOr str;
430 description = lib.mdDoc ''
431 Path to router info of floodfill to reseed from.
435 reseed.zipfile = mkOption {
436 type = with types; nullOr str;
438 description = lib.mdDoc ''
439 Path to local .zip file to reseed from.
443 reseed.proxy = mkOption {
444 type = with types; nullOr str;
446 description = lib.mdDoc ''
447 URL for reseed proxy, supports http/socks.
451 addressbook.defaulturl = mkOption {
453 default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt";
454 description = lib.mdDoc ''
455 AddressBook subscription URL for initial setup
458 addressbook.subscriptions = mkOption {
459 type = with types; listOf str;
461 "http://inr.i2p/export/alive-hosts.txt"
462 "http://i2p-projekt.i2p/hosts.txt"
463 "http://stats.i2p/cgi-bin/newhosts.txt"
465 description = lib.mdDoc ''
466 AddressBook subscription URLs
470 trust.enable = mkEnableOption (lib.mdDoc "Explicit trust options");
472 trust.family = mkOption {
473 type = with types; nullOr str;
475 description = lib.mdDoc ''
476 Router Familiy to trust for first hops.
480 trust.routers = mkOption {
481 type = with types; listOf str;
483 description = lib.mdDoc ''
484 Only connect to the listed routers.
488 trust.hidden = mkEnableOption (lib.mdDoc "Router concealment");
490 websocket = mkEndpointOpt "websockets" "127.0.0.1" 7666;
492 exploratory.inbound = i2cpOpts "exploratory";
493 exploratory.outbound = i2cpOpts "exploratory";
495 ntcp2.enable = mkEnableTrueOption "NTCP2";
496 ntcp2.published = mkEnableOption (lib.mdDoc "NTCP2 publication");
497 ntcp2.port = mkOption {
500 description = lib.mdDoc ''
501 Port to listen for incoming NTCP2 connections (0=auto).
505 limits.transittunnels = mkOption {
508 description = lib.mdDoc ''
509 Maximum number of active transit sessions.
513 limits.coreSize = mkOption {
516 description = lib.mdDoc ''
517 Maximum size of corefile in Kb (0 - use system limit).
521 limits.openFiles = mkOption {
524 description = lib.mdDoc ''
525 Maximum number of open files (0 - use system default).
529 limits.ntcpHard = mkOption {
532 description = lib.mdDoc ''
533 Maximum number of active transit sessions.
537 limits.ntcpSoft = mkOption {
540 description = lib.mdDoc ''
541 Threshold to start probabalistic backoff with ntcp sessions (default: use system limit).
545 limits.ntcpThreads = mkOption {
548 description = lib.mdDoc ''
549 Maximum number of threads used by NTCP DH worker.
553 yggdrasil.enable = mkEnableOption (lib.mdDoc "Yggdrasil");
555 yggdrasil.address = mkOption {
556 type = with types; nullOr str;
558 description = lib.mdDoc ''
559 Your local yggdrasil address. Specify it if you want to bind your router to a
564 proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // {
566 auth = mkEnableOption (lib.mdDoc "Webconsole authentication");
571 description = lib.mdDoc ''
572 Username for webconsole access
579 description = lib.mdDoc ''
580 Password for webconsole access.
584 strictHeaders = mkOption {
585 type = with types; nullOr bool;
587 description = lib.mdDoc ''
588 Enable strict host checking on WebUI.
592 hostname = mkOption {
593 type = with types; nullOr str;
595 description = lib.mdDoc ''
596 Expected hostname for WebUI.
601 proto.httpProxy = (mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 "httpproxy-keys.dat")
603 outproxy = mkOption {
604 type = with types; nullOr str;
606 description = lib.mdDoc "Upstream outproxy bind address.";
609 proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "socksproxy-keys.dat")
611 outproxyEnable = mkEnableOption (lib.mdDoc "SOCKS outproxy");
612 outproxy = mkOption {
614 default = "127.0.0.1";
615 description = lib.mdDoc "Upstream outproxy bind address.";
617 outproxyPort = mkOption {
620 description = lib.mdDoc "Upstream outproxy bind port.";
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;
629 outTunnels = mkOption {
631 type = with types; attrsOf (submodule (
634 destinationPort = mkOption {
635 type = with types; nullOr int;
637 description = lib.mdDoc "Connect to particular port at destination.";
639 } // commonTunOpts name;
641 name = mkDefault name;
645 description = lib.mdDoc ''
646 Connect to someone as a client and establish a local accept endpoint
650 inTunnels = mkOption {
652 type = with types; attrsOf (submodule (
658 description = lib.mdDoc "Service port. Default to the tunnel's listen port.";
660 accessList = mkOption {
661 type = with types; listOf str;
663 description = lib.mdDoc "I2P nodes that are allowed to connect to this service.";
665 } // commonTunOpts name;
667 name = mkDefault name;
671 description = lib.mdDoc ''
672 Serve something on I2P network at port and delegate requests to address inPort.
679 ###### implementation
681 config = mkIf cfg.enable {
685 description = "I2Pd User";
688 uid = config.ids.uids.i2pd;
691 users.groups.i2pd.gid = config.ids.gids.i2pd;
693 systemd.services.i2pd = {
694 description = "Minimal I2P router";
695 after = [ "network.target" ];
696 wantedBy = [ "multi-user.target" ];
700 WorkingDirectory = homeDir;
701 Restart = "on-abort";
702 ExecStart = "${cfg.package}/bin/i2pd ${i2pdFlags}";