about summary refs log tree commit diff
path: root/nixpkgs/nixos/modules/services/torrent/transmission.nix
diff options
context:
space:
mode:
authorAlyssa Ross <hi@alyssa.is>2022-01-03 23:55:00 +0000
committerAlyssa Ross <hi@alyssa.is>2022-02-19 11:03:39 +0000
commitf4cf97a04cd5d0b86aa46baec9fb228a8f671c03 (patch)
tree28192415ff39a661d0001563bf81cc93fa25d16d /nixpkgs/nixos/modules/services/torrent/transmission.nix
parentf8422837c9bde058e8f2de37702e7e94b2226040 (diff)
parent18c84ea816348e2a098390101b92d1e39a9dbd45 (diff)
downloadnixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.tar
nixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.tar.gz
nixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.tar.bz2
nixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.tar.lz
nixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.tar.xz
nixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.tar.zst
nixlib-f4cf97a04cd5d0b86aa46baec9fb228a8f671c03.zip
Merge commit '18c84ea816348e2a098390101b92d1e39a9dbd45'
Conflicts:
	nixpkgs/nixos/modules/misc/documentation.nix
	nixpkgs/pkgs/applications/networking/browsers/firefox/packages.nix
	nixpkgs/pkgs/applications/window-managers/sway/default.nix
	nixpkgs/pkgs/build-support/rust/build-rust-package/default.nix
	nixpkgs/pkgs/development/go-modules/generic/default.nix
	nixpkgs/pkgs/development/interpreters/ruby/default.nix
	nixpkgs/pkgs/development/interpreters/ruby/patchsets.nix
	nixpkgs/pkgs/development/libraries/boehm-gc/7.6.6.nix
	nixpkgs/pkgs/development/python-modules/django-mailman3/default.nix
	nixpkgs/pkgs/servers/mail/mailman/web.nix
	nixpkgs/pkgs/top-level/aliases.nix
	nixpkgs/pkgs/top-level/all-packages.nix
	nixpkgs/pkgs/top-level/impure.nix
Diffstat (limited to 'nixpkgs/nixos/modules/services/torrent/transmission.nix')
-rw-r--r--nixpkgs/nixos/modules/services/torrent/transmission.nix362
1 files changed, 225 insertions, 137 deletions
diff --git a/nixpkgs/nixos/modules/services/torrent/transmission.nix b/nixpkgs/nixos/modules/services/torrent/transmission.nix
index b8cfcf391215..d12d8aa23980 100644
--- a/nixpkgs/nixos/modules/services/torrent/transmission.nix
+++ b/nixpkgs/nixos/modules/services/torrent/transmission.nix
@@ -4,18 +4,24 @@ with lib;
 
 let
   cfg = config.services.transmission;
+  opt = options.services.transmission;
   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,81 +30,170 @@ 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 <xref linkend="opt-services.transmission.home"/>/${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
           <literal>.config/transmission-daemon/settings.json</literal>
-          (each time the service starts). String values must be quoted, integer and
-          boolean values must not.
+          (each time the service starts).
 
           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}";
+            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 = types.str;
-        default = "770";
-        example = "775";
+        type = with types; nullOr str;
+        default = null;
+        example = "770";
         description = ''
-          The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
-          on the directories <link linkend="opt-services.transmission.settings">settings.download-dir</link>
-          and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link>.
+          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
-          <link linkend="opt-services.transmission.settings">settings.umask</link>.
-        '';
-      };
-
-      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>.
+          <xref linkend="opt-services.transmission.settings.umask"/>.
         '';
       };
 
       home = mkOption {
         type = types.path;
-        default = homeDir;
+        default = "/var/lib/transmission";
         description = ''
           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,
-          and <literal>${incompleteDir}/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
+          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.
         '';
       };
 
@@ -119,19 +214,30 @@ 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 <link linkend="opt-services.transmission.settings">settings.rpc-password</link>.
+          to set secret config parameters like <code>rpc-password</code>.
         '';
         default = "/dev/null";
         example = "/var/lib/secrets/transmission/settings.json";
       };
 
-      openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
+      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
-        <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 might not suite your regular desktop use.
         For instance, SSH sessions may time out more easily'';
@@ -146,43 +252,17 @@ in
     # 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.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}'
-      '';
-
-    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; };
+    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";
@@ -199,15 +279,13 @@ in
           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}";
+        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";
-        # Avoid mounting rootDir in the own rootDir of ExecStart='s mount namespace.
-        InaccessiblePaths = ["-+${rootDir}"];
         # This is for BindPaths= and BindReadOnlyPaths=
         # to allow traversal of directories they create in RootDirectory=.
         UMask = "0066";
@@ -228,11 +306,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.
@@ -241,8 +317,18 @@ 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;
+        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 = "";
@@ -287,7 +373,6 @@ in
           "quotactl"
         ];
         SystemCallArchitectures = "native";
-        SystemCallErrorNumber = "EPERM";
       };
     };
 
@@ -309,25 +394,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,
@@ -342,21 +430,21 @@ 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;
       })
     ];
 
@@ -372,7 +460,7 @@ in
         rw ${cfg.settings.incomplete-dir}/**,
       ''}
       ${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 {
         rw ${cfg.settings.download-dir}/**,
@@ -380,12 +468,12 @@ in
           rw ${cfg.settings.incomplete-dir}/**,
         ''}
         ${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 &&
-                        cfg.settings.script-torrent-done-filename != "") ''
+                        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=