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