nixos/transmission: use freeformType on settings

This commit is contained in:
Julien Moutinho 2020-12-03 18:08:55 +01:00
parent ab8489cc40
commit 63e87892ff

View File

@ -7,15 +7,20 @@ let
inherit (config.environment) etc; inherit (config.environment) etc;
apparmor = config.security.apparmor; apparmor = config.security.apparmor;
rootDir = "/run/transmission"; rootDir = "/run/transmission";
homeDir = "/var/lib/transmission";
settingsDir = ".config/transmission-daemon"; settingsDir = ".config/transmission-daemon";
downloadsDir = "Downloads"; downloadsDir = "Downloads";
incompleteDir = ".incomplete"; incompleteDir = ".incomplete";
watchDir = "watchdir"; watchDir = "watchdir";
# TODO: switch to configGen.json once RFC0042 is implemented settingsFormat = pkgs.formats.json {};
settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings); settingsFile = settingsFormat.generate "settings.json" cfg.settings;
in in
{ {
imports = [
(mkRenamedOptionModule ["services" "transmission" "port"]
["services" "transmission" "settings" "rpc-port"])
(mkAliasOptionModule ["services" "transmission" "openFirewall"]
["services" "transmission" "openPeerPorts"])
];
options = { options = {
services.transmission = { services.transmission = {
enable = mkEnableOption ''the headless Transmission BitTorrent daemon. enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
@ -24,48 +29,141 @@ in
transmission-remote, the WebUI (http://127.0.0.1:9091/ by default), transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
or other clients like stig or tremc. or other clients like stig or tremc.
Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
accessible to users in the "transmission" group''; accessible to users in the "transmission" group'';
settings = mkOption rec { settings = mkOption {
# TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
type = types.attrs;
apply = recursiveUpdate default;
default =
{
download-dir = "${cfg.home}/${downloadsDir}";
incomplete-dir = "${cfg.home}/${incompleteDir}";
incomplete-dir-enabled = true;
watch-dir = "${cfg.home}/${watchDir}";
watch-dir-enabled = false;
message-level = 1;
peer-port = 51413;
peer-port-random-high = 65535;
peer-port-random-low = 49152;
peer-port-random-on-start = false;
rpc-bind-address = "127.0.0.1";
rpc-port = 9091;
script-torrent-done-enabled = false;
script-torrent-done-filename = "";
umask = 2; # 0o002 in decimal as expected by Transmission
utp-enabled = true;
};
example =
{
download-dir = "/srv/torrents/";
incomplete-dir = "/srv/torrents/.incomplete/";
incomplete-dir-enabled = true;
rpc-whitelist = "127.0.0.1,192.168.*.*";
};
description = '' description = ''
Attribute set whose fields overwrites fields in Settings whose options overwrite fields in
<literal>.config/transmission-daemon/settings.json</literal> <literal>.config/transmission-daemon/settings.json</literal>
(each time the service starts). String values must be quoted, integer and (each time the service starts).
boolean values must not.
See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link> See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
for documentation. 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}";
description = "Directory where to download torrents.";
};
options.incomplete-dir = mkOption {
type = types.path;
default = "${cfg.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 2;
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}";
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 { downloadDirPermissions = mkOption {
@ -74,31 +172,22 @@ in
example = "775"; example = "775";
description = '' description = ''
The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal> The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link> on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>
and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>. and <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
Note that you may also want to change Note that you may also want to change
<link linkend="opt-services.transmission.settings">settings.umask</link>. <xref linkend="opt-services.transmission.settings.umask"/>.
'';
};
port = mkOption {
type = types.port;
description = ''
TCP port number to run the RPC/web interface.
If instead you want to change the peer port,
use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
''; '';
}; };
home = mkOption { home = mkOption {
type = types.path; type = types.path;
default = homeDir; default = "/var/lib/transmission";
description = '' description = ''
The directory where Transmission will create <literal>${settingsDir}</literal>. The directory where Transmission will create <literal>${settingsDir}</literal>.
as well as <literal>${downloadsDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed, as well as <literal>${downloadsDir}/</literal> unless
and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed. <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.
''; '';
}; };
@ -119,19 +208,21 @@ in
description = '' description = ''
Path to a JSON file to be merged with the settings. 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 Useful to merge a file which is better kept out of the Nix store
because it contains sensible data like <link linkend="opt-services.transmission.settings">settings.rpc-password</link>. to set secret config parameters like <code>rpc-password</code>.
''; '';
default = "/dev/null"; default = "/dev/null";
example = "/var/lib/secrets/transmission/settings.json"; example = "/var/lib/secrets/transmission/settings.json";
}; };
openFirewall = mkEnableOption "opening of the peer port(s) in the firewall"; 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 performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
to open many more connections at the same time. to open many more connections at the same time.
Note that you may also want to increase Note that you may also want to increase
<link linkend="opt-services.transmission.settings">settings.peer-limit-global</link>. <code>peer-limit-global"</code>.
And be aware that these settings are quite aggressive And be aware that these settings are quite aggressive
and might not suite your regular desktop use. and might not suite your regular desktop use.
For instance, SSH sessions may time out more easily''; For instance, SSH sessions may time out more easily'';
@ -156,34 +247,6 @@ in
install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}' install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
''; '';
assertions = [
{ assertion = builtins.match "^/.*" cfg.home != null;
message = "`services.transmission.home' must be an absolute path.";
}
{ assertion = types.path.check cfg.settings.download-dir;
message = "`services.transmission.settings.download-dir' must be an absolute path.";
}
{ assertion = types.path.check cfg.settings.incomplete-dir;
message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
}
{ assertion = types.path.check cfg.settings.watch-dir;
message = "`services.transmission.settings.watch-dir' must be an absolute path.";
}
{ assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
}
{ assertion = types.port.check cfg.settings.rpc-port;
message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
}
# In case both port and settings.rpc-port are explicitely defined: they must be the same.
{ assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
}
];
services.transmission.settings =
optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
systemd.services.transmission = { systemd.services.transmission = {
description = "Transmission BitTorrent Service"; description = "Transmission BitTorrent Service";
after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service"; after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
@ -228,11 +291,9 @@ in
cfg.settings.download-dir cfg.settings.download-dir
] ++ ] ++
optional cfg.settings.incomplete-dir-enabled optional cfg.settings.incomplete-dir-enabled
cfg.settings.incomplete-dir cfg.settings.incomplete-dir ++
++ optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
optional cfg.settings.watch-dir-enabled cfg.settings.watch-dir;
cfg.settings.watch-dir
;
BindReadOnlyPaths = [ BindReadOnlyPaths = [
# No confinement done of /nix/store here like in systemd-confinement.nix, # 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. # an AppArmor profile is provided to get a confinement based upon paths and rights.
@ -241,8 +302,10 @@ in
"/run" "/run"
] ++ ] ++
optional (cfg.settings.script-torrent-done-enabled && optional (cfg.settings.script-torrent-done-enabled &&
cfg.settings.script-torrent-done-filename != "") cfg.settings.script-torrent-done-filename != null)
cfg.settings.script-torrent-done-filename; cfg.settings.script-torrent-done-filename ++
optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
cfg.settings.watch-dir;
# The following options are only for optimizing: # The following options are only for optimizing:
# systemd-analyze security transmission # systemd-analyze security transmission
AmbientCapabilities = ""; AmbientCapabilities = "";
@ -309,25 +372,28 @@ in
}; };
}); });
networking.firewall = mkIf cfg.openFirewall ( networking.firewall = mkMerge [
if cfg.settings.peer-port-random-on-start (mkIf cfg.openPeerPorts (
then if cfg.settings.peer-port-random-on-start
{ allowedTCPPortRanges = then
[ { from = cfg.settings.peer-port-random-low; { allowedTCPPortRanges =
to = cfg.settings.peer-port-random-high; [ { from = cfg.settings.peer-port-random-low;
} to = cfg.settings.peer-port-random-high;
]; }
allowedUDPPortRanges = ];
[ { from = cfg.settings.peer-port-random-low; allowedUDPPortRanges =
to = cfg.settings.peer-port-random-high; [ { from = cfg.settings.peer-port-random-low;
} to = cfg.settings.peer-port-random-high;
]; }
} ];
else }
{ allowedTCPPorts = [ cfg.settings.peer-port ]; else
allowedUDPPorts = [ cfg.settings.peer-port ]; { allowedTCPPorts = [ cfg.settings.peer-port ];
} allowedUDPPorts = [ cfg.settings.peer-port ];
); }
))
(mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
];
boot.kernel.sysctl = mkMerge [ boot.kernel.sysctl = mkMerge [
# Transmission uses a single UDP socket in order to implement multiple uTP sockets, # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
@ -342,21 +408,21 @@ in
# Increase the number of available source (local) TCP and UDP ports to 49151. # Increase the number of available source (local) TCP and UDP ports to 49151.
# Usual default is 32768 60999, ie. 28231 ports. # Usual default is 32768 60999, ie. 28231 ports.
# Find out your current usage with: ss -s # Find out your current usage with: ss -s
"net.ipv4.ip_local_port_range" = "16384 65535"; "net.ipv4.ip_local_port_range" = mkDefault "16384 65535";
# Timeout faster generic TCP states. # Timeout faster generic TCP states.
# Usual default is 600. # Usual default is 600.
# Find out your current usage with: watch -n 1 netstat -nptuo # Find out your current usage with: watch -n 1 netstat -nptuo
"net.netfilter.nf_conntrack_generic_timeout" = 60; "net.netfilter.nf_conntrack_generic_timeout" = mkDefault 60;
# Timeout faster established but inactive connections. # Timeout faster established but inactive connections.
# Usual default is 432000. # Usual default is 432000.
"net.netfilter.nf_conntrack_tcp_timeout_established" = 600; "net.netfilter.nf_conntrack_tcp_timeout_established" = mkDefault 600;
# Clear immediately TCP states after timeout. # Clear immediately TCP states after timeout.
# Usual default is 120. # Usual default is 120.
"net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1; "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = mkDefault 1;
# Increase the number of trackable connections. # Increase the number of trackable connections.
# Usual default is 262144. # Usual default is 262144.
# Find out your current usage with: conntrack -C # Find out your current usage with: conntrack -C
"net.netfilter.nf_conntrack_max" = 1048576; "net.netfilter.nf_conntrack_max" = mkDefault 1048576;
}) })
]; ];
@ -372,7 +438,7 @@ in
rw ${cfg.settings.incomplete-dir}/**, rw ${cfg.settings.incomplete-dir}/**,
''} ''}
${optionalString cfg.settings.watch-dir-enabled '' ${optionalString cfg.settings.watch-dir-enabled ''
rw ${cfg.settings.watch-dir}/**, r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
''} ''}
profile dirs { profile dirs {
rw ${cfg.settings.download-dir}/**, rw ${cfg.settings.download-dir}/**,
@ -380,12 +446,12 @@ in
rw ${cfg.settings.incomplete-dir}/**, rw ${cfg.settings.incomplete-dir}/**,
''} ''}
${optionalString cfg.settings.watch-dir-enabled '' ${optionalString cfg.settings.watch-dir-enabled ''
rw ${cfg.settings.watch-dir}/**, r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
''} ''}
} }
${optionalString (cfg.settings.script-torrent-done-enabled && ${optionalString (cfg.settings.script-torrent-done-enabled &&
cfg.settings.script-torrent-done-filename != "") '' cfg.settings.script-torrent-done-filename != null) ''
# Stack transmission_directories profile on top of # Stack transmission_directories profile on top of
# any existing profile for script-torrent-done-filename # any existing profile for script-torrent-done-filename
# FIXME: to be tested as I'm not sure it works well with NoNewPrivileges= # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=