1{ config, lib, pkgs, ... }:
2
3with lib;
4let
5 cfg = config.services.tahoe;
6in
7 {
8 options.services.tahoe = {
9 introducers = mkOption {
10 default = {};
11 type = with types; attrsOf (submodule {
12 options = {
13 nickname = mkOption {
14 type = types.str;
15 description = lib.mdDoc ''
16 The nickname of this Tahoe introducer.
17 '';
18 };
19 tub.port = mkOption {
20 default = 3458;
21 type = types.port;
22 description = lib.mdDoc ''
23 The port on which the introducer will listen.
24 '';
25 };
26 tub.location = mkOption {
27 default = null;
28 type = types.nullOr types.str;
29 description = lib.mdDoc ''
30 The external location that the introducer should listen on.
31
32 If specified, the port should be included.
33 '';
34 };
35 package = mkOption {
36 default = pkgs.tahoelafs;
37 defaultText = literalExpression "pkgs.tahoelafs";
38 type = types.package;
39 description = lib.mdDoc ''
40 The package to use for the Tahoe LAFS daemon.
41 '';
42 };
43 };
44 });
45 description = lib.mdDoc ''
46 The Tahoe introducers.
47 '';
48 };
49 nodes = mkOption {
50 default = {};
51 type = with types; attrsOf (submodule {
52 options = {
53 nickname = mkOption {
54 type = types.str;
55 description = lib.mdDoc ''
56 The nickname of this Tahoe node.
57 '';
58 };
59 tub.port = mkOption {
60 default = 3457;
61 type = types.port;
62 description = lib.mdDoc ''
63 The port on which the tub will listen.
64
65 This is the correct setting to tweak if you want Tahoe's storage
66 system to listen on a different port.
67 '';
68 };
69 tub.location = mkOption {
70 default = null;
71 type = types.nullOr types.str;
72 description = lib.mdDoc ''
73 The external location that the node should listen on.
74
75 This is the setting to tweak if there are multiple interfaces
76 and you want to alter which interface Tahoe is advertising.
77
78 If specified, the port should be included.
79 '';
80 };
81 web.port = mkOption {
82 default = 3456;
83 type = types.port;
84 description = lib.mdDoc ''
85 The port on which the Web server will listen.
86
87 This is the correct setting to tweak if you want Tahoe's WUI to
88 listen on a different port.
89 '';
90 };
91 client.introducer = mkOption {
92 default = null;
93 type = types.nullOr types.str;
94 description = lib.mdDoc ''
95 The furl for a Tahoe introducer node.
96
97 Like all furls, keep this safe and don't share it.
98 '';
99 };
100 client.helper = mkOption {
101 default = null;
102 type = types.nullOr types.str;
103 description = lib.mdDoc ''
104 The furl for a Tahoe helper node.
105
106 Like all furls, keep this safe and don't share it.
107 '';
108 };
109 client.shares.needed = mkOption {
110 default = 3;
111 type = types.int;
112 description = lib.mdDoc ''
113 The number of shares required to reconstitute a file.
114 '';
115 };
116 client.shares.happy = mkOption {
117 default = 7;
118 type = types.int;
119 description = lib.mdDoc ''
120 The number of distinct storage nodes required to store
121 a file.
122 '';
123 };
124 client.shares.total = mkOption {
125 default = 10;
126 type = types.int;
127 description = lib.mdDoc ''
128 The number of shares required to store a file.
129 '';
130 };
131 storage.enable = mkEnableOption (lib.mdDoc "storage service");
132 storage.reservedSpace = mkOption {
133 default = "1G";
134 type = types.str;
135 description = lib.mdDoc ''
136 The amount of filesystem space to not use for storage.
137 '';
138 };
139 helper.enable = mkEnableOption (lib.mdDoc "helper service");
140 sftpd.enable = mkEnableOption (lib.mdDoc "SFTP service");
141 sftpd.port = mkOption {
142 default = null;
143 type = types.nullOr types.int;
144 description = lib.mdDoc ''
145 The port on which the SFTP server will listen.
146
147 This is the correct setting to tweak if you want Tahoe's SFTP
148 daemon to listen on a different port.
149 '';
150 };
151 sftpd.hostPublicKeyFile = mkOption {
152 default = null;
153 type = types.nullOr types.path;
154 description = lib.mdDoc ''
155 Path to the SSH host public key.
156 '';
157 };
158 sftpd.hostPrivateKeyFile = mkOption {
159 default = null;
160 type = types.nullOr types.path;
161 description = lib.mdDoc ''
162 Path to the SSH host private key.
163 '';
164 };
165 sftpd.accounts.file = mkOption {
166 default = null;
167 type = types.nullOr types.path;
168 description = lib.mdDoc ''
169 Path to the accounts file.
170 '';
171 };
172 sftpd.accounts.url = mkOption {
173 default = null;
174 type = types.nullOr types.str;
175 description = lib.mdDoc ''
176 URL of the accounts server.
177 '';
178 };
179 package = mkOption {
180 default = pkgs.tahoelafs;
181 defaultText = literalExpression "pkgs.tahoelafs";
182 type = types.package;
183 description = lib.mdDoc ''
184 The package to use for the Tahoe LAFS daemon.
185 '';
186 };
187 };
188 });
189 description = lib.mdDoc ''
190 The Tahoe nodes.
191 '';
192 };
193 };
194 config = mkMerge [
195 (mkIf (cfg.introducers != {}) {
196 environment = {
197 etc = flip mapAttrs' cfg.introducers (node: settings:
198 nameValuePair "tahoe-lafs/introducer-${node}.cfg" {
199 mode = "0444";
200 text = ''
201 # This configuration is generated by Nix. Edit at your own
202 # peril; here be dragons.
203
204 [node]
205 nickname = ${settings.nickname}
206 tub.port = ${toString settings.tub.port}
207 ${optionalString (settings.tub.location != null)
208 "tub.location = ${settings.tub.location}"}
209 '';
210 });
211 # Actually require Tahoe, so that we will have it installed.
212 systemPackages = flip mapAttrsToList cfg.introducers (node: settings:
213 settings.package
214 );
215 };
216 # Open up the firewall.
217 # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.introducers
218 # (node: settings: settings.tub.port);
219 systemd.services = flip mapAttrs' cfg.introducers (node: settings:
220 let
221 pidfile = "/run/tahoe.introducer-${node}.pid";
222 # This is a directory, but it has no trailing slash. Tahoe commands
223 # get antsy when there's a trailing slash.
224 nodedir = "/var/db/tahoe-lafs/introducer-${node}";
225 in nameValuePair "tahoe.introducer-${node}" {
226 description = "Tahoe LAFS node ${node}";
227 wantedBy = [ "multi-user.target" ];
228 path = [ settings.package ];
229 restartTriggers = [
230 config.environment.etc."tahoe-lafs/introducer-${node}.cfg".source ];
231 serviceConfig = {
232 Type = "simple";
233 PIDFile = pidfile;
234 # Believe it or not, Tahoe is very brittle about the order of
235 # arguments to $(tahoe run). The node directory must come first,
236 # and arguments which alter Twisted's behavior come afterwards.
237 ExecStart = ''
238 ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
239 '';
240 };
241 preStart = ''
242 if [ ! -d ${lib.escapeShellArg nodedir} ]; then
243 mkdir -p /var/db/tahoe-lafs
244 # See https://github.com/NixOS/nixpkgs/issues/25273
245 tahoe create-introducer \
246 --hostname="${config.networking.hostName}" \
247 ${lib.escapeShellArg nodedir}
248 fi
249
250 # Tahoe has created a predefined tahoe.cfg which we must now
251 # scribble over.
252 # XXX I thought that a symlink would work here, but it doesn't, so
253 # we must do this on every prestart. Fixes welcome.
254 # rm ${nodedir}/tahoe.cfg
255 # ln -s /etc/tahoe-lafs/introducer-${node}.cfg ${nodedir}/tahoe.cfg
256 cp /etc/tahoe-lafs/introducer-"${node}".cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
257 '';
258 });
259 users.users = flip mapAttrs' cfg.introducers (node: _:
260 nameValuePair "tahoe.introducer-${node}" {
261 description = "Tahoe node user for introducer ${node}";
262 isSystemUser = true;
263 });
264 })
265 (mkIf (cfg.nodes != {}) {
266 environment = {
267 etc = flip mapAttrs' cfg.nodes (node: settings:
268 nameValuePair "tahoe-lafs/${node}.cfg" {
269 mode = "0444";
270 text = ''
271 # This configuration is generated by Nix. Edit at your own
272 # peril; here be dragons.
273
274 [node]
275 nickname = ${settings.nickname}
276 tub.port = ${toString settings.tub.port}
277 ${optionalString (settings.tub.location != null)
278 "tub.location = ${settings.tub.location}"}
279 # This is a Twisted endpoint. Twisted Web doesn't work on
280 # non-TCP. ~ C.
281 web.port = tcp:${toString settings.web.port}
282
283 [client]
284 ${optionalString (settings.client.introducer != null)
285 "introducer.furl = ${settings.client.introducer}"}
286 ${optionalString (settings.client.helper != null)
287 "helper.furl = ${settings.client.helper}"}
288
289 shares.needed = ${toString settings.client.shares.needed}
290 shares.happy = ${toString settings.client.shares.happy}
291 shares.total = ${toString settings.client.shares.total}
292
293 [storage]
294 enabled = ${boolToString settings.storage.enable}
295 reserved_space = ${settings.storage.reservedSpace}
296
297 [helper]
298 enabled = ${boolToString settings.helper.enable}
299
300 [sftpd]
301 enabled = ${boolToString settings.sftpd.enable}
302 ${optionalString (settings.sftpd.port != null)
303 "port = ${toString settings.sftpd.port}"}
304 ${optionalString (settings.sftpd.hostPublicKeyFile != null)
305 "host_pubkey_file = ${settings.sftpd.hostPublicKeyFile}"}
306 ${optionalString (settings.sftpd.hostPrivateKeyFile != null)
307 "host_privkey_file = ${settings.sftpd.hostPrivateKeyFile}"}
308 ${optionalString (settings.sftpd.accounts.file != null)
309 "accounts.file = ${settings.sftpd.accounts.file}"}
310 ${optionalString (settings.sftpd.accounts.url != null)
311 "accounts.url = ${settings.sftpd.accounts.url}"}
312 '';
313 });
314 # Actually require Tahoe, so that we will have it installed.
315 systemPackages = flip mapAttrsToList cfg.nodes (node: settings:
316 settings.package
317 );
318 };
319 # Open up the firewall.
320 # networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.nodes
321 # (node: settings: settings.tub.port);
322 systemd.services = flip mapAttrs' cfg.nodes (node: settings:
323 let
324 pidfile = "/run/tahoe.${node}.pid";
325 # This is a directory, but it has no trailing slash. Tahoe commands
326 # get antsy when there's a trailing slash.
327 nodedir = "/var/db/tahoe-lafs/${node}";
328 in nameValuePair "tahoe.${node}" {
329 description = "Tahoe LAFS node ${node}";
330 wantedBy = [ "multi-user.target" ];
331 path = [ settings.package ];
332 restartTriggers = [
333 config.environment.etc."tahoe-lafs/${node}.cfg".source ];
334 serviceConfig = {
335 Type = "simple";
336 PIDFile = pidfile;
337 # Believe it or not, Tahoe is very brittle about the order of
338 # arguments to $(tahoe run). The node directory must come first,
339 # and arguments which alter Twisted's behavior come afterwards.
340 ExecStart = ''
341 ${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
342 '';
343 };
344 preStart = ''
345 if [ ! -d ${lib.escapeShellArg nodedir} ]; then
346 mkdir -p /var/db/tahoe-lafs
347 tahoe create-node --hostname=localhost ${lib.escapeShellArg nodedir}
348 fi
349
350 # Tahoe has created a predefined tahoe.cfg which we must now
351 # scribble over.
352 # XXX I thought that a symlink would work here, but it doesn't, so
353 # we must do this on every prestart. Fixes welcome.
354 # rm ${nodedir}/tahoe.cfg
355 # ln -s /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${nodedir}/tahoe.cfg
356 cp /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
357 '';
358 });
359 users.users = flip mapAttrs' cfg.nodes (node: _:
360 nameValuePair "tahoe.${node}" {
361 description = "Tahoe node user for node ${node}";
362 isSystemUser = true;
363 });
364 })
365 ];
366 }