diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index e9b5834dab4..779924a65a8 100644
--- a/nixos/modules/services/torrent/transmission.nix
+++ b/nixos/modules/services/torrent/transmission.nix
@@ -7,15 +7,20 @@ let
inherit (config.environment) etc;
apparmor = config.security.apparmor;
rootDir = "/run/transmission";
- homeDir = "/var/lib/transmission";
settingsDir = ".config/transmission-daemon";
downloadsDir = "Downloads";
incompleteDir = ".incomplete";
watchDir = "watchdir";
- # TODO: switch to configGen.json once RFC0042 is implemented
- settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
+ 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.
@@ -24,48 +29,141 @@ in
transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
or other clients like stig or tremc.
- Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
+ Torrents are downloaded to /${downloadsDir} by default and are
accessible to users in the "transmission" group'';
- settings = mkOption rec {
- # 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.*.*";
- };
+ settings = mkOption {
description = ''
- Attribute set whose fields overwrites fields in
+ Settings whose options overwrite fields in
.config/transmission-daemon/settings.json
- (each time the service starts). String values must be quoted, integer and
- boolean values must not.
+ (each time the service starts).
See Transmission's Wiki
- 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
+ ,
+ new torrents will download the files to this directory.
+ When complete, the files will be moved to 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 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 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
+
+ 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 Micro Transport Protocol (µTP).
+ '';
+ };
+ 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
+ .
+ '';
+ };
+ options.trash-original-torrent-files = mkOption {
+ type = types.bool;
+ default = false;
+ description = ''Whether to delete torrents added from the
+ .
+ '';
+ };
+ };
};
downloadDirPermissions = mkOption {
@@ -74,31 +172,22 @@ in
example = "775";
description = ''
The permissions set by systemd.activationScripts.transmission-daemon
- on the directories settings.download-dir
- and settings.incomplete-dir.
+ on the directories
+ and .
Note that you may also want to change
- 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 settings.peer-port
- or settings.peer-port-random-on-start.
+ .
'';
};
home = mkOption {
type = types.path;
- default = homeDir;
+ default = "/var/lib/transmission";
description = ''
The directory where Transmission will create ${settingsDir}.
- as well as ${downloadsDir}/ unless settings.download-dir is changed,
- and ${incompleteDir}/ unless settings.incomplete-dir is changed.
+ as well as ${downloadsDir}/ unless
+ is changed,
+ and ${incompleteDir}/ unless
+ is changed.
'';
};
@@ -119,19 +208,22 @@ in
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
- because it contains sensible data like settings.rpc-password.
+ because it contains sensible data like
+ .
'';
default = "/dev/null";
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
to open many more connections at the same time.
Note that you may also want to increase
- settings.peer-limit-global.
+ .
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'';
@@ -152,36 +244,10 @@ in
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}'
'';
- 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 = {
description = "Transmission BitTorrent Service";
after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
@@ -226,11 +292,9 @@ in
cfg.settings.download-dir
] ++
optional cfg.settings.incomplete-dir-enabled
- cfg.settings.incomplete-dir
- ++
- optional cfg.settings.watch-dir-enabled
- cfg.settings.watch-dir
- ;
+ 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.
@@ -239,8 +303,10 @@ in
"/run"
] ++
optional (cfg.settings.script-torrent-done-enabled &&
- cfg.settings.script-torrent-done-filename != "")
- cfg.settings.script-torrent-done-filename;
+ 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;
# The following options are only for optimizing:
# systemd-analyze security transmission
AmbientCapabilities = "";
@@ -307,25 +373,28 @@ in
};
});
- networking.firewall = mkIf cfg.openFirewall (
- 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 ];
- }
- );
+ 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,
@@ -340,74 +409,57 @@ in
# 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" = "16384 65535";
+ "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" = 60;
+ "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" = 600;
+ "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" = 1;
+ "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" = 1048576;
+ "net.netfilter.nf_conntrack_max" = mkDefault 1048576;
})
];
security.apparmor.policies."bin.transmission-daemon".profile = ''
- include
- ${pkgs.transmission}/bin/transmission-daemon {
- include
- include
- include
- include "${pkgs.apparmorRulesFromClosure
- { name = "transmission-daemon"; }
- [ pkgs.transmission ]}"
- include
-
- r @{PROC}/sys/kernel/random/uuid,
- r @{PROC}/sys/vm/overcommit_memory,
- r @{PROC}/@{pid}/environ,
- r @{PROC}/@{pid}/mounts,
- rwk /tmp/tr_session_id_*,
- r /run/systemd/resolve/stub-resolv.conf,
-
- r ${pkgs.openssl.out}/etc/**,
- 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 ''
- rw ${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 ''
- rw ${cfg.settings.watch-dir}/**,
- ''}
- }
-
- ${optionalString (cfg.settings.script-torrent-done-enabled &&
- cfg.settings.script-torrent-done-filename != "") ''
- # 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},
- ''}
- }
+ 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},
+ ''}
'';
- security.apparmor.includes."local/bin.transmission-daemon" = "";
};
meta.maintainers = with lib.maintainers; [ julm ];
diff --git a/pkgs/applications/networking/p2p/transmission/default.nix b/pkgs/applications/networking/p2p/transmission/default.nix
index ab4fc0908ba..858b09c9aaa 100644
--- a/pkgs/applications/networking/p2p/transmission/default.nix
+++ b/pkgs/applications/networking/p2p/transmission/default.nix
@@ -20,6 +20,7 @@
, enableSystemd ? stdenv.isLinux
, enableDaemon ? true
, enableCli ? true
+, apparmorRulesFromClosure
}:
let
@@ -37,6 +38,8 @@ in stdenv.mkDerivation {
fetchSubmodules = true;
};
+ outputs = [ "out" "apparmor" ];
+
cmakeFlags =
let
mkFlag = opt: if opt then "ON" else "OFF";
@@ -72,6 +75,30 @@ in stdenv.mkDerivation {
NIX_LDFLAGS = lib.optionalString stdenv.isDarwin "-framework CoreFoundation";
+ postInstall = ''
+ install -D /dev/stdin $apparmor/bin.transmission-daemon <
+ $out/bin/transmission-daemon {
+ include
+ include
+ include
+ include "${apparmorRulesFromClosure { name = "transmission-daemon"; } ([
+ curl libevent openssl pcre zlib
+ ] ++ lib.optionals enableSystemd [ systemd ]
+ ++ lib.optionals stdenv.isLinux [ inotify-tools ]
+ )}"
+ r @{PROC}/sys/kernel/random/uuid,
+ r @{PROC}/sys/vm/overcommit_memory,
+ r @{PROC}/@{pid}/environ,
+ r @{PROC}/@{pid}/mounts,
+ rwk /tmp/tr_session_id_*,
+ r /run/systemd/resolve/stub-resolv.conf,
+
+ include
+ }
+ EOF
+ '';
+
meta = {
description = "A fast, easy and free BitTorrent client";
longDescription = ''