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