1{ config, lib, pkgs, ... }: 2 3with lib; 4 5let 6 7 cfg = config.services.i2pd; 8 9 homeDir = "/var/lib/i2pd"; 10 11 extip = "EXTIP=\$(${pkgs.curl}/bin/curl -sf \"http://jsonip.com\" | ${pkgs.gawk}/bin/awk -F'\"' '{print $4}')"; 12 13 toOneZero = b: if b then "1" else "0"; 14 15 mkEndpointOpt = name: addr: port: { 16 name = mkOption { 17 type = types.str; 18 default = name; 19 description = "The endpoint name."; 20 }; 21 address = mkOption { 22 type = types.str; 23 default = addr; 24 description = "Bind address for ${name} endpoint. Default: " + addr; 25 }; 26 port = mkOption { 27 type = types.int; 28 default = port; 29 description = "Bind port for ${name} endoint. Default: " + toString port; 30 }; 31 }; 32 33 commonTunOpts = let 34 i2cpOpts = { 35 length = mkOption { 36 type = types.int; 37 description = "Guaranteed minimum hops."; 38 default = 3; 39 }; 40 quantity = mkOption { 41 type = types.int; 42 description = "Number of simultaneous tunnels."; 43 default = 5; 44 }; 45 }; 46 in name: { 47 outbound = i2cpOpts; 48 inbound = i2cpOpts; 49 crypto.tagsToSend = mkOption { 50 type = types.int; 51 description = "Number of ElGamal/AES tags to send."; 52 default = 40; 53 }; 54 destination = mkOption { 55 type = types.str; 56 description = "Remote endpoint, I2P hostname or b32.i2p address."; 57 }; 58 keys = mkOption { 59 type = types.str; 60 default = name + "-keys.dat"; 61 description = "Keyset used for tunnel identity."; 62 }; 63 } // mkEndpointOpt name "127.0.0.1" 0; 64 65 i2pdConf = pkgs.writeText "i2pd.conf" '' 66 ipv6 = ${toOneZero cfg.enableIPv6} 67 notransit = ${toOneZero cfg.notransit} 68 floodfill = ${toOneZero cfg.floodfill} 69 ${if isNull cfg.port then "" else "port = ${toString cfg.port}"} 70 ${flip concatMapStrings 71 (collect (proto: proto ? port && proto ? address && proto ? name) cfg.proto) 72 (proto: let portStr = toString proto.port; in '' 73 [${proto.name}] 74 address = ${proto.address} 75 port = ${toString proto.port} 76 '') 77 } 78 ''; 79 80 i2pdTunnelConf = pkgs.writeText "i2pd-tunnels.conf" '' 81 ${flip concatMapStrings 82 (collect (tun: tun ? port && tun ? destination) cfg.outTunnels) 83 (tun: let portStr = toString tun.port; in '' 84 [${tun.name}] 85 type = client 86 destination = ${tun.destination} 87 keys = ${tun.keys} 88 address = ${tun.address} 89 port = ${toString tun.port} 90 inbound.length = ${toString tun.inbound.length} 91 outbound.length = ${toString tun.outbound.length} 92 inbound.quantity = ${toString tun.inbound.quantity} 93 outbound.quantity = ${toString tun.outbound.quantity} 94 crypto.tagsToSend = ${toString tun.crypto.tagsToSend} 95 '') 96 } 97 ${flip concatMapStrings 98 (collect (tun: tun ? port && tun ? host) cfg.inTunnels) 99 (tun: let portStr = toString tun.port; in '' 100 [${tun.name}] 101 type = server 102 destination = ${tun.destination} 103 keys = ${tun.keys} 104 host = ${tun.address} 105 port = ${tun.port} 106 inport = ${tun.inPort} 107 accesslist = ${concatStringSep "," tun.accessList} 108 '') 109 } 110 ''; 111 112 i2pdSh = pkgs.writeScriptBin "i2pd" '' 113 #!/bin/sh 114 ${if isNull cfg.extIp then extip else ""} 115 ${pkgs.i2pd}/bin/i2pd --log=1 \ 116 --host=${if isNull cfg.extIp then "$EXTIP" else cfg.extIp} \ 117 --conf=${i2pdConf} \ 118 --tunconf=${i2pdTunnelConf} 119 ''; 120 121in 122 123{ 124 125 ###### interface 126 127 options = { 128 129 services.i2pd = { 130 131 enable = mkOption { 132 type = types.bool; 133 default = false; 134 description = '' 135 Enables I2Pd as a running service upon activation. 136 ''; 137 }; 138 139 extIp = mkOption { 140 type = with types; nullOr str; 141 default = null; 142 description = '' 143 Your external IP. 144 ''; 145 }; 146 147 notransit = mkOption { 148 type = types.bool; 149 default = false; 150 description = '' 151 Tells the router to not accept transit tunnels during startup. 152 ''; 153 }; 154 155 floodfill = mkOption { 156 type = types.bool; 157 default = false; 158 description = '' 159 If the router is declared to be unreachable and needs introduction nodes. 160 ''; 161 }; 162 163 port = mkOption { 164 type = with types; nullOr int; 165 default = null; 166 description = '' 167 I2P listen port. If no one is given the router will pick between 9111 and 30777. 168 ''; 169 }; 170 171 enableIPv6 = mkOption { 172 type = types.bool; 173 default = false; 174 description = '' 175 Enables IPv6 connectivity. Disabled by default. 176 ''; 177 }; 178 179 proto.http = mkEndpointOpt "http" "127.0.0.1" 7070; 180 proto.sam = mkEndpointOpt "sam" "127.0.0.1" 7656; 181 proto.bob = mkEndpointOpt "bob" "127.0.0.1" 2827; 182 proto.i2pControl = mkEndpointOpt "i2pcontrol" "127.0.0.1" 7650; 183 proto.httpProxy = mkEndpointOpt "httpproxy" "127.0.0.1" 4446; 184 proto.socksProxy = mkEndpointOpt "socksproxy" "127.0.0.1" 4447; 185 186 outTunnels = mkOption { 187 default = {}; 188 type = with types; loaOf optionSet; 189 description = '' 190 Connect to someone as a client and establish a local accept endpoint 191 ''; 192 options = [ ({ name, config, ... }: { 193 options = commonTunOpts name; 194 config = { 195 name = mkDefault name; 196 }; 197 }) ]; 198 }; 199 200 inTunnels = mkOption { 201 default = {}; 202 type = with types; loaOf optionSet; 203 description = '' 204 Serve something on I2P network at port and delegate requests to address inPort. 205 ''; 206 options = [ ({ name, config, ... }: { 207 208 options = { 209 inPort = mkOption { 210 type = types.int; 211 default = 0; 212 description = "Service port. Default to the tunnel's listen port."; 213 }; 214 accessList = mkOption { 215 type = with types; listOf str; 216 default = []; 217 description = "I2P nodes that are allowed to connect to this service."; 218 }; 219 } // commonTunOpts name; 220 221 config = { 222 name = mkDefault name; 223 }; 224 225 }) ]; 226 }; 227 }; 228 }; 229 230 231 ###### implementation 232 233 config = mkIf cfg.enable { 234 235 users.extraUsers.i2pd = { 236 group = "i2pd"; 237 description = "I2Pd User"; 238 home = homeDir; 239 createHome = true; 240 uid = config.ids.uids.i2pd; 241 }; 242 243 users.extraGroups.i2pd.gid = config.ids.gids.i2pd; 244 245 systemd.services.i2pd = { 246 description = "Minimal I2P router"; 247 after = [ "network.target" ]; 248 wantedBy = [ "multi-user.target" ]; 249 serviceConfig = 250 { 251 User = "i2pd"; 252 WorkingDirectory = homeDir; 253 Restart = "on-abort"; 254 ExecStart = "${i2pdSh}/bin/i2pd"; 255 }; 256 }; 257 }; 258}