1 diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
2 index e9b5834dab4..779924a65a8 100644
3 --- a/nixos/modules/services/torrent/transmission.nix
4 +++ b/nixos/modules/services/torrent/transmission.nix
6 inherit (config.environment) etc;
7 apparmor = config.security.apparmor;
8 rootDir = "/run/transmission";
9 - homeDir = "/var/lib/transmission";
10 settingsDir = ".config/transmission-daemon";
11 downloadsDir = "Downloads";
12 incompleteDir = ".incomplete";
13 watchDir = "watchdir";
14 - # TODO: switch to configGen.json once RFC0042 is implemented
15 - settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
16 + settingsFormat = pkgs.formats.json {};
17 + settingsFile = settingsFormat.generate "settings.json" cfg.settings;
21 + (mkRenamedOptionModule ["services" "transmission" "port"]
22 + ["services" "transmission" "settings" "rpc-port"])
23 + (mkAliasOptionModule ["services" "transmission" "openFirewall"]
24 + ["services" "transmission" "openPeerPorts"])
27 services.transmission = {
28 enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
29 @@ -24,48 +29,141 @@ in
30 transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
31 or other clients like stig or tremc.
33 - Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
34 + Torrents are downloaded to <xref linkend="opt-services.transmission.home"/>/${downloadsDir} by default and are
35 accessible to users in the "transmission" group'';
37 - settings = mkOption rec {
38 - # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
40 - apply = recursiveUpdate default;
43 - download-dir = "${cfg.home}/${downloadsDir}";
44 - incomplete-dir = "${cfg.home}/${incompleteDir}";
45 - incomplete-dir-enabled = true;
46 - watch-dir = "${cfg.home}/${watchDir}";
47 - watch-dir-enabled = false;
50 - peer-port-random-high = 65535;
51 - peer-port-random-low = 49152;
52 - peer-port-random-on-start = false;
53 - rpc-bind-address = "127.0.0.1";
55 - script-torrent-done-enabled = false;
56 - script-torrent-done-filename = "";
57 - umask = 2; # 0o002 in decimal as expected by Transmission
62 - download-dir = "/srv/torrents/";
63 - incomplete-dir = "/srv/torrents/.incomplete/";
64 - incomplete-dir-enabled = true;
65 - rpc-whitelist = "127.0.0.1,192.168.*.*";
67 + settings = mkOption {
69 - Attribute set whose fields overwrites fields in
70 + Settings whose options overwrite fields in
71 <literal>.config/transmission-daemon/settings.json</literal>
72 - (each time the service starts). String values must be quoted, integer and
73 - boolean values must not.
74 + (each time the service starts).
76 See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
78 + for documentation of settings not explicitely covered by this module.
81 + type = types.submodule {
82 + freeformType = settingsFormat.type;
83 + options.download-dir = mkOption {
85 + default = "${cfg.home}/${downloadsDir}";
86 + description = "Directory where to download torrents.";
88 + options.incomplete-dir = mkOption {
90 + default = "${cfg.home}/${incompleteDir}";
93 + services.transmission.home
94 + <xref linkend="opt-services.transmission.settings.incomplete-dir-enabled"/>,
95 + new torrents will download the files to this directory.
96 + When complete, the files will be moved to download-dir
97 + <xref linkend="opt-services.transmission.settings.download-dir"/>.
100 + options.incomplete-dir-enabled = mkOption {
105 + options.message-level = mkOption {
106 + type = types.ints.between 0 2;
108 + description = "Set verbosity of transmission messages.";
110 + options.peer-port = mkOption {
113 + description = "The peer port to listen for incoming connections.";
115 + options.peer-port-random-high = mkOption {
119 + The maximum peer port to listen to for incoming connections
120 + when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
123 + options.peer-port-random-low = mkOption {
127 + The minimal peer port to listen to for incoming connections
128 + when <xref linkend="opt-services.transmission.settings.peer-port-random-on-start"/> is enabled.
131 + options.peer-port-random-on-start = mkOption {
134 + description = "Randomize the peer port.";
136 + options.rpc-bind-address = mkOption {
138 + default = "127.0.0.1";
139 + example = "0.0.0.0";
141 + Where to listen for RPC connections.
142 + Use \"0.0.0.0\" to listen on all interfaces.
145 + options.rpc-port = mkOption {
148 + description = "The RPC port to listen to.";
150 + options.script-torrent-done-enabled = mkOption {
155 + <xref linkend="opt-services.transmission.settings.script-torrent-done-filename"/>
156 + at torrent completion.
159 + options.script-torrent-done-filename = mkOption {
160 + type = types.nullOr types.path;
162 + description = "Executable to be run at torrent completion.";
164 + options.umask = mkOption {
168 + Sets transmission's file mode creation mask.
169 + See the umask(2) manpage for more information.
170 + Users who want their saved torrents to be world-writable
171 + may want to set this value to 0.
172 + Bear in mind that the json markup language only accepts numbers in base 10,
173 + so the standard umask(2) octal notation "022" is written in settings.json as 18.
176 + options.utp-enabled = mkOption {
180 + Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
183 + options.watch-dir = mkOption {
185 + default = "${cfg.home}/${watchDir}";
186 + description = "Watch a directory for torrent files and add them to transmission.";
188 + options.watch-dir-enabled = mkOption {
191 + description = ''Whether to enable the
192 + <xref linkend="opt-services.transmission.settings.watch-dir"/>.
195 + options.trash-original-torrent-files = mkOption {
198 + description = ''Whether to delete torrents added from the
199 + <xref linkend="opt-services.transmission.settings.watch-dir"/>.
205 downloadDirPermissions = mkOption {
206 @@ -74,31 +172,22 @@ in
209 The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
210 - on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link>
211 - and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>.
212 + on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>
213 + and <xref linkend="opt-services.transmission.settings.incomplete-dir"/>.
214 Note that you may also want to change
215 - <link linkend="opt-services.transmission.settings">settings.umask</link>.
222 - TCP port number to run the RPC/web interface.
224 - If instead you want to change the peer port,
225 - use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
226 - or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
227 + <xref linkend="opt-services.transmission.settings.umask"/>.
234 + default = "/var/lib/transmission";
236 The directory where Transmission will create <literal>${settingsDir}</literal>.
237 - as well as <literal>${downloadsDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed,
238 - and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
239 + as well as <literal>${downloadsDir}/</literal> unless
240 + <xref linkend="opt-services.transmission.settings.download-dir"/> is changed,
241 + and <literal>${incompleteDir}/</literal> unless
242 + <xref linkend="opt-services.transmission.settings.incomplete-dir"/> is changed.
246 @@ -119,19 +208,22 @@ in
248 Path to a JSON file to be merged with the settings.
249 Useful to merge a file which is better kept out of the Nix store
250 - because it contains sensible data like <link linkend="opt-services.transmission.settings">settings.rpc-password</link>.
251 + because it contains sensible data like
252 + <xref linkend="opt-services.transmission.settings.rpc-password"/>.
254 default = "/dev/null";
255 example = "/var/lib/secrets/transmission/settings.json";
258 - openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
259 + openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
261 + openRPCPort = mkEnableOption "opening of the RPC port in the firewall";
263 performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
264 to open many more connections at the same time.
266 Note that you may also want to increase
267 - <link linkend="opt-services.transmission.settings">settings.peer-limit-global</link>.
268 + <xref linkend="opt-services.transmission.settings.peer-limit-global"/>.
269 And be aware that these settings are quite aggressive
270 and might not suite your regular desktop use.
271 For instance, SSH sessions may time out more easily'';
272 @@ -152,36 +244,10 @@ in
273 install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
274 '' + optionalString cfg.settings.incomplete-dir-enabled ''
275 install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
276 + '' + optionalString cfg.settings.watch-dir-enabled ''
277 + install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
281 - { assertion = builtins.match "^/.*" cfg.home != null;
282 - message = "`services.transmission.home' must be an absolute path.";
284 - { assertion = types.path.check cfg.settings.download-dir;
285 - message = "`services.transmission.settings.download-dir' must be an absolute path.";
287 - { assertion = types.path.check cfg.settings.incomplete-dir;
288 - message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
290 - { assertion = types.path.check cfg.settings.watch-dir;
291 - message = "`services.transmission.settings.watch-dir' must be an absolute path.";
293 - { assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
294 - message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
296 - { assertion = types.port.check cfg.settings.rpc-port;
297 - message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
299 - # In case both port and settings.rpc-port are explicitely defined: they must be the same.
300 - { assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
301 - message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
305 - services.transmission.settings =
306 - optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
308 systemd.services.transmission = {
309 description = "Transmission BitTorrent Service";
310 after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
311 @@ -226,11 +292,9 @@ in
312 cfg.settings.download-dir
314 optional cfg.settings.incomplete-dir-enabled
315 - cfg.settings.incomplete-dir
317 - optional cfg.settings.watch-dir-enabled
318 - cfg.settings.watch-dir
320 + cfg.settings.incomplete-dir ++
321 + optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
322 + cfg.settings.watch-dir;
323 BindReadOnlyPaths = [
324 # No confinement done of /nix/store here like in systemd-confinement.nix,
325 # an AppArmor profile is provided to get a confinement based upon paths and rights.
326 @@ -239,8 +303,10 @@ in
329 optional (cfg.settings.script-torrent-done-enabled &&
330 - cfg.settings.script-torrent-done-filename != "")
331 - cfg.settings.script-torrent-done-filename;
332 + cfg.settings.script-torrent-done-filename != null)
333 + cfg.settings.script-torrent-done-filename ++
334 + optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
335 + cfg.settings.watch-dir;
336 # The following options are only for optimizing:
337 # systemd-analyze security transmission
338 AmbientCapabilities = "";
339 @@ -307,25 +373,28 @@ in
343 - networking.firewall = mkIf cfg.openFirewall (
344 - if cfg.settings.peer-port-random-on-start
346 - { allowedTCPPortRanges =
347 - [ { from = cfg.settings.peer-port-random-low;
348 - to = cfg.settings.peer-port-random-high;
351 - allowedUDPPortRanges =
352 - [ { from = cfg.settings.peer-port-random-low;
353 - to = cfg.settings.peer-port-random-high;
358 - { allowedTCPPorts = [ cfg.settings.peer-port ];
359 - allowedUDPPorts = [ cfg.settings.peer-port ];
362 + networking.firewall = mkMerge [
363 + (mkIf cfg.openPeerPorts (
364 + if cfg.settings.peer-port-random-on-start
366 + { allowedTCPPortRanges =
367 + [ { from = cfg.settings.peer-port-random-low;
368 + to = cfg.settings.peer-port-random-high;
371 + allowedUDPPortRanges =
372 + [ { from = cfg.settings.peer-port-random-low;
373 + to = cfg.settings.peer-port-random-high;
378 + { allowedTCPPorts = [ cfg.settings.peer-port ];
379 + allowedUDPPorts = [ cfg.settings.peer-port ];
382 + (mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
385 boot.kernel.sysctl = mkMerge [
386 # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
387 @@ -340,74 +409,57 @@ in
388 # Increase the number of available source (local) TCP and UDP ports to 49151.
389 # Usual default is 32768 60999, ie. 28231 ports.
390 # Find out your current usage with: ss -s
391 - "net.ipv4.ip_local_port_range" = "16384 65535";
392 + "net.ipv4.ip_local_port_range" = mkDefault "16384 65535";
393 # Timeout faster generic TCP states.
394 # Usual default is 600.
395 # Find out your current usage with: watch -n 1 netstat -nptuo
396 - "net.netfilter.nf_conntrack_generic_timeout" = 60;
397 + "net.netfilter.nf_conntrack_generic_timeout" = mkDefault 60;
398 # Timeout faster established but inactive connections.
399 # Usual default is 432000.
400 - "net.netfilter.nf_conntrack_tcp_timeout_established" = 600;
401 + "net.netfilter.nf_conntrack_tcp_timeout_established" = mkDefault 600;
402 # Clear immediately TCP states after timeout.
403 # Usual default is 120.
404 - "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = 1;
405 + "net.netfilter.nf_conntrack_tcp_timeout_time_wait" = mkDefault 1;
406 # Increase the number of trackable connections.
407 # Usual default is 262144.
408 # Find out your current usage with: conntrack -C
409 - "net.netfilter.nf_conntrack_max" = 1048576;
410 + "net.netfilter.nf_conntrack_max" = mkDefault 1048576;
414 security.apparmor.policies."bin.transmission-daemon".profile = ''
415 - include <tunables/global>
416 - ${pkgs.transmission}/bin/transmission-daemon {
417 - include <abstractions/base>
418 - include <abstractions/nameservice>
419 - include <abstractions/ssl_certs>
420 - include "${pkgs.apparmorRulesFromClosure
421 - { name = "transmission-daemon"; }
422 - [ pkgs.transmission ]}"
423 - include <local/bin.transmission-daemon>
425 - r @{PROC}/sys/kernel/random/uuid,
426 - r @{PROC}/sys/vm/overcommit_memory,
427 - r @{PROC}/@{pid}/environ,
428 - r @{PROC}/@{pid}/mounts,
429 - rwk /tmp/tr_session_id_*,
430 - r /run/systemd/resolve/stub-resolv.conf,
432 - r ${pkgs.openssl.out}/etc/**,
433 - r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
435 - owner rw ${cfg.home}/${settingsDir}/**,
436 - rw ${cfg.settings.download-dir}/**,
437 - ${optionalString cfg.settings.incomplete-dir-enabled ''
438 - rw ${cfg.settings.incomplete-dir}/**,
440 - ${optionalString cfg.settings.watch-dir-enabled ''
441 - rw ${cfg.settings.watch-dir}/**,
444 - rw ${cfg.settings.download-dir}/**,
445 - ${optionalString cfg.settings.incomplete-dir-enabled ''
446 - rw ${cfg.settings.incomplete-dir}/**,
448 - ${optionalString cfg.settings.watch-dir-enabled ''
449 - rw ${cfg.settings.watch-dir}/**,
453 - ${optionalString (cfg.settings.script-torrent-done-enabled &&
454 - cfg.settings.script-torrent-done-filename != "") ''
455 - # Stack transmission_directories profile on top of
456 - # any existing profile for script-torrent-done-filename
457 - # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
458 - # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
459 - px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
462 + include "${pkgs.transmission.apparmor}/bin.transmission-daemon"
464 + security.apparmor.includes."local/bin.transmission-daemon" = ''
465 + r ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
467 + owner rw ${cfg.home}/${settingsDir}/**,
468 + rw ${cfg.settings.download-dir}/**,
469 + ${optionalString cfg.settings.incomplete-dir-enabled ''
470 + rw ${cfg.settings.incomplete-dir}/**,
472 + ${optionalString cfg.settings.watch-dir-enabled ''
473 + r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
476 + rw ${cfg.settings.download-dir}/**,
477 + ${optionalString cfg.settings.incomplete-dir-enabled ''
478 + rw ${cfg.settings.incomplete-dir}/**,
480 + ${optionalString cfg.settings.watch-dir-enabled ''
481 + r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
485 + ${optionalString (cfg.settings.script-torrent-done-enabled &&
486 + cfg.settings.script-torrent-done-filename != null) ''
487 + # Stack transmission_directories profile on top of
488 + # any existing profile for script-torrent-done-filename
489 + # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=
490 + # https://gitlab.com/apparmor/apparmor/-/wikis/AppArmorStacking#seccomp-and-no_new_privs
491 + px ${cfg.settings.script-torrent-done-filename} -> &@{dirs},
494 - security.apparmor.includes."local/bin.transmission-daemon" = "";
497 meta.maintainers = with lib.maintainers; [ julm ];
498 diff --git a/pkgs/applications/networking/p2p/transmission/default.nix b/pkgs/applications/networking/p2p/transmission/default.nix
499 index ab4fc0908ba..858b09c9aaa 100644
500 --- a/pkgs/applications/networking/p2p/transmission/default.nix
501 +++ b/pkgs/applications/networking/p2p/transmission/default.nix
503 , enableSystemd ? stdenv.isLinux
504 , enableDaemon ? true
506 +, apparmorRulesFromClosure
510 @@ -37,6 +38,8 @@ in stdenv.mkDerivation {
511 fetchSubmodules = true;
514 + outputs = [ "out" "apparmor" ];
518 mkFlag = opt: if opt then "ON" else "OFF";
519 @@ -72,6 +75,30 @@ in stdenv.mkDerivation {
521 NIX_LDFLAGS = lib.optionalString stdenv.isDarwin "-framework CoreFoundation";
524 + install -D /dev/stdin $apparmor/bin.transmission-daemon <<EOF
525 + include <tunables/global>
526 + $out/bin/transmission-daemon {
527 + include <abstractions/base>
528 + include <abstractions/nameservice>
529 + include <abstractions/ssl_certs>
530 + include "${apparmorRulesFromClosure { name = "transmission-daemon"; } ([
531 + curl libevent openssl pcre zlib
532 + ] ++ lib.optionals enableSystemd [ systemd ]
533 + ++ lib.optionals stdenv.isLinux [ inotify-tools ]
535 + r @{PROC}/sys/kernel/random/uuid,
536 + r @{PROC}/sys/vm/overcommit_memory,
537 + r @{PROC}/@{pid}/environ,
538 + r @{PROC}/@{pid}/mounts,
539 + rwk /tmp/tr_session_id_*,
540 + r /run/systemd/resolve/stub-resolv.conf,
542 + include <local/bin.transmission-daemon>
548 description = "A fast, easy and free BitTorrent client";