at 24.11-pre 21 kB view raw
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}