1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.i2pd; 8 9 homeDir = "/var/lib/i2pd"; 10 11 mkEndpointOpt = name: addr: port: { 12 enable = mkEnableOption name; 13 name = mkOption { 14 type = types.str; 15 default = name; 16 description = "The endpoint name."; 17 }; 18 address = mkOption { 19 type = types.str; 20 default = addr; 21 description = "Bind address for ${name} endpoint. Default: " + addr; 22 }; 23 port = mkOption { 24 type = types.int; 25 default = port; 26 description = "Bind port for ${name} endoint. Default: " + toString port; 27 }; 28 }; 29 30 mkKeyedEndpointOpt = name: addr: port: keyFile: 31 (mkEndpointOpt name addr port) // { 32 keys = mkOption { 33 type = types.str; 34 default = ""; 35 description = '' 36 File to persist ${lib.toUpper name} keys. 37 ''; 38 }; 39 }; 40 41 commonTunOpts = let 42 i2cpOpts = { 43 length = mkOption { 44 type = types.int; 45 description = "Guaranteed minimum hops."; 46 default = 3; 47 }; 48 quantity = mkOption { 49 type = types.int; 50 description = "Number of simultaneous tunnels."; 51 default = 5; 52 }; 53 }; 54 in name: { 55 outbound = i2cpOpts; 56 inbound = i2cpOpts; 57 crypto.tagsToSend = mkOption { 58 type = types.int; 59 description = "Number of ElGamal/AES tags to send."; 60 default = 40; 61 }; 62 destination = mkOption { 63 type = types.str; 64 description = "Remote endpoint, I2P hostname or b32.i2p address."; 65 }; 66 keys = mkOption { 67 type = types.str; 68 default = name + "-keys.dat"; 69 description = "Keyset used for tunnel identity."; 70 }; 71 } // mkEndpointOpt name "127.0.0.1" 0; 72 73 i2pdConf = pkgs.writeText "i2pd.conf" '' 74 # DO NOT EDIT -- this file has been generated automatically. 75 loglevel = ${cfg.logLevel} 76 77 ipv4 = ${boolToString cfg.enableIPv4} 78 ipv6 = ${boolToString cfg.enableIPv6} 79 notransit = ${boolToString cfg.notransit} 80 floodfill = ${boolToString cfg.floodfill} 81 netid = ${toString cfg.netid} 82 ${if isNull cfg.bandwidth then "" else "bandwidth = ${toString cfg.bandwidth}" } 83 ${if isNull cfg.port then "" else "port = ${toString cfg.port}"} 84 85 [limits] 86 transittunnels = ${toString cfg.limits.transittunnels} 87 88 [upnp] 89 enabled = ${boolToString cfg.upnp.enable} 90 name = ${cfg.upnp.name} 91 92 [precomputation] 93 elgamal = ${boolToString cfg.precomputation.elgamal} 94 95 [reseed] 96 verify = ${boolToString cfg.reseed.verify} 97 file = ${cfg.reseed.file} 98 urls = ${builtins.concatStringsSep "," cfg.reseed.urls} 99 100 [addressbook] 101 defaulturl = ${cfg.addressbook.defaulturl} 102 subscriptions = ${builtins.concatStringsSep "," cfg.addressbook.subscriptions} 103 104 ${flip concatMapStrings 105 (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto) 106 (proto: let portStr = toString proto.port; in '' 107 [${proto.name}] 108 enabled = ${boolToString proto.enable} 109 address = ${proto.address} 110 port = ${toString proto.port} 111 ${if proto ? keys then "keys = ${proto.keys}" else ""} 112 ${if proto ? auth then "auth = ${boolToString proto.auth}" else ""} 113 ${if proto ? user then "user = ${proto.user}" else ""} 114 ${if proto ? pass then "pass = ${proto.pass}" else ""} 115 ${if proto ? outproxy then "outproxy = ${proto.outproxy}" else ""} 116 ${if proto ? outproxyPort then "outproxyport = ${toString proto.outproxyPort}" else ""} 117 '') 118 } 119 ''; 120 121 i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" '' 122 # DO NOT EDIT -- this file has been generated automatically. 123 ${flip concatMapStrings 124 (collect (tun: tun ? port && tun ? destination) cfg.outTunnels) 125 (tun: let portStr = toString tun.port; in '' 126 [${tun.name}] 127 type = client 128 destination = ${tun.destination} 129 destinationport = ${toString tun.destinationPort} 130 keys = ${tun.keys} 131 address = ${tun.address} 132 port = ${toString tun.port} 133 inbound.length = ${toString tun.inbound.length} 134 outbound.length = ${toString tun.outbound.length} 135 inbound.quantity = ${toString tun.inbound.quantity} 136 outbound.quantity = ${toString tun.outbound.quantity} 137 crypto.tagsToSend = ${toString tun.crypto.tagsToSend} 138 '') 139 } 140 ${flip concatMapStrings 141 (collect (tun: tun ? port && tun ? address) cfg.inTunnels) 142 (tun: '' 143 [${tun.name}] 144 type = server 145 destination = ${tun.destination} 146 keys = ${tun.keys} 147 host = ${tun.address} 148 port = ${toString tun.port} 149 inport = ${toString tun.inPort} 150 accesslist = ${builtins.concatStringsSep "," tun.accessList} 151 '') 152 } 153 ''; 154 155 i2pdSh = pkgs.writeScriptBin "i2pd" '' 156 #!/bin/sh 157 exec ${pkgs.i2pd}/bin/i2pd \ 158 ${if isNull cfg.address then "" else "--host="+cfg.address} \ 159 --conf=${i2pdConf} \ 160 --tunconf=${i2pdTunnelConf} 161 ''; 162 163in 164 165{ 166 167 ###### interface 168 169 options = { 170 171 services.i2pd = { 172 173 enable = mkOption { 174 type = types.bool; 175 default = false; 176 description = '' 177 Enables I2Pd as a running service upon activation. 178 Please read http://i2pd.readthedocs.io/en/latest/ for further 179 configuration help. 180 ''; 181 }; 182 183 logLevel = mkOption { 184 type = types.enum ["debug" "info" "warn" "error"]; 185 default = "error"; 186 description = '' 187 The log level. <command>i2pd</command> defaults to "info" 188 but that generates copious amounts of log messages. 189 190 We default to "error" which is similar to the default log 191 level of <command>tor</command>. 192 ''; 193 }; 194 195 address = mkOption { 196 type = with types; nullOr str; 197 default = null; 198 description = '' 199 Your external IP or hostname. 200 ''; 201 }; 202 203 notransit = mkOption { 204 type = types.bool; 205 default = false; 206 description = '' 207 Tells the router to not accept transit tunnels during startup. 208 ''; 209 }; 210 211 floodfill = mkOption { 212 type = types.bool; 213 default = false; 214 description = '' 215 If the router is declared to be unreachable and needs introduction nodes. 216 ''; 217 }; 218 219 netid = mkOption { 220 type = types.int; 221 default = 2; 222 description = '' 223 I2P overlay netid. 224 ''; 225 }; 226 227 bandwidth = mkOption { 228 type = with types; nullOr int; 229 default = null; 230 description = '' 231 Set a router bandwidth limit integer in KBps. 232 If not set, <command>i2pd</command> defaults to 32KBps. 233 ''; 234 }; 235 236 port = mkOption { 237 type = with types; nullOr int; 238 default = null; 239 description = '' 240 I2P listen port. If no one is given the router will pick between 9111 and 30777. 241 ''; 242 }; 243 244 enableIPv4 = mkOption { 245 type = types.bool; 246 default = true; 247 description = '' 248 Enables IPv4 connectivity. Enabled by default. 249 ''; 250 }; 251 252 enableIPv6 = mkOption { 253 type = types.bool; 254 default = false; 255 description = '' 256 Enables IPv6 connectivity. Disabled by default. 257 ''; 258 }; 259 260 nat = mkOption { 261 type = types.bool; 262 default = true; 263 description = '' 264 Assume router is NATed. Enabled by default. 265 ''; 266 }; 267 268 upnp = { 269 enable = mkOption { 270 type = types.bool; 271 default = false; 272 description = '' 273 Enables UPnP. 274 ''; 275 }; 276 277 name = mkOption { 278 type = types.str; 279 default = "I2Pd"; 280 description = '' 281 Name i2pd appears in UPnP forwardings list. 282 ''; 283 }; 284 }; 285 286 precomputation.elgamal = mkOption { 287 type = types.bool; 288 default = true; 289 description = '' 290 Whenever to use precomputated tables for ElGamal. 291 <command>i2pd</command> defaults to <literal>false</literal> 292 to save 64M of memory (and looses some performance). 293 294 We default to <literal>true</literal> as that is what most 295 users want anyway. 296 ''; 297 }; 298 299 reseed = { 300 verify = mkOption { 301 type = types.bool; 302 default = false; 303 description = '' 304 Request SU3 signature verification 305 ''; 306 }; 307 308 file = mkOption { 309 type = types.str; 310 default = ""; 311 description = '' 312 Full path to SU3 file to reseed from 313 ''; 314 }; 315 316 urls = mkOption { 317 type = with types; listOf str; 318 default = [ 319 "https://reseed.i2p-project.de/" 320 "https://i2p.mooo.com/netDb/" 321 "https://netdb.i2p2.no/" 322 "https://us.reseed.i2p2.no:444/" 323 "https://uk.reseed.i2p2.no:444/" 324 "https://i2p.manas.ca:8443/" 325 ]; 326 description = '' 327 Reseed URLs 328 ''; 329 }; 330 }; 331 332 addressbook = { 333 defaulturl = mkOption { 334 type = types.str; 335 default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; 336 description = '' 337 AddressBook subscription URL for initial setup 338 ''; 339 }; 340 subscriptions = mkOption { 341 type = with types; listOf str; 342 default = [ 343 "http://inr.i2p/export/alive-hosts.txt" 344 "http://i2p-projekt.i2p/hosts.txt" 345 "http://stats.i2p/cgi-bin/newhosts.txt" 346 ]; 347 description = '' 348 AddressBook subscription URLs 349 ''; 350 }; 351 }; 352 353 limits.transittunnels = mkOption { 354 type = types.int; 355 default = 2500; 356 description = '' 357 Maximum number of active transit sessions 358 ''; 359 }; 360 361 proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // { 362 auth = mkOption { 363 type = types.bool; 364 default = false; 365 description = '' 366 Enable authentication for webconsole. 367 ''; 368 }; 369 user = mkOption { 370 type = types.str; 371 default = "i2pd"; 372 description = '' 373 Username for webconsole access 374 ''; 375 }; 376 pass = mkOption { 377 type = types.str; 378 default = "i2pd"; 379 description = '' 380 Password for webconsole access. 381 ''; 382 }; 383 }; 384 385 proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 ""; 386 proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "") 387 // { 388 outproxy = mkOption { 389 type = types.str; 390 default = "127.0.0.1"; 391 description = "Upstream outproxy bind address."; 392 }; 393 outproxyPort = mkOption { 394 type = types.int; 395 default = 4444; 396 description = "Upstream outproxy bind port."; 397 }; 398 }; 399 400 proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656; 401 proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827; 402 proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654; 403 proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650; 404 405 outTunnels = mkOption { 406 default = {}; 407 type = with types; loaOf (submodule ( 408 { name, config, ... }: { 409 options = { 410 destinationPort = mkOption { 411 type = types.int; 412 default = 0; 413 description = "Connect to particular port at destination."; 414 }; 415 } // commonTunOpts name; 416 config = { 417 name = mkDefault name; 418 }; 419 } 420 )); 421 description = '' 422 Connect to someone as a client and establish a local accept endpoint 423 ''; 424 }; 425 426 inTunnels = mkOption { 427 default = {}; 428 type = with types; loaOf (submodule ( 429 { name, config, ... }: { 430 options = { 431 inPort = mkOption { 432 type = types.int; 433 default = 0; 434 description = "Service port. Default to the tunnel's listen port."; 435 }; 436 accessList = mkOption { 437 type = with types; listOf str; 438 default = []; 439 description = "I2P nodes that are allowed to connect to this service."; 440 }; 441 } // commonTunOpts name; 442 config = { 443 name = mkDefault name; 444 }; 445 } 446 )); 447 description = '' 448 Serve something on I2P network at port and delegate requests to address inPort. 449 ''; 450 }; 451 }; 452 }; 453 454 455 ###### implementation 456 457 config = mkIf cfg.enable { 458 459 users.extraUsers.i2pd = { 460 group = "i2pd"; 461 description = "I2Pd User"; 462 home = homeDir; 463 createHome = true; 464 uid = config.ids.uids.i2pd; 465 }; 466 467 users.extraGroups.i2pd.gid = config.ids.gids.i2pd; 468 469 systemd.services.i2pd = { 470 description = "Minimal I2P router"; 471 after = [ "network.target" ]; 472 wantedBy = [ "multi-user.target" ]; 473 serviceConfig = 474 { 475 User = "i2pd"; 476 WorkingDirectory = homeDir; 477 Restart = "on-abort"; 478 ExecStart = "${i2pdSh}/bin/i2pd"; 479 }; 480 }; 481 }; 482}