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}