uboot: (firmwareOdroidC2/C4) don't invoke patch tool, use patches = [] instead
https://github.com/NixOS/nixpkgs/blob/master/pkgs/stdenv/generic/setup.sh#L948 this can do it nicely. Signed-off-by: Anton Arapov <anton@deadbeef.mx>
This commit is contained in:
commit
56de2bcd43
30691 changed files with 3076956 additions and 0 deletions
279
nixos/modules/services/torrent/deluge.nix
Normal file
279
nixos/modules/services/torrent/deluge.nix
Normal file
|
|
@ -0,0 +1,279 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.deluge;
|
||||
cfg_web = config.services.deluge.web;
|
||||
isDeluge1 = versionOlder cfg.package.version "2.0.0";
|
||||
|
||||
openFilesLimit = 4096;
|
||||
listenPortsDefault = [ 6881 6889 ];
|
||||
|
||||
listToRange = x: { from = elemAt x 0; to = elemAt x 1; };
|
||||
|
||||
configDir = "${cfg.dataDir}/.config/deluge";
|
||||
configFile = pkgs.writeText "core.conf" (builtins.toJSON cfg.config);
|
||||
declarativeLockFile = "${configDir}/.declarative";
|
||||
|
||||
preStart = if cfg.declarative then ''
|
||||
if [ -e ${declarativeLockFile} ]; then
|
||||
# Was declarative before, no need to back up anything
|
||||
${if isDeluge1 then "ln -sf" else "cp"} ${configFile} ${configDir}/core.conf
|
||||
ln -sf ${cfg.authFile} ${configDir}/auth
|
||||
else
|
||||
# Declarative for the first time, backup stateful files
|
||||
${if isDeluge1 then "ln -s" else "cp"} -b --suffix=.stateful ${configFile} ${configDir}/core.conf
|
||||
ln -sb --suffix=.stateful ${cfg.authFile} ${configDir}/auth
|
||||
echo "Autogenerated file that signifies that this server configuration is managed declaratively by NixOS" \
|
||||
> ${declarativeLockFile}
|
||||
fi
|
||||
'' else ''
|
||||
if [ -e ${declarativeLockFile} ]; then
|
||||
rm ${declarativeLockFile}
|
||||
fi
|
||||
'';
|
||||
in {
|
||||
options = {
|
||||
services = {
|
||||
deluge = {
|
||||
enable = mkEnableOption "Deluge daemon";
|
||||
|
||||
openFilesLimit = mkOption {
|
||||
default = openFilesLimit;
|
||||
type = types.either types.int types.str;
|
||||
description = ''
|
||||
Number of files to allow deluged to open.
|
||||
'';
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
type = types.attrs;
|
||||
default = {};
|
||||
example = literalExpression ''
|
||||
{
|
||||
download_location = "/srv/torrents/";
|
||||
max_upload_speed = "1000.0";
|
||||
share_ratio_limit = "2.0";
|
||||
allow_remote = true;
|
||||
daemon_port = 58846;
|
||||
listen_ports = [ ${toString listenPortsDefault} ];
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
Deluge core configuration for the core.conf file. Only has an effect
|
||||
when <option>services.deluge.declarative</option> is set to
|
||||
<literal>true</literal>. String values must be quoted, integer and
|
||||
boolean values must not. See
|
||||
<link xlink:href="https://git.deluge-torrent.org/deluge/tree/deluge/core/preferencesmanager.py#n41"/>
|
||||
for the availaible options.
|
||||
'';
|
||||
};
|
||||
|
||||
declarative = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to use a declarative deluge configuration.
|
||||
Only if set to <literal>true</literal>, the options
|
||||
<option>services.deluge.config</option>,
|
||||
<option>services.deluge.openFirewall</option> and
|
||||
<option>services.deluge.authFile</option> will be
|
||||
applied.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
default = false;
|
||||
type = types.bool;
|
||||
description = ''
|
||||
Whether to open the firewall for the ports in
|
||||
<option>services.deluge.config.listen_ports</option>. It only takes effet if
|
||||
<option>services.deluge.declarative</option> is set to
|
||||
<literal>true</literal>.
|
||||
|
||||
It does NOT apply to the daemon port nor the web UI port. To access those
|
||||
ports secuerly check the documentation
|
||||
<link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/ThinClient#CreateSSHTunnel"/>
|
||||
or use a VPN or configure certificates for deluge.
|
||||
'';
|
||||
};
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/deluge";
|
||||
description = ''
|
||||
The directory where deluge will create files.
|
||||
'';
|
||||
};
|
||||
|
||||
authFile = mkOption {
|
||||
type = types.path;
|
||||
example = "/run/keys/deluge-auth";
|
||||
description = ''
|
||||
The file managing the authentication for deluge, the format of this
|
||||
file is straightforward, each line contains a
|
||||
username:password:level tuple in plaintext. It only has an effect
|
||||
when <option>services.deluge.declarative</option> is set to
|
||||
<literal>true</literal>.
|
||||
See <link xlink:href="https://dev.deluge-torrent.org/wiki/UserGuide/Authentication"/> for
|
||||
more informations.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "deluge";
|
||||
description = ''
|
||||
User account under which deluge runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "deluge";
|
||||
description = ''
|
||||
Group under which deluge runs.
|
||||
'';
|
||||
};
|
||||
|
||||
extraPackages = mkOption {
|
||||
type = types.listOf types.package;
|
||||
default = [];
|
||||
description = ''
|
||||
Extra packages available at runtime to enable Deluge's plugins. For example,
|
||||
extraction utilities are required for the built-in "Extractor" plugin.
|
||||
This always contains unzip, gnutar, xz and bzip2.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
example = literalExpression "pkgs.deluge-2_x";
|
||||
description = ''
|
||||
Deluge package to use.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
deluge.web = {
|
||||
enable = mkEnableOption "Deluge Web daemon";
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 8112;
|
||||
description = ''
|
||||
Deluge web UI port.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Open ports in the firewall for deluge web daemon
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
services.deluge.package = mkDefault (
|
||||
if versionAtLeast config.system.stateVersion "20.09" then
|
||||
pkgs.deluge-2_x
|
||||
else
|
||||
# deluge-1_x is no longer packaged and this will resolve to an error
|
||||
# thanks to the alias for this name. This is left here so that anyone
|
||||
# using NixOS older than 20.09 receives that error when they upgrade
|
||||
# and is forced to make an intentional choice to switch to deluge-2_x.
|
||||
# That might be slightly inconvenient but there is no path to
|
||||
# downgrade from 2.x to 1.x so NixOS should not automatically perform
|
||||
# this state migration.
|
||||
pkgs.deluge-1_x
|
||||
);
|
||||
|
||||
# Provide a default set of `extraPackages`.
|
||||
services.deluge.extraPackages = with pkgs; [ unzip gnutar xz bzip2 ];
|
||||
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.dataDir}' 0770 ${cfg.user} ${cfg.group}"
|
||||
"d '${cfg.dataDir}/.config' 0770 ${cfg.user} ${cfg.group}"
|
||||
"d '${cfg.dataDir}/.config/deluge' 0770 ${cfg.user} ${cfg.group}"
|
||||
]
|
||||
++ optional (cfg.config ? download_location)
|
||||
"d '${cfg.config.download_location}' 0770 ${cfg.user} ${cfg.group}"
|
||||
++ optional (cfg.config ? torrentfiles_location)
|
||||
"d '${cfg.config.torrentfiles_location}' 0770 ${cfg.user} ${cfg.group}"
|
||||
++ optional (cfg.config ? move_completed_path)
|
||||
"d '${cfg.config.move_completed_path}' 0770 ${cfg.user} ${cfg.group}";
|
||||
|
||||
systemd.services.deluged = {
|
||||
after = [ "network.target" ];
|
||||
description = "Deluge BitTorrent Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ cfg.package ] ++ cfg.extraPackages;
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${cfg.package}/bin/deluged \
|
||||
--do-not-daemonize \
|
||||
--config ${configDir}
|
||||
'';
|
||||
# To prevent "Quit & shutdown daemon" from working; we want systemd to
|
||||
# manage it!
|
||||
Restart = "on-success";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
UMask = "0002";
|
||||
LimitNOFILE = cfg.openFilesLimit;
|
||||
};
|
||||
preStart = preStart;
|
||||
};
|
||||
|
||||
systemd.services.delugeweb = mkIf cfg_web.enable {
|
||||
after = [ "network.target" "deluged.service"];
|
||||
requires = [ "deluged.service" ];
|
||||
description = "Deluge BitTorrent WebUI";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
path = [ cfg.package ];
|
||||
serviceConfig = {
|
||||
ExecStart = ''
|
||||
${cfg.package}/bin/deluge-web \
|
||||
${optionalString (!isDeluge1) "--do-not-daemonize"} \
|
||||
--config ${configDir} \
|
||||
--port ${toString cfg.web.port}
|
||||
'';
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall = mkMerge [
|
||||
(mkIf (cfg.declarative && cfg.openFirewall && !(cfg.config.random_port or true)) {
|
||||
allowedTCPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
|
||||
allowedUDPPortRanges = singleton (listToRange (cfg.config.listen_ports or listenPortsDefault));
|
||||
})
|
||||
(mkIf (cfg.web.openFirewall) {
|
||||
allowedTCPPorts = [ cfg.web.port ];
|
||||
})
|
||||
];
|
||||
|
||||
environment.systemPackages = [ cfg.package ];
|
||||
|
||||
users.users = mkIf (cfg.user == "deluge") {
|
||||
deluge = {
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.deluge;
|
||||
home = cfg.dataDir;
|
||||
description = "Deluge Daemon user";
|
||||
};
|
||||
};
|
||||
|
||||
users.groups = mkIf (cfg.group == "deluge") {
|
||||
deluge = {
|
||||
gid = config.ids.gids.deluge;
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
100
nixos/modules/services/torrent/flexget.nix
Normal file
100
nixos/modules/services/torrent/flexget.nix
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.flexget;
|
||||
pkg = pkgs.flexget;
|
||||
ymlFile = pkgs.writeText "flexget.yml" ''
|
||||
${cfg.config}
|
||||
|
||||
${optionalString cfg.systemScheduler "schedules: no"}
|
||||
'';
|
||||
configFile = "${toString cfg.homeDir}/flexget.yml";
|
||||
in {
|
||||
options = {
|
||||
services.flexget = {
|
||||
enable = mkEnableOption "Run FlexGet Daemon";
|
||||
|
||||
user = mkOption {
|
||||
default = "deluge";
|
||||
example = "some_user";
|
||||
type = types.str;
|
||||
description = "The user under which to run flexget.";
|
||||
};
|
||||
|
||||
homeDir = mkOption {
|
||||
default = "/var/lib/deluge";
|
||||
example = "/home/flexget";
|
||||
type = types.path;
|
||||
description = "Where files live.";
|
||||
};
|
||||
|
||||
interval = mkOption {
|
||||
default = "10m";
|
||||
example = "1h";
|
||||
type = types.str;
|
||||
description = "When to perform a <command>flexget</command> run. See <command>man 7 systemd.time</command> for the format.";
|
||||
};
|
||||
|
||||
systemScheduler = mkOption {
|
||||
default = true;
|
||||
example = false;
|
||||
type = types.bool;
|
||||
description = "When true, execute the runs via the flexget-runner.timer. If false, you have to specify the settings yourself in the YML file.";
|
||||
};
|
||||
|
||||
config = mkOption {
|
||||
default = "";
|
||||
type = types.lines;
|
||||
description = "The YAML configuration for FlexGet.";
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
environment.systemPackages = [ pkg ];
|
||||
|
||||
systemd.services = {
|
||||
flexget = {
|
||||
description = "FlexGet Daemon";
|
||||
path = [ pkg ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Environment = "TZ=${config.time.timeZone}";
|
||||
ExecStartPre = "${pkgs.coreutils}/bin/install -m644 ${ymlFile} ${configFile}";
|
||||
ExecStart = "${pkg}/bin/flexget -c ${configFile} daemon start";
|
||||
ExecStop = "${pkg}/bin/flexget -c ${configFile} daemon stop";
|
||||
ExecReload = "${pkg}/bin/flexget -c ${configFile} daemon reload";
|
||||
Restart = "on-failure";
|
||||
PrivateTmp = true;
|
||||
WorkingDirectory = toString cfg.homeDir;
|
||||
};
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
};
|
||||
|
||||
flexget-runner = mkIf cfg.systemScheduler {
|
||||
description = "FlexGet Runner";
|
||||
after = [ "flexget.service" ];
|
||||
wants = [ "flexget.service" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
ExecStart = "${pkg}/bin/flexget -c ${configFile} execute";
|
||||
PrivateTmp = true;
|
||||
WorkingDirectory = toString cfg.homeDir;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
systemd.timers.flexget-runner = mkIf cfg.systemScheduler {
|
||||
description = "Run FlexGet every ${cfg.interval}";
|
||||
wantedBy = [ "timers.target" ];
|
||||
timerConfig = {
|
||||
OnBootSec = "5m";
|
||||
OnUnitInactiveSec = cfg.interval;
|
||||
Unit = "flexget-runner.service";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
220
nixos/modules/services/torrent/magnetico.nix
Normal file
220
nixos/modules/services/torrent/magnetico.nix
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.magnetico;
|
||||
|
||||
dataDir = "/var/lib/magnetico";
|
||||
|
||||
credFile = with cfg.web;
|
||||
if credentialsFile != null
|
||||
then credentialsFile
|
||||
else pkgs.writeText "magnetico-credentials"
|
||||
(concatStrings (mapAttrsToList
|
||||
(user: hash: "${user}:${hash}\n")
|
||||
cfg.web.credentials));
|
||||
|
||||
# default options in magneticod/main.go
|
||||
dbURI = concatStrings
|
||||
[ "sqlite3://${dataDir}/database.sqlite3"
|
||||
"?_journal_mode=WAL"
|
||||
"&_busy_timeout=3000"
|
||||
"&_foreign_keys=true"
|
||||
];
|
||||
|
||||
crawlerArgs = with cfg.crawler; escapeShellArgs
|
||||
([ "--database=${dbURI}"
|
||||
"--indexer-addr=${address}:${toString port}"
|
||||
"--indexer-max-neighbors=${toString maxNeighbors}"
|
||||
"--leech-max-n=${toString maxLeeches}"
|
||||
] ++ extraOptions);
|
||||
|
||||
webArgs = with cfg.web; escapeShellArgs
|
||||
([ "--database=${dbURI}"
|
||||
(if (cfg.web.credentialsFile != null || cfg.web.credentials != { })
|
||||
then "--credentials=${toString credFile}"
|
||||
else "--no-auth")
|
||||
"--addr=${address}:${toString port}"
|
||||
] ++ extraOptions);
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options.services.magnetico = {
|
||||
enable = mkEnableOption "Magnetico, Bittorrent DHT crawler";
|
||||
|
||||
crawler.address = mkOption {
|
||||
type = types.str;
|
||||
default = "0.0.0.0";
|
||||
example = "1.2.3.4";
|
||||
description = ''
|
||||
Address to be used for indexing DHT nodes.
|
||||
'';
|
||||
};
|
||||
|
||||
crawler.port = mkOption {
|
||||
type = types.port;
|
||||
default = 0;
|
||||
description = ''
|
||||
Port to be used for indexing DHT nodes.
|
||||
This port should be added to
|
||||
<option>networking.firewall.allowedTCPPorts</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
crawler.maxNeighbors = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 1000;
|
||||
description = ''
|
||||
Maximum number of simultaneous neighbors of an indexer.
|
||||
Be careful changing this number: high values can very
|
||||
easily cause your network to be congested or even crash
|
||||
your router.
|
||||
'';
|
||||
};
|
||||
|
||||
crawler.maxLeeches = mkOption {
|
||||
type = types.ints.positive;
|
||||
default = 200;
|
||||
description = ''
|
||||
Maximum number of simultaneous leeches.
|
||||
'';
|
||||
};
|
||||
|
||||
crawler.extraOptions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Extra command line arguments to pass to magneticod.
|
||||
'';
|
||||
};
|
||||
|
||||
web.address = mkOption {
|
||||
type = types.str;
|
||||
default = "localhost";
|
||||
example = "1.2.3.4";
|
||||
description = ''
|
||||
Address the web interface will listen to.
|
||||
'';
|
||||
};
|
||||
|
||||
web.port = mkOption {
|
||||
type = types.port;
|
||||
default = 8080;
|
||||
description = ''
|
||||
Port the web interface will listen to.
|
||||
'';
|
||||
};
|
||||
|
||||
web.credentials = mkOption {
|
||||
type = types.attrsOf types.str;
|
||||
default = {};
|
||||
example = lib.literalExpression ''
|
||||
{
|
||||
myuser = "$2y$12$YE01LZ8jrbQbx6c0s2hdZO71dSjn2p/O9XsYJpz.5968yCysUgiaG";
|
||||
}
|
||||
'';
|
||||
description = ''
|
||||
The credentials to access the web interface, in case authentication is
|
||||
enabled, in the format <literal>username:hash</literal>. If unset no
|
||||
authentication will be required.
|
||||
|
||||
Usernames must start with a lowercase ([a-z]) ASCII character, might
|
||||
contain non-consecutive underscores except at the end, and consists of
|
||||
small-case a-z characters and digits 0-9. The
|
||||
<command>htpasswd</command> tool from the <package>apacheHttpd
|
||||
</package> package may be used to generate the hash: <command>htpasswd
|
||||
-bnBC 12 username password</command>
|
||||
|
||||
<warning>
|
||||
<para>
|
||||
The hashes will be stored world-readable in the nix store.
|
||||
Consider using the <literal>credentialsFile</literal> option if you
|
||||
don't want this.
|
||||
</para>
|
||||
</warning>
|
||||
'';
|
||||
};
|
||||
|
||||
web.credentialsFile = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = ''
|
||||
The path to the file holding the credentials to access the web
|
||||
interface. If unset no authentication will be required.
|
||||
|
||||
The file must constain user names and password hashes in the format
|
||||
<literal>username:hash </literal>, one for each line. Usernames must
|
||||
start with a lowecase ([a-z]) ASCII character, might contain
|
||||
non-consecutive underscores except at the end, and consists of
|
||||
small-case a-z characters and digits 0-9.
|
||||
The <command>htpasswd</command> tool from the <package>apacheHttpd
|
||||
</package> package may be used to generate the hash:
|
||||
<command>htpasswd -bnBC 12 username password</command>
|
||||
'';
|
||||
};
|
||||
|
||||
web.extraOptions = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
description = ''
|
||||
Extra command line arguments to pass to magneticow.
|
||||
'';
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.users.magnetico = {
|
||||
description = "Magnetico daemons user";
|
||||
group = "magnetico";
|
||||
isSystemUser = true;
|
||||
};
|
||||
users.groups.magnetico = {};
|
||||
|
||||
systemd.services.magneticod = {
|
||||
description = "Magnetico DHT crawler";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
|
||||
serviceConfig = {
|
||||
User = "magnetico";
|
||||
Restart = "on-failure";
|
||||
ExecStart = "${pkgs.magnetico}/bin/magneticod ${crawlerArgs}";
|
||||
};
|
||||
};
|
||||
|
||||
systemd.services.magneticow = {
|
||||
description = "Magnetico web interface";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" "magneticod.service"];
|
||||
|
||||
serviceConfig = {
|
||||
User = "magnetico";
|
||||
StateDirectory = "magnetico";
|
||||
Restart = "on-failure";
|
||||
ExecStart = "${pkgs.magnetico}/bin/magneticow ${webArgs}";
|
||||
};
|
||||
};
|
||||
|
||||
assertions =
|
||||
[
|
||||
{
|
||||
assertion = cfg.web.credentialsFile == null || cfg.web.credentials == { };
|
||||
message = ''
|
||||
The options services.magnetico.web.credentialsFile and
|
||||
services.magnetico.web.credentials are mutually exclusives.
|
||||
'';
|
||||
}
|
||||
];
|
||||
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ rnhmjoj ];
|
||||
|
||||
}
|
||||
45
nixos/modules/services/torrent/opentracker.nix
Normal file
45
nixos/modules/services/torrent/opentracker.nix
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
{ config, lib, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
let
|
||||
cfg = config.services.opentracker;
|
||||
in {
|
||||
options.services.opentracker = {
|
||||
enable = mkEnableOption "opentracker";
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
description = ''
|
||||
opentracker package to use
|
||||
'';
|
||||
default = pkgs.opentracker;
|
||||
defaultText = literalExpression "pkgs.opentracker";
|
||||
};
|
||||
|
||||
extraOptions = mkOption {
|
||||
type = types.separatedString " ";
|
||||
description = ''
|
||||
Configuration Arguments for opentracker
|
||||
See https://erdgeist.org/arts/software/opentracker/ for all params
|
||||
'';
|
||||
default = "";
|
||||
};
|
||||
};
|
||||
|
||||
config = lib.mkIf cfg.enable {
|
||||
|
||||
systemd.services.opentracker = {
|
||||
description = "opentracker server";
|
||||
after = [ "network.target" ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
restartIfChanged = true;
|
||||
serviceConfig = {
|
||||
ExecStart = "${cfg.package}/bin/opentracker ${cfg.extraOptions}";
|
||||
PrivateTmp = true;
|
||||
WorkingDirectory = "/var/empty";
|
||||
# By default opentracker drops all privileges and runs in chroot after starting up as root.
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
71
nixos/modules/services/torrent/peerflix.nix
Normal file
71
nixos/modules/services/torrent/peerflix.nix
Normal file
|
|
@ -0,0 +1,71 @@
|
|||
{ config, lib, options, pkgs, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.peerflix;
|
||||
opt = options.services.peerflix;
|
||||
|
||||
configFile = pkgs.writeText "peerflix-config.json" ''
|
||||
{
|
||||
"connections": 50,
|
||||
"tmp": "${cfg.downloadDir}"
|
||||
}
|
||||
'';
|
||||
|
||||
in {
|
||||
|
||||
###### interface
|
||||
|
||||
options.services.peerflix = {
|
||||
enable = mkOption {
|
||||
description = "Whether to enable peerflix service.";
|
||||
default = false;
|
||||
type = types.bool;
|
||||
};
|
||||
|
||||
stateDir = mkOption {
|
||||
description = "Peerflix state directory.";
|
||||
default = "/var/lib/peerflix";
|
||||
type = types.path;
|
||||
};
|
||||
|
||||
downloadDir = mkOption {
|
||||
description = "Peerflix temporary download directory.";
|
||||
default = "${cfg.stateDir}/torrents";
|
||||
defaultText = literalExpression ''"''${config.${opt.stateDir}}/torrents"'';
|
||||
type = types.path;
|
||||
};
|
||||
};
|
||||
|
||||
###### implementation
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
systemd.tmpfiles.rules = [
|
||||
"d '${cfg.stateDir}' - peerflix - - -"
|
||||
];
|
||||
|
||||
systemd.services.peerflix = {
|
||||
description = "Peerflix Daemon";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
after = [ "network.target" ];
|
||||
environment.HOME = cfg.stateDir;
|
||||
|
||||
preStart = ''
|
||||
mkdir -p "${cfg.stateDir}"/{torrents,.config/peerflix-server}
|
||||
ln -fs "${configFile}" "${cfg.stateDir}/.config/peerflix-server/config.json"
|
||||
'';
|
||||
|
||||
serviceConfig = {
|
||||
ExecStart = "${pkgs.nodePackages.peerflix-server}/bin/peerflix-server";
|
||||
User = "peerflix";
|
||||
};
|
||||
};
|
||||
|
||||
users.users.peerflix = {
|
||||
isSystemUser = true;
|
||||
group = "peerflix";
|
||||
};
|
||||
users.groups.peerflix = {};
|
||||
};
|
||||
}
|
||||
211
nixos/modules/services/torrent/rtorrent.nix
Normal file
211
nixos/modules/services/torrent/rtorrent.nix
Normal file
|
|
@ -0,0 +1,211 @@
|
|||
{ config, options, pkgs, lib, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
|
||||
cfg = config.services.rtorrent;
|
||||
opt = options.services.rtorrent;
|
||||
|
||||
in {
|
||||
options.services.rtorrent = {
|
||||
enable = mkEnableOption "rtorrent";
|
||||
|
||||
dataDir = mkOption {
|
||||
type = types.str;
|
||||
default = "/var/lib/rtorrent";
|
||||
description = ''
|
||||
The directory where rtorrent stores its data files.
|
||||
'';
|
||||
};
|
||||
|
||||
downloadDir = mkOption {
|
||||
type = types.str;
|
||||
default = "${cfg.dataDir}/download";
|
||||
defaultText = literalExpression ''"''${config.${opt.dataDir}}/download"'';
|
||||
description = ''
|
||||
Where to put downloaded files.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "rtorrent";
|
||||
description = ''
|
||||
User account under which rtorrent runs.
|
||||
'';
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "rtorrent";
|
||||
description = ''
|
||||
Group under which rtorrent runs.
|
||||
'';
|
||||
};
|
||||
|
||||
package = mkOption {
|
||||
type = types.package;
|
||||
default = pkgs.rtorrent;
|
||||
defaultText = literalExpression "pkgs.rtorrent";
|
||||
description = ''
|
||||
The rtorrent package to use.
|
||||
'';
|
||||
};
|
||||
|
||||
port = mkOption {
|
||||
type = types.port;
|
||||
default = 50000;
|
||||
description = ''
|
||||
The rtorrent port.
|
||||
'';
|
||||
};
|
||||
|
||||
openFirewall = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to open the firewall for the port in <option>services.rtorrent.port</option>.
|
||||
'';
|
||||
};
|
||||
|
||||
rpcSocket = mkOption {
|
||||
type = types.str;
|
||||
readOnly = true;
|
||||
default = "/run/rtorrent/rpc.sock";
|
||||
description = ''
|
||||
RPC socket path.
|
||||
'';
|
||||
};
|
||||
|
||||
configText = mkOption {
|
||||
type = types.lines;
|
||||
default = "";
|
||||
description = ''
|
||||
The content of <filename>rtorrent.rc</filename>. The <link xlink:href="https://rtorrent-docs.readthedocs.io/en/latest/cookbook.html#modernized-configuration-template">modernized configuration template</link> with the values specified in this module will be prepended using mkBefore. You can use mkForce to overwrite the config completly.
|
||||
'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
|
||||
users.groups = mkIf (cfg.group == "rtorrent") {
|
||||
rtorrent = {};
|
||||
};
|
||||
|
||||
users.users = mkIf (cfg.user == "rtorrent") {
|
||||
rtorrent = {
|
||||
group = cfg.group;
|
||||
shell = pkgs.bashInteractive;
|
||||
home = cfg.dataDir;
|
||||
description = "rtorrent Daemon user";
|
||||
isSystemUser = true;
|
||||
};
|
||||
};
|
||||
|
||||
networking.firewall.allowedTCPPorts = mkIf (cfg.openFirewall) [ cfg.port ];
|
||||
|
||||
services.rtorrent.configText = mkBefore ''
|
||||
# Instance layout (base paths)
|
||||
method.insert = cfg.basedir, private|const|string, (cat,"${cfg.dataDir}/")
|
||||
method.insert = cfg.watch, private|const|string, (cat,(cfg.basedir),"watch/")
|
||||
method.insert = cfg.logs, private|const|string, (cat,(cfg.basedir),"log/")
|
||||
method.insert = cfg.logfile, private|const|string, (cat,(cfg.logs),(system.time),".log")
|
||||
method.insert = cfg.rpcsock, private|const|string, (cat,"${cfg.rpcSocket}")
|
||||
|
||||
# Create instance directories
|
||||
execute.throw = sh, -c, (cat, "mkdir -p ", (cfg.basedir), "/session ", (cfg.watch), " ", (cfg.logs))
|
||||
|
||||
# Listening port for incoming peer traffic (fixed; you can also randomize it)
|
||||
network.port_range.set = ${toString cfg.port}-${toString cfg.port}
|
||||
network.port_random.set = no
|
||||
|
||||
# Tracker-less torrent and UDP tracker support
|
||||
# (conservative settings for 'private' trackers, change for 'public')
|
||||
dht.mode.set = disable
|
||||
protocol.pex.set = no
|
||||
trackers.use_udp.set = no
|
||||
|
||||
# Peer settings
|
||||
throttle.max_uploads.set = 100
|
||||
throttle.max_uploads.global.set = 250
|
||||
|
||||
throttle.min_peers.normal.set = 20
|
||||
throttle.max_peers.normal.set = 60
|
||||
throttle.min_peers.seed.set = 30
|
||||
throttle.max_peers.seed.set = 80
|
||||
trackers.numwant.set = 80
|
||||
|
||||
protocol.encryption.set = allow_incoming,try_outgoing,enable_retry
|
||||
|
||||
# Limits for file handle resources, this is optimized for
|
||||
# an `ulimit` of 1024 (a common default). You MUST leave
|
||||
# a ceiling of handles reserved for rTorrent's internal needs!
|
||||
network.http.max_open.set = 50
|
||||
network.max_open_files.set = 600
|
||||
network.max_open_sockets.set = 3000
|
||||
|
||||
# Memory resource usage (increase if you have a large number of items loaded,
|
||||
# and/or the available resources to spend)
|
||||
pieces.memory.max.set = 1800M
|
||||
network.xmlrpc.size_limit.set = 4M
|
||||
|
||||
# Basic operational settings (no need to change these)
|
||||
session.path.set = (cat, (cfg.basedir), "session/")
|
||||
directory.default.set = "${cfg.downloadDir}"
|
||||
log.execute = (cat, (cfg.logs), "execute.log")
|
||||
##log.xmlrpc = (cat, (cfg.logs), "xmlrpc.log")
|
||||
execute.nothrow = sh, -c, (cat, "echo >", (session.path), "rtorrent.pid", " ", (system.pid))
|
||||
|
||||
# Other operational settings (check & adapt)
|
||||
encoding.add = utf8
|
||||
system.umask.set = 0027
|
||||
system.cwd.set = (cfg.basedir)
|
||||
network.http.dns_cache_timeout.set = 25
|
||||
schedule2 = monitor_diskspace, 15, 60, ((close_low_diskspace, 1000M))
|
||||
|
||||
# Watch directories (add more as you like, but use unique schedule names)
|
||||
#schedule2 = watch_start, 10, 10, ((load.start, (cat, (cfg.watch), "start/*.torrent")))
|
||||
#schedule2 = watch_load, 11, 10, ((load.normal, (cat, (cfg.watch), "load/*.torrent")))
|
||||
|
||||
# Logging:
|
||||
# Levels = critical error warn notice info debug
|
||||
# Groups = connection_* dht_* peer_* rpc_* storage_* thread_* tracker_* torrent_*
|
||||
print = (cat, "Logging to ", (cfg.logfile))
|
||||
log.open_file = "log", (cfg.logfile)
|
||||
log.add_output = "info", "log"
|
||||
##log.add_output = "tracker_debug", "log"
|
||||
|
||||
# XMLRPC
|
||||
scgi_local = (cfg.rpcsock)
|
||||
schedule = scgi_group,0,0,"execute.nothrow=chown,\":rtorrent\",(cfg.rpcsock)"
|
||||
schedule = scgi_permission,0,0,"execute.nothrow=chmod,\"g+w,o=\",(cfg.rpcsock)"
|
||||
'';
|
||||
|
||||
systemd = {
|
||||
services = {
|
||||
rtorrent = let
|
||||
rtorrentConfigFile = pkgs.writeText "rtorrent.rc" cfg.configText;
|
||||
in {
|
||||
description = "rTorrent system service";
|
||||
after = [ "network.target" ];
|
||||
path = [ cfg.package pkgs.bash ];
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
serviceConfig = {
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
Type = "simple";
|
||||
Restart = "on-failure";
|
||||
WorkingDirectory = cfg.dataDir;
|
||||
ExecStartPre=''${pkgs.bash}/bin/bash -c "if test -e ${cfg.dataDir}/session/rtorrent.lock && test -z $(${pkgs.procps}/bin/pidof rtorrent); then rm -f ${cfg.dataDir}/session/rtorrent.lock; fi"'';
|
||||
ExecStart="${cfg.package}/bin/rtorrent -n -o system.daemon.set=true -o import=${rtorrentConfigFile}";
|
||||
RuntimeDirectory = "rtorrent";
|
||||
RuntimeDirectoryMode = 755;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
tmpfiles.rules = [ "d '${cfg.dataDir}' 0750 ${cfg.user} ${cfg.group} -" ];
|
||||
};
|
||||
};
|
||||
}
|
||||
487
nixos/modules/services/torrent/transmission.nix
Normal file
487
nixos/modules/services/torrent/transmission.nix
Normal file
|
|
@ -0,0 +1,487 @@
|
|||
{ config, lib, pkgs, options, ... }:
|
||||
|
||||
with lib;
|
||||
|
||||
let
|
||||
cfg = config.services.transmission;
|
||||
opt = options.services.transmission;
|
||||
inherit (config.environment) etc;
|
||||
apparmor = config.security.apparmor;
|
||||
rootDir = "/run/transmission";
|
||||
settingsDir = ".config/transmission-daemon";
|
||||
downloadsDir = "Downloads";
|
||||
incompleteDir = ".incomplete";
|
||||
watchDir = "watchdir";
|
||||
settingsFormat = pkgs.formats.json {};
|
||||
settingsFile = settingsFormat.generate "settings.json" cfg.settings;
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
(mkRenamedOptionModule ["services" "transmission" "port"]
|
||||
["services" "transmission" "settings" "rpc-port"])
|
||||
(mkAliasOptionModule ["services" "transmission" "openFirewall"]
|
||||
["services" "transmission" "openPeerPorts"])
|
||||
];
|
||||
options = {
|
||||
services.transmission = {
|
||||
enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
|
||||
|
||||
Transmission daemon can be controlled via the RPC interface using
|
||||
transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
|
||||
or other clients like stig or tremc.
|
||||
|
||||
Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
|
||||
accessible to users in the "transmission" group'';
|
||||
|
||||
settings = mkOption {
|
||||
description = ''
|
||||
Settings whose options overwrite fields in
|
||||
<literal>.config/transmission-daemon/settings.json</literal>
|
||||
(each time the service starts).
|
||||
|
||||
See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
|
||||
for documentation of settings not explicitely covered by this module.
|
||||
'';
|
||||
default = {};
|
||||
type = types.submodule {
|
||||
freeformType = settingsFormat.type;
|
||||
options.download-dir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.home}/${downloadsDir}";
|
||||
defaultText = literalExpression ''"''${config.${opt.home}}/${downloadsDir}"'';
|
||||
description = "Directory where to download torrents.";
|
||||
};
|
||||
options.incomplete-dir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.home}/${incompleteDir}";
|
||||
defaultText = literalExpression ''"''${config.${opt.home}}/${incompleteDir}"'';
|
||||
description = ''
|
||||
When enabled with
|
||||
services.transmission.home
|
||||
<xref linkend="opt-services.transmission.settings.incomplete-dir-enabled"/>,
|
||||
new torrents will download the files to this directory.
|
||||
When complete, the files will be moved to download-dir
|
||||
<xref linkend="opt-services.transmission.settings.download-dir"/>.
|
||||
'';
|
||||
};
|
||||
options.incomplete-dir-enabled = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = "";
|
||||
};
|
||||
options.message-level = mkOption {
|
||||
type = types.ints.between 0 3;
|
||||
default = 2;
|
||||
description = "Set verbosity of transmission messages.";
|
||||
};
|
||||
options.peer-port = mkOption {
|
||||
type = types.port;
|
||||
default = 51413;
|
||||
description = "The peer port to listen for incoming connections.";
|
||||
};
|
||||
options.peer-port-random-high = mkOption {
|
||||
type = types.port;
|
||||
default = 65535;
|
||||
description = ''
|
||||
The maximum peer port to listen to for incoming connections
|
||||
when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
|
||||
'';
|
||||
};
|
||||
options.peer-port-random-low = mkOption {
|
||||
type = types.port;
|
||||
default = 65535;
|
||||
description = ''
|
||||
The minimal peer port to listen to for incoming connections
|
||||
when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
|
||||
'';
|
||||
};
|
||||
options.peer-port-random-on-start = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = "Randomize the peer port.";
|
||||
};
|
||||
options.rpc-bind-address = mkOption {
|
||||
type = types.str;
|
||||
default = "127.0.0.1";
|
||||
example = "0.0.0.0";
|
||||
description = ''
|
||||
Where to listen for RPC connections.
|
||||
Use \"0.0.0.0\" to listen on all interfaces.
|
||||
'';
|
||||
};
|
||||
options.rpc-port = mkOption {
|
||||
type = types.port;
|
||||
default = 9091;
|
||||
description = "The RPC port to listen to.";
|
||||
};
|
||||
options.script-torrent-done-enabled = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''
|
||||
Whether to run
|
||||
<xref linkend="opt-services.transmission.settings.script-torrent-done-filename"/>
|
||||
at torrent completion.
|
||||
'';
|
||||
};
|
||||
options.script-torrent-done-filename = mkOption {
|
||||
type = types.nullOr types.path;
|
||||
default = null;
|
||||
description = "Executable to be run at torrent completion.";
|
||||
};
|
||||
options.umask = mkOption {
|
||||
type = types.int;
|
||||
default = 2;
|
||||
description = ''
|
||||
Sets transmission's file mode creation mask.
|
||||
See the umask(2) manpage for more information.
|
||||
Users who want their saved torrents to be world-writable
|
||||
may want to set this value to 0.
|
||||
Bear in mind that the json markup language only accepts numbers in base 10,
|
||||
so the standard umask(2) octal notation "022" is written in settings.json as 18.
|
||||
'';
|
||||
};
|
||||
options.utp-enabled = mkOption {
|
||||
type = types.bool;
|
||||
default = true;
|
||||
description = ''
|
||||
Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
|
||||
'';
|
||||
};
|
||||
options.watch-dir = mkOption {
|
||||
type = types.path;
|
||||
default = "${cfg.home}/${watchDir}";
|
||||
defaultText = literalExpression ''"''${config.${opt.home}}/${watchDir}"'';
|
||||
description = "Watch a directory for torrent files and add them to transmission.";
|
||||
};
|
||||
options.watch-dir-enabled = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''Whether to enable the
|
||||
<xref linkend="opt-services.transmission.settings.watch-dir"/>.
|
||||
'';
|
||||
};
|
||||
options.trash-original-torrent-files = mkOption {
|
||||
type = types.bool;
|
||||
default = false;
|
||||
description = ''Whether to delete torrents added from the
|
||||
<xref linkend="opt-services.transmission.settings.watch-dir"/>.
|
||||
'';
|
||||
};
|
||||
};
|
||||
};
|
||||
|
||||
downloadDirPermissions = mkOption {
|
||||
type = with types; nullOr str;
|
||||
default = null;
|
||||
example = "770";
|
||||
description = ''
|
||||
If not <code>null</code>, is used as the permissions
|
||||
set by <literal>systemd.activationScripts.transmission-daemon</literal>
|
||||
on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>,
|
||||
<xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
|
||||
and <xref linkend="opt-services.transmission.settings.watch-dir"/>.
|
||||
Note that you may also want to change
|
||||
<xref linkend="opt-services.transmission.settings.umask"/>.
|
||||
'';
|
||||
};
|
||||
|
||||
home = mkOption {
|
||||
type = types.path;
|
||||
default = "/var/lib/transmission";
|
||||
description = ''
|
||||
The directory where Transmission will create <literal>${settingsDir}</literal>.
|
||||
as well as <literal>${downloadsDir}/</literal> unless
|
||||
<xref linkend="opt-services.transmission.settings.download-dir"/> is changed,
|
||||
and <literal>${incompleteDir}/</literal> unless
|
||||
<xref linkend="opt-services.transmission.settings.incomplete-dir"/> is changed.
|
||||
'';
|
||||
};
|
||||
|
||||
user = mkOption {
|
||||
type = types.str;
|
||||
default = "transmission";
|
||||
description = "User account under which Transmission runs.";
|
||||
};
|
||||
|
||||
group = mkOption {
|
||||
type = types.str;
|
||||
default = "transmission";
|
||||
description = "Group account under which Transmission runs.";
|
||||
};
|
||||
|
||||
credentialsFile = mkOption {
|
||||
type = types.path;
|
||||
description = ''
|
||||
Path to a JSON file to be merged with the settings.
|
||||
Useful to merge a file which is better kept out of the Nix store
|
||||
to set secret config parameters like <code>rpc-password</code>.
|
||||
'';
|
||||
default = "/dev/null";
|
||||
example = "/var/lib/secrets/transmission/settings.json";
|
||||
};
|
||||
|
||||
extraFlags = mkOption {
|
||||
type = types.listOf types.str;
|
||||
default = [];
|
||||
example = [ "--log-debug" ];
|
||||
description = ''
|
||||
Extra flags passed to the transmission command in the service definition.
|
||||
'';
|
||||
};
|
||||
|
||||
openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
|
||||
|
||||
openRPCPort = mkEnableOption "opening of the RPC port in the firewall";
|
||||
|
||||
performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
|
||||
to open many more connections at the same time.
|
||||
|
||||
Note that you may also want to increase
|
||||
<code>peer-limit-global"</code>.
|
||||
And be aware that these settings are quite aggressive
|
||||
and might not suite your regular desktop use.
|
||||
For instance, SSH sessions may time out more easily'';
|
||||
};
|
||||
};
|
||||
|
||||
config = mkIf cfg.enable {
|
||||
# Note that using systemd.tmpfiles would not work here
|
||||
# because it would fail when creating a directory
|
||||
# with a different owner than its parent directory, by saying:
|
||||
# Detected unsafe path transition /home/foo → /home/foo/Downloads during canonicalization of /home/foo/Downloads
|
||||
# when /home/foo is not owned by cfg.user.
|
||||
# Note also that using an ExecStartPre= wouldn't work either
|
||||
# because BindPaths= needs these directories before.
|
||||
system.activationScripts = mkIf (cfg.downloadDirPermissions != null)
|
||||
{ transmission-daemon = ''
|
||||
install -d -m 700 '${cfg.home}/${settingsDir}'
|
||||
chown -R '${cfg.user}:${cfg.group}' ${cfg.home}/${settingsDir}
|
||||
install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
|
||||
'' + optionalString cfg.settings.incomplete-dir-enabled ''
|
||||
install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
|
||||
'' + optionalString cfg.settings.watch-dir-enabled ''
|
||||
install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
|
||||
'';
|
||||
};
|
||||
|
||||
systemd.services.transmission = {
|
||||
description = "Transmission BitTorrent Service";
|
||||
after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
|
||||
requires = optional apparmor.enable "apparmor.service";
|
||||
wantedBy = [ "multi-user.target" ];
|
||||
environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
|
||||
|
||||
serviceConfig = {
|
||||
# Use "+" because credentialsFile may not be accessible to User= or Group=.
|
||||
ExecStartPre = [("+" + pkgs.writeShellScript "transmission-prestart" ''
|
||||
set -eu${lib.optionalString (cfg.settings.message-level >= 3) "x"}
|
||||
${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
|
||||
install -D -m 600 -o '${cfg.user}' -g '${cfg.group}' /dev/stdin \
|
||||
'${cfg.home}/${settingsDir}/settings.json'
|
||||
'')];
|
||||
ExecStart="${pkgs.transmission}/bin/transmission-daemon -f -g ${cfg.home}/${settingsDir} ${escapeShellArgs cfg.extraFlags}";
|
||||
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
|
||||
User = cfg.user;
|
||||
Group = cfg.group;
|
||||
# Create rootDir in the host's mount namespace.
|
||||
RuntimeDirectory = [(baseNameOf rootDir)];
|
||||
RuntimeDirectoryMode = "755";
|
||||
# This is for BindPaths= and BindReadOnlyPaths=
|
||||
# to allow traversal of directories they create in RootDirectory=.
|
||||
UMask = "0066";
|
||||
# Using RootDirectory= makes it possible
|
||||
# to use the same paths download-dir/incomplete-dir
|
||||
# (which appear in user's interfaces) without requiring cfg.user
|
||||
# to have access to their parent directories,
|
||||
# by using BindPaths=/BindReadOnlyPaths=.
|
||||
# Note that TemporaryFileSystem= could have been used instead
|
||||
# but not without adding some BindPaths=/BindReadOnlyPaths=
|
||||
# that would only be needed for ExecStartPre=,
|
||||
# because RootDirectoryStartOnly=true would not help.
|
||||
RootDirectory = rootDir;
|
||||
RootDirectoryStartOnly = true;
|
||||
MountAPIVFS = true;
|
||||
BindPaths =
|
||||
[ "${cfg.home}/${settingsDir}"
|
||||
cfg.settings.download-dir
|
||||
] ++
|
||||
optional cfg.settings.incomplete-dir-enabled
|
||||
cfg.settings.incomplete-dir ++
|
||||
optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
|
||||
cfg.settings.watch-dir;
|
||||
BindReadOnlyPaths = [
|
||||
# No confinement done of /nix/store here like in systemd-confinement.nix,
|
||||
# an AppArmor profile is provided to get a confinement based upon paths and rights.
|
||||
builtins.storeDir
|
||||
"/etc"
|
||||
"/run"
|
||||
] ++
|
||||
optional (cfg.settings.script-torrent-done-enabled &&
|
||||
cfg.settings.script-torrent-done-filename != null)
|
||||
cfg.settings.script-torrent-done-filename ++
|
||||
optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
|
||||
cfg.settings.watch-dir;
|
||||
StateDirectory = [
|
||||
"transmission"
|
||||
"transmission/.config/transmission-daemon"
|
||||
"transmission/.incomplete"
|
||||
"transmission/Downloads"
|
||||
"transmission/watch-dir"
|
||||
];
|
||||
StateDirectoryMode = mkDefault 750;
|
||||
# The following options are only for optimizing:
|
||||
# systemd-analyze security transmission
|
||||
AmbientCapabilities = "";
|
||||
CapabilityBoundingSet = "";
|
||||
# ProtectClock= adds DeviceAllow=char-rtc r
|
||||
DeviceAllow = "";
|
||||
LockPersonality = true;
|
||||
MemoryDenyWriteExecute = true;
|
||||
NoNewPrivileges = true;
|
||||
PrivateDevices = true;
|
||||
PrivateMounts = true;
|
||||
PrivateNetwork = mkDefault false;
|
||||
PrivateTmp = true;
|
||||
PrivateUsers = true;
|
||||
ProtectClock = true;
|
||||
ProtectControlGroups = true;
|
||||
# ProtectHome=true would not allow BindPaths= to work accross /home,
|
||||
# and ProtectHome=tmpfs would break statfs(),
|
||||
# preventing transmission-daemon to report the available free space.
|
||||
# However, RootDirectory= is used, so this is not a security concern
|
||||
# since there would be nothing in /home but any BindPaths= wanted by the user.
|
||||
ProtectHome = "read-only";
|
||||
ProtectHostname = true;
|
||||
ProtectKernelLogs = true;
|
||||
ProtectKernelModules = true;
|
||||
ProtectKernelTunables = true;
|
||||
ProtectSystem = "strict";
|
||||
RemoveIPC = true;
|
||||
# AF_UNIX may become usable one day:
|
||||
# https://github.com/transmission/transmission/issues/441
|
||||
RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
|
||||
RestrictNamespaces = true;
|
||||
RestrictRealtime = true;
|
||||
RestrictSUIDSGID = true;
|
||||
SystemCallFilter = [
|
||||
"@system-service"
|
||||
# Groups in @system-service which do not contain a syscall
|
||||
# listed by perf stat -e 'syscalls:sys_enter_*' transmission-daemon -f
|
||||
# in tests, and seem likely not necessary for transmission-daemon.
|
||||
"~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
|
||||
# In the @privileged group, but reached when querying infos through RPC (eg. with stig).
|
||||
"quotactl"
|
||||
];
|
||||
SystemCallArchitectures = "native";
|
||||
};
|
||||
};
|
||||
|
||||
# It's useful to have transmission in path, e.g. for remote control
|
||||
environment.systemPackages = [ pkgs.transmission ];
|
||||
|
||||
users.users = optionalAttrs (cfg.user == "transmission") ({
|
||||
transmission = {
|
||||
group = cfg.group;
|
||||
uid = config.ids.uids.transmission;
|
||||
description = "Transmission BitTorrent user";
|
||||
home = cfg.home;
|
||||
};
|
||||
});
|
||||
|
||||
users.groups = optionalAttrs (cfg.group == "transmission") ({
|
||||
transmission = {
|
||||
gid = config.ids.gids.transmission;
|
||||
};
|
||||
});
|
||||
|
||||
networking.firewall = mkMerge [
|
||||
(mkIf cfg.openPeerPorts (
|
||||
if cfg.settings.peer-port-random-on-start
|
||||
then
|
||||
{ allowedTCPPortRanges =
|
||||
[ { from = cfg.settings.peer-port-random-low;
|
||||
to = cfg.settings.peer-port-random-high;
|
||||
}
|
||||
];
|
||||
allowedUDPPortRanges =
|
||||
[ { from = cfg.settings.peer-port-random-low;
|
||||
to = cfg.settings.peer-port-random-high;
|
||||
}
|
||||
];
|
||||
}
|
||||
else
|
||||
{ allowedTCPPorts = [ cfg.settings.peer-port ];
|
||||
allowedUDPPorts = [ cfg.settings.peer-port ];
|
||||
}
|
||||
))
|
||||
(mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
|
||||
];
|
||||
|
||||
boot.kernel.sysctl = mkMerge [
|
||||
# Transmission uses a single UDP socket in order to implement multiple uTP sockets,
|
||||
# and thus expects large kernel buffers for the UDP socket,
|
||||
# https://trac.transmissionbt.com/browser/trunk/libtransmission/tr-udp.c?rev=11956.
|
||||
# at least up to the values hardcoded here:
|
||||
(mkIf cfg.settings.utp-enabled {
|
||||
"net.core.rmem_max" = mkDefault "4194304"; # 4MB
|
||||
"net.core.wmem_max" = mkDefault "1048576"; # 1MB
|
||||
})
|
||||
(mkIf cfg.performanceNetParameters {
|
||||
# Increase the number of available source (local) TCP and UDP ports to 49151.
|
||||
# Usual default is 32768 60999, ie. 28231 ports.
|
||||
# Find out your current usage with: ss -s
|
||||
"net.ipv4.ip_local_port_range" = mkDefault "16384 65535";
|
||||
# Timeout faster generic TCP states.
|
||||
# Usual default is 600.
|
||||
# Find out your current usage with: watch -n 1 netstat -nptuo
|
||||
"net.netfilter.nf_conntrack_generic_timeout" = mkDefault 60;
|
||||
# Timeout faster established but inactive connections.
|
||||
# Usual default is 432000.
|
||||
"net.netfilter.nf_conntrack_tcp_timeout_established" = mkDefault 600;
|
||||
# Clear immediately TCP states after timeout.
|
||||
# Usual default is 120.
|
||||
"net.netfilter.nf_conntrack_tcp_timeout_time_wait" = mkDefault 1;
|
||||
# Increase the number of trackable connections.
|
||||
# Usual default is 262144.
|
||||
# Find out your current usage with: conntrack -C
|
||||
"net.netfilter.nf_conntrack_max" = mkDefault 1048576;
|
||||
})
|
||||
];
|
||||
|
||||
security.apparmor.policies."bin.transmission-daemon".profile = ''
|
||||
include "${pkgs.transmission.apparmor}/bin.transmission-daemon"
|
||||
'';
|
||||
security.apparmor.includes."local/bin.transmission-daemon" = ''
|
||||
r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
|
||||
|
||||
owner rw ${cfg.home}/${settingsDir}/**,
|
||||
rw ${cfg.settings.download-dir}/**,
|
||||
${optionalString cfg.settings.incomplete-dir-enabled ''
|
||||
rw ${cfg.settings.incomplete-dir}/**,
|
||||
''}
|
||||
${optionalString cfg.settings.watch-dir-enabled ''
|
||||
r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
|
||||
''}
|
||||
profile dirs {
|
||||
rw ${cfg.settings.download-dir}/**,
|
||||
${optionalString cfg.settings.incomplete-dir-enabled ''
|
||||
rw ${cfg.settings.incomplete-dir}/**,
|
||||
''}
|
||||
${optionalString cfg.settings.watch-dir-enabled ''
|
||||
r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
|
||||
''}
|
||||
}
|
||||
|
||||
${optionalString (cfg.settings.script-torrent-done-enabled &&
|
||||
cfg.settings.script-torrent-done-filename != null) ''
|
||||
# Stack transmission_directories profile on top of
|
||||
# any existing profile for script-torrent-done-filename
|
||||
# FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
|
||||
# https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
|
||||
px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
|
||||
''}
|
||||
'';
|
||||
};
|
||||
|
||||
meta.maintainers = with lib.maintainers; [ julm ];
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue