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