Newer
Older
# Copy/pasted from nixos/modules/services/network-filesystems/tahoe.nix :/ We
# require control over additional configuration options compared to upstream
# and it's not clear how to do this without duplicating everything.
{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.tahoe;
ini = pkgs.callPackage ../lib/ini.nix { };
# Upstream tahoe-lafs module conflicts with ours (since ours is a
# copy/paste/edit of upstream's...). Disable it.
#
# https://nixos.org/nixos/manual/#sec-replace-modules
disabledModules =
[ "services/network-filesystems/tahoe.nix"
];
options.services.tahoe = {
introducers = mkOption {
default = {};
type = with types; attrsOf (submodule {
options = {
nickname = mkOption {
type = types.str;
description = ''
The nickname of this Tahoe introducer.
'';
};
tub.port = mkOption {
default = 3458;
type = types.int;
description = ''
The port on which the introducer will listen.
'';
};
tub.location = mkOption {
default = null;
type = types.nullOr types.str;
description = ''
The external location that the introducer should listen on.
If specified, the port should be included.
'';
};
package = mkOption {
default = pkgs.tahoelafs;
defaultText = "pkgs.tahoelafs";
type = types.package;
description = ''
The package to use for the Tahoe LAFS daemon.
'';
};
};
});
description = ''
The Tahoe introducers.
'';
};
nodes = mkOption {
default = {};
type = with types; attrsOf (submodule {
options = {
sections = mkOption {
type = types.attrs;
description = ''
Top-level configuration sections.
'';
default = {
"node" = { };
"client" = { };
"storage" = { };
};
};
package = mkOption {
default = pkgs.tahoelafs;
defaultText = "pkgs.tahoelafs";
type = types.package;
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
description = ''
The package to use for the Tahoe LAFS daemon.
'';
};
};
});
description = ''
The Tahoe nodes.
'';
};
};
config = mkMerge [
(mkIf (cfg.introducers != {}) {
environment = {
etc = flip mapAttrs' cfg.introducers (node: settings:
nameValuePair "tahoe-lafs/introducer-${node}.cfg" {
mode = "0444";
text = ''
# This configuration is generated by Nix. Edit at your own
# peril; here be dragons.
[node]
nickname = ${settings.nickname}
tub.port = ${toString settings.tub.port}
${optionalString (settings.tub.location != null)
"tub.location = ${settings.tub.location}"}
'';
});
# Actually require Tahoe, so that we will have it installed.
systemPackages = flip mapAttrsToList cfg.introducers (node: settings:
settings.package
);
};
# Open up the firewall.
# networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.introducers
# (node: settings: settings.tub.port);
# Make systemd open a port for us:
systemd.sockets.tahoe-web = {
description = "Tahoe Web Server Socket";
wantedBy = [ "sockets.target" ];
before = [ "multi-user.target" ];
socketConfig.Accept = true;
socketConfig.ListenStream = settings.tub.port;
};
systemd.services = flip mapAttrs' cfg.introducers (node: settings:
let
pidfile = "/run/tahoe.introducer-${node}.pid";
# This is a directory, but it has no trailing slash. Tahoe commands
# get antsy when there's a trailing slash.
nodedir = "/var/db/tahoe-lafs/introducer-${node}";
in nameValuePair "tahoe.introducer-${node}" {
description = "Tahoe LAFS node ${node}";
wantedBy = [ "multi-user.target" ];
path = [ settings.package ];
restartTriggers = [
config.environment.etc."tahoe-lafs/introducer-${node}.cfg".source ];
serviceConfig = {
Type = "simple";
PIDFile = pidfile;
Twisted wants non-blocking sockets:
NonBlocking = true;
# Believe it or not, Tahoe is very brittle about the order of
# arguments to $(tahoe run). The node directory must come first,
# and arguments which alter Twisted's behavior come afterwards.
ExecStart = ''
${settings.package}/bin/tahoe run ${lib.escapeShellArg nodedir} -n -l- --pidfile=${lib.escapeShellArg pidfile}
'';
};
preStart = ''
if [ ! -d ${lib.escapeShellArg nodedir} ]; then
mkdir -p /var/db/tahoe-lafs
tahoe create-introducer ${lib.escapeShellArg nodedir}
fi
# Tahoe has created a predefined tahoe.cfg which we must now
# scribble over.
# XXX I thought that a symlink would work here, but it doesn't, so
# we must do this on every prestart. Fixes welcome.
# rm ${nodedir}/tahoe.cfg
# ln -s /etc/tahoe-lafs/introducer-${node}.cfg ${nodedir}/tahoe.cfg
cp /etc/tahoe-lafs/introducer-"${node}".cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
'';
});
users.users = flip mapAttrs' cfg.introducers (node: _:
nameValuePair "tahoe.introducer-${node}" {
description = "Tahoe node user for introducer ${node}";
isSystemUser = true;
group = "tahoe.introducer-${node}";
});
users.groups = flip mapAttrs' cfg.introducers (node: _:
nameValuePair "tahoe.introducer-${node}" {
});
})
(mkIf (cfg.nodes != {}) {
environment = {
etc = flip mapAttrs' cfg.nodes (node: settings:
nameValuePair "tahoe-lafs/${node}.cfg" {
mode = "0444";
text = ''
# This configuration is generated by Nix. Edit at your own
# peril; here be dragons.
${ini.allConfigSectionsText settings.sections}
'';
});
# Actually require Tahoe, so that we will have it installed.
systemPackages = flip mapAttrsToList cfg.nodes (node: settings:
settings.package
);
};
# Open up the firewall.
# networking.firewall.allowedTCPPorts = flip mapAttrsToList cfg.nodes
# (node: settings: settings.tub.port);
systemd.services = flip mapAttrs' cfg.nodes (node: settings:
let
pidfile = "/run/tahoe.${lib.escapeShellArg node}.pid";
# This is a directory, but it has no trailing slash. Tahoe commands
# get antsy when there's a trailing slash.
nodedir = "/var/db/tahoe-lafs/${lib.escapeShellArg node}";
eliotLog = "file:${nodedir}/logs/eliot.json,rotate_length=${toString (1024 * 1024 * 32)},max_rotated_files=32";
in nameValuePair "tahoe.${node}" {
description = "Tahoe LAFS node ${node}";
wantedBy = [ "multi-user.target" ];
path = [ settings.package ];
restartTriggers = [
config.environment.etc."tahoe-lafs/${node}.cfg".source ];
serviceConfig = {
Type = "simple";
PIDFile = pidfile;
# Believe it or not, Tahoe is very brittle about the order of
# arguments to $(tahoe run). The node directory must come first,
# and arguments which alter Twisted's behavior come afterwards.
ExecStart = ''
${settings.package}/bin/tahoe --eliot-destination ${eliotLog} run ${nodedir} -n -l- --pidfile=${pidfile}
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
# The rlimit on number of open files controls how many
# connections a particular storage server can accept (factoring
# in the number of non-connection files the server needs open -
# eg for logging, reading and writing shares, etc).
#
# Once the maximum number of open files, as controlled by rlimit
# is reached, service suffers dramatically. New connections
# cannot be accepted. Shares cannot be read or written.
#
# The default limit on open files is fairly low, perhaps 1024
# (2^10) or 8192 (2^13). This can easily be raised. If it is
# raised to 2^16 then the rlimit is approximately equal to the
# limit imposed by TCP (which only has around 2^16 ports
# available per IP address). If we want each connection to also
# be able to read or write a share file, a limit of 2^15 would
# allow this. Then, we should scale the limit linearly with the
# number of IP addresses available. If the service can be
# reached on 2 IP addresses, allow twice as many files (2^15 * 2
# = 2^16). If it can be reached on 3 IP addresses, (2^16 *
# 3). etc.
#
# Python also sometimes wants to open files as a side effect of
# other things going. For example, if there's a traceback, it
# opens the source files to read lines to put into the
# traceback. If random numbers are generated, /dev/urandom
# might be opened, etc. There is also some fixed overhead for
# listening ports and such. This currently doesn't factor into
# our choice but perhaps it could somehow.
#
# Tahoe-LAFS has no logic to raise soft limit to hard limit so
# make it the same.
#
# There is only one IPv4 address assigned to each host right
# now. So it makes sense to have the limit be 2^15 right now.
LimitNOFILE = 32768;
preStart =
let
created = "${nodedir}.created";
atomic = "${nodedir}.atomic";
in ''
set -eo pipefail
if [ ! -e ${created} ]; then
mkdir -p /var/db/tahoe-lafs/
# Get rid of any prior partial efforts. It might not exist.
# Don't let this tank us.
rm -rv ${atomic} || [ ! -e ${atomic} ]
# Really create the node.
tahoe create-node --hostname=localhost ${atomic}
# Get rid of any existing partially created node directory
# that might be in the way.
if [ -e ${nodedir} ]; then
for backup in $(seq 1 100); do
if [ ! -e ${nodedir}.$backup ]; then
mv ${nodedir} ${nodedir}.$backup
break
fi
done
fi
# Move the new thing to the real location. We don't create it
# in-place because we might fail partway through and leave
# inconsistent state. Also, systemd probably created
# logs/incidents/ already and `create-node` complains if it
# finds these exist already.
mv ${atomic} ${nodedir}
# Record our complete, consistent success.
fi
# Tahoe has created a predefined tahoe.cfg which we must now
# scribble over.
# XXX I thought that a symlink would work here, but it doesn't, so
# we must do this on every prestart. Fixes welcome.
# rm ${nodedir}/tahoe.cfg
# ln -s /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${nodedir}/tahoe.cfg
cp /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${nodedir}/tahoe.cfg
'';
});
users.users = flip mapAttrs' cfg.nodes (node: _:
nameValuePair "tahoe.${node}" {
description = "Tahoe node user for node ${node}";
isSystemUser = true;
users.groups = flip mapAttrs' cfg.nodes (node: _:
});
})
];
}