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 keys = ${tun.keys} 130 address = ${tun.address} 131 port = ${toString tun.port} 132 inbound.length = ${toString tun.inbound.length} 133 outbound.length = ${toString tun.outbound.length} 134 inbound.quantity = ${toString tun.inbound.quantity} 135 outbound.quantity = ${toString tun.outbound.quantity} 136 crypto.tagsToSend = ${toString tun.crypto.tagsToSend} 137 '') 138 } 139 ${flip concatMapStrings 140 (collect (tun: tun ? port && tun ? host) cfg.inTunnels) 141 (tun: let portStr = toString tun.port; in '' 142 [${tun.name}] 143 type = server 144 destination = ${tun.destination} 145 keys = ${tun.keys} 146 host = ${tun.address} 147 port = ${tun.port} 148 inport = ${tun.inPort} 149 accesslist = ${builtins.concatStringsSep "," tun.accessList} 150 '') 151 } 152 ''; 153 154 i2pdSh = pkgs.writeScriptBin "i2pd" '' 155 #!/bin/sh 156 exec ${pkgs.i2pd}/bin/i2pd \ 157 ${if isNull cfg.address then "" else "--host="+cfg.address} \ 158 --conf=${i2pdConf} \ 159 --tunconf=${i2pdTunnelConf} 160 ''; 161 162in 163 164{ 165 166 ###### interface 167 168 options = { 169 170 services.i2pd = { 171 172 enable = mkOption { 173 type = types.bool; 174 default = false; 175 description = '' 176 Enables I2Pd as a running service upon activation. 177 Please read http://i2pd.readthedocs.io/en/latest/ for further 178 configuration help. 179 ''; 180 }; 181 182 logLevel = mkOption { 183 type = types.enum ["debug" "info" "warn" "error"]; 184 default = "error"; 185 description = '' 186 The log level. <command>i2pd</command> defaults to "info" 187 but that generates copious amounts of log messages. 188 189 We default to "error" which is similar to the default log 190 level of <command>tor</command>. 191 ''; 192 }; 193 194 address = mkOption { 195 type = with types; nullOr str; 196 default = null; 197 description = '' 198 Your external IP or hostname. 199 ''; 200 }; 201 202 notransit = mkOption { 203 type = types.bool; 204 default = false; 205 description = '' 206 Tells the router to not accept transit tunnels during startup. 207 ''; 208 }; 209 210 floodfill = mkOption { 211 type = types.bool; 212 default = false; 213 description = '' 214 If the router is declared to be unreachable and needs introduction nodes. 215 ''; 216 }; 217 218 netid = mkOption { 219 type = types.int; 220 default = 2; 221 description = '' 222 I2P overlay netid. 223 ''; 224 }; 225 226 bandwidth = mkOption { 227 type = with types; nullOr int; 228 default = null; 229 description = '' 230 Set a router bandwidth limit integer in KBps. 231 If not set, <command>i2pd</command> defaults to 32KBps. 232 ''; 233 }; 234 235 port = mkOption { 236 type = with types; nullOr int; 237 default = null; 238 description = '' 239 I2P listen port. If no one is given the router will pick between 9111 and 30777. 240 ''; 241 }; 242 243 enableIPv4 = mkOption { 244 type = types.bool; 245 default = true; 246 description = '' 247 Enables IPv4 connectivity. Enabled by default. 248 ''; 249 }; 250 251 enableIPv6 = mkOption { 252 type = types.bool; 253 default = false; 254 description = '' 255 Enables IPv6 connectivity. Disabled by default. 256 ''; 257 }; 258 259 upnp = { 260 enable = mkOption { 261 type = types.bool; 262 default = false; 263 description = '' 264 Enables UPnP. 265 ''; 266 }; 267 268 name = mkOption { 269 type = types.str; 270 default = "I2Pd"; 271 description = '' 272 Name i2pd appears in UPnP forwardings list. 273 ''; 274 }; 275 }; 276 277 precomputation.elgamal = mkOption { 278 type = types.bool; 279 default = true; 280 description = '' 281 Whenever to use precomputated tables for ElGamal. 282 <command>i2pd</command> defaults to <literal>false</literal> 283 to save 64M of memory (and looses some performance). 284 285 We default to <literal>true</literal> as that is what most 286 users want anyway. 287 ''; 288 }; 289 290 reseed = { 291 verify = mkOption { 292 type = types.bool; 293 default = false; 294 description = '' 295 Request SU3 signature verification 296 ''; 297 }; 298 299 file = mkOption { 300 type = types.str; 301 default = ""; 302 description = '' 303 Full path to SU3 file to reseed from 304 ''; 305 }; 306 307 urls = mkOption { 308 type = with types; listOf str; 309 default = [ 310 "https://reseed.i2p-project.de/" 311 "https://i2p.mooo.com/netDb/" 312 "https://netdb.i2p2.no/" 313 "https://us.reseed.i2p2.no:444/" 314 "https://uk.reseed.i2p2.no:444/" 315 "https://i2p.manas.ca:8443/" 316 ]; 317 description = '' 318 Reseed URLs 319 ''; 320 }; 321 }; 322 323 addressbook = { 324 defaulturl = mkOption { 325 type = types.str; 326 default = "http://joajgazyztfssty4w2on5oaqksz6tqoxbduy553y34mf4byv6gpq.b32.i2p/export/alive-hosts.txt"; 327 description = '' 328 AddressBook subscription URL for initial setup 329 ''; 330 }; 331 subscriptions = mkOption { 332 type = with types; listOf str; 333 default = [ 334 "http://inr.i2p/export/alive-hosts.txt" 335 "http://i2p-projekt.i2p/hosts.txt" 336 "http://stats.i2p/cgi-bin/newhosts.txt" 337 ]; 338 description = '' 339 AddressBook subscription URLs 340 ''; 341 }; 342 }; 343 344 limits.transittunnels = mkOption { 345 type = types.int; 346 default = 2500; 347 description = '' 348 Maximum number of active transit sessions 349 ''; 350 }; 351 352 proto.http = (mkEndpointOpt "http" "127.0.0.1" 7070) // { 353 auth = mkOption { 354 type = types.bool; 355 default = false; 356 description = '' 357 Enable authentication for webconsole. 358 ''; 359 }; 360 user = mkOption { 361 type = types.str; 362 default = "i2pd"; 363 description = '' 364 Username for webconsole access 365 ''; 366 }; 367 pass = mkOption { 368 type = types.str; 369 default = "i2pd"; 370 description = '' 371 Password for webconsole access. 372 ''; 373 }; 374 }; 375 376 proto.httpProxy = mkKeyedEndpointOpt "httpproxy" "127.0.0.1" 4444 ""; 377 proto.socksProxy = (mkKeyedEndpointOpt "socksproxy" "127.0.0.1" 4447 "") 378 // { 379 outproxy = mkOption { 380 type = types.str; 381 default = "127.0.0.1"; 382 description = "Upstream outproxy bind address."; 383 }; 384 outproxyPort = mkOption { 385 type = types.int; 386 default = 4444; 387 description = "Upstream outproxy bind port."; 388 }; 389 }; 390 391 proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656; 392 proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827; 393 proto.i2cp = mkEndpointOpt "i2cp" "127.0.0.1" 7654; 394 proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650; 395 396 outTunnels = mkOption { 397 default = {}; 398 type = with types; loaOf (submodule ( 399 { name, config, ... }: { 400 options = commonTunOpts name; 401 config = { 402 name = mkDefault name; 403 }; 404 } 405 )); 406 description = '' 407 Connect to someone as a client and establish a local accept endpoint 408 ''; 409 }; 410 411 inTunnels = mkOption { 412 default = {}; 413 type = with types; loaOf (submodule ( 414 { name, config, ... }: { 415 options = { 416 inPort = mkOption { 417 type = types.int; 418 default = 0; 419 description = "Service port. Default to the tunnel's listen port."; 420 }; 421 accessList = mkOption { 422 type = with types; listOf str; 423 default = []; 424 description = "I2P nodes that are allowed to connect to this service."; 425 }; 426 } // commonTunOpts name; 427 config = { 428 name = mkDefault name; 429 }; 430 } 431 )); 432 description = '' 433 Serve something on I2P network at port and delegate requests to address inPort. 434 ''; 435 }; 436 }; 437 }; 438 439 440 ###### implementation 441 442 config = mkIf cfg.enable { 443 444 users.extraUsers.i2pd = { 445 group = "i2pd"; 446 description = "I2Pd User"; 447 home = homeDir; 448 createHome = true; 449 uid = config.ids.uids.i2pd; 450 }; 451 452 users.extraGroups.i2pd.gid = config.ids.gids.i2pd; 453 454 systemd.services.i2pd = { 455 description = "Minimal I2P router"; 456 after = [ "network.target" ]; 457 wantedBy = [ "multi-user.target" ]; 458 serviceConfig = 459 { 460 User = "i2pd"; 461 WorkingDirectory = homeDir; 462 Restart = "on-abort"; 463 ExecStart = "${i2pdSh}/bin/i2pd"; 464 }; 465 }; 466 }; 467}