dotfiles/hosts/soryu/tahoe.nix

294 lines
13 KiB
Nix

{ config, lib, pkgs, ... }:
with lib;
let
cfg = config.services.tahoe;
format = pkgs.formats.ini { };
in
{
options.services.tahoe = {
introducers = mkOption {
default = {};
type = with types; attrsOf (submodule {
options = {
settings = mkOption {
type = types.submodule {
freeformType = format.type;
options = {
node.nickname = mkOption {
type = types.str;
description = "The nickname of this Tahoe introducer.";
};
node."tub.port" = mkOption {
default = 3458;
type = types.port;
description = "The port on which the introducer will listen.";
};
node."tub.location" = mkOption {
type = types.nullOr types.str;
description = ''
The external location that the introducer should listen on.
If specified, the port should be included.
'';
};
};
};
description = "Freeform settings for the introducer";
};
package = mkOption {
default = pkgs.tahoe-lafs;
defaultText = literalExpression "pkgs.tahoe-lafs";
type = types.package;
description = "The package to use for the Tahoe LAFS daemon.";
};
};
});
description = lib.mdDoc "The Tahoe introducers.";
};
nodes = mkOption {
default = {};
type = with types; attrsOf (submodule ({name, config, ...}: {
options = {
settings = mkOption {
type = types.submodule {
freeformType = format.type;
options = {
node.nickname = mkOption {
type = types.str;
description = "Value to display in management tools.";
default = name;
};
node."tub.port" = mkOption {
type = types.oneOf [ types.str types.port (types.enum [ "disabled" null ]) ];
description = "A twisted server endpoint specification for receiving connections from other nodes.";
example = "tcp:12345:interface=127.0.0.1";
default = 3457;
};
node."tub.location" = mkOption {
type = types.either types.str (types.enum [ "disabled" null ]);
description = "comma separated connection strings that can be reached publically.";
example = "tcp:mynode.example.com:3457,AUTO";
default = "AUTO";
};
node."web.port" = mkOption {
type = types.nullOr (types.either types.str types.port);
description = "Twisted strport specification for webui and REST-api.";
example = "tcp:3456:interface=127.0.0.1";
default = 3456;
};
client."shares.needed" = mkOption {
type = types.ints.between 1 256;
description = "Default amount of shares needed to reconstruct an uploaded file.";
default = 3;
};
client."shares.total" = mkOption {
type = types.ints.between 1 256;
description = "Default amount of shares a file is split into.";
default = 10;
};
client."shares.happy" = mkOption {
type = types.ints.positive;
description = ''
How spread out should your shares be.
Can be smaller than needed, but not more than amount of servers available.";
'';
default = 7;
};
client."mutable.format" = mkOption {
type = types.enum [ "sdmf" "mdmf" ];
description = ''
What format to save mutable files in.
SDMF is useful when some nodes on your network run an older version of Tahoe-LAFS.
MDMF supports inplace modification and streaming downloads.
'';
default = "sdmf";
};
storage.enabled = mkEnableOption "storage service";
storage.anonymous = mkOption {
type = types.bool;
description = "Whether to expose storage with just the FURL and no other authentication.";
default = true;
};
storage.reserved_space = mkOption {
type = types.str;
description = "The minimum amount of free disk space to keep.";
default = "1G";
};
helper.enabled = mkEnableOption "helper service";
sftpd.enabled = mkEnableOption "sftpd service";
sftpd.port = mkOption {
type = types.nullOr types.str;
description = "A twisted connection string to listen on for the sftpd service.";
example = "tcp:8022:interface=127.0.0.1";
default = null;
};
sftpd.host_pubkey_file = mkOption {
type = types.nullOr types.path;
description = "Path to ssh public key to use for the service.";
default = null;
};
sftpd.host_privkey_file = mkOption {
type = types.nullOr types.path;
description = "Path to ssh private key to use for the service.";
default = null;
};
};
};
description = "freeform options for a normal tahoe-lafs node";
};
client.introducersFile = mkOption {
type = types.nullOr types.path;
description = "Path to a secret file containing introducers, will be placed in private/introducers.yaml";
default = null;
};
client.helperFile = mkOption {
type = types.nullOr types.path;
description = "Secret file containing a furl to use as a helper.";
default = null;
};
sftpd.accountsFile = mkOption {
type = types.nullOr types.path;
description = "Path to the accounts file. Will be copied to private/accounts";
default = null;
};
package = mkOption {
default = pkgs.tahoe-lafs;
defaultText = literalExpression "pkgs.tahoelafs";
type = types.package;
description = lib.mdDoc ''
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";
source = format.generate "tahoe-lafs-introducer" settings.settings;
});
# Actually require Tahoe, so that we will have it installed.
systemPackages = flip mapAttrsToList cfg.introducers (node: settings:
settings.package
);
};
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;
# 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} --pidfile=${lib.escapeShellArg pidfile}
'';
};
preStart = ''
if [ ! -d ${lib.escapeShellArg nodedir} ]; then
mkdir -p /var/db/tahoe-lafs
# See https://github.com/NixOS/nixpkgs/issues/25273
tahoe create-introducer \
--hostname="${config.networking.hostName}" \
${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.nodes (node: _:
nameValuePair "tahoe.introducer-${node}" { });
})
(mkIf (cfg.nodes != {}) {
environment = {
etc = flip mapAttrs' cfg.nodes (node: settings:
nameValuePair "tahoe-lafs/${node}.cfg" {
mode = "0444";
source = let placeholderFile = lib.pipe settings.settings [
(s: lib.recursiveUpdate
(lib.optionalAttrs (settings.client.helperFile != null) { client."helper.furl" = "@CLIENT_HELPER_FURL@"; })
s)
];
in format.generate "tahoe-lafs-node" placeholderFile;
});
# Actually require Tahoe, so that we will have it installed.
# systemPackages = flip mapAttrsToList cfg.nodes (node: settings:
# settings.package
# );
};
systemd.services = flip mapAttrs' cfg.nodes (node: settings:
let
pidfile = "/run/tahoe.${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/${node}";
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 run ${lib.escapeShellArg nodedir} --pidfile=${lib.escapeShellArg pidfile}
'';
};
preStart = ''
if [ ! -d ${lib.escapeShellArg nodedir} ]; then
mkdir -p /var/db/tahoe-lafs
tahoe create-node --hostname=localhost ${lib.escapeShellArg nodedir}
fi
cp /etc/tahoe-lafs/${lib.escapeShellArg node}.cfg ${lib.escapeShellArg nodedir}/tahoe.cfg
'' + lib.optionalString (settings.client.helperFile != null) ''
${pkgs.replace-secret}/bin/replace-secret '@CLIENT_HELPER_FURL@' ${settings.client.helperFile} ${lib.escapeShellArg nodedir}/tahoe.cfg
'' + lib.optionalString (settings.client.introducersFile != null) ''
cp "${config.settings.client.introducersFile}" ${lib.escapeShellArg nodedir}/private/introducers.yaml
'' + lib.optionalString (settings.sftpd.accountsFile != null) ''
cp "${config.settings.client.introducersFile}" ${lib.escapeShellArg nodedir}/private/accounts
'';
});
users.users = flip mapAttrs' cfg.nodes (node: _:
nameValuePair "tahoe.${node}" {
description = "Tahoe node user for node ${node}";
isSystemUser = true;
group = "tahoe.${node}";
});
users.groups = flip mapAttrs' cfg.nodes (node: _:
nameValuePair "tahoe.${node}" { });
})
];
}