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