summary refs log tree commit diff
path: root/nixos/modules/services/torrent/transmission.nix
diff options
context:
space:
mode:
authorJulien Moutinho <julm+nixpkgs@sourcephile.fr>2020-12-03 18:08:55 +0100
committerJulien Moutinho <julm+nixpkgs@sourcephile.fr>2021-10-18 19:35:41 +0200
commit63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb (patch)
treed3270d60eb017e39b095f0fc4b13d8a3a4d7ad43 /nixos/modules/services/torrent/transmission.nix
parentab8489cc404462f4108c0735c7e33106917f82d9 (diff)
downloadnixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.tar
nixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.tar.gz
nixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.tar.bz2
nixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.tar.lz
nixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.tar.xz
nixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.tar.zst
nixpkgs-63e87892ffdf8af5faa0239d3fb4d53fab0bc2bb.zip
nixos/transmission: use freeformType on settings
Diffstat (limited to 'nixos/modules/services/torrent/transmission.nix')
-rw-r--r--nixos/modules/services/torrent/transmission.nix306
1 files changed, 186 insertions, 120 deletions
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
index b8cfcf39121..301cb2647b5 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 <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}";
+            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 {
@@ -74,31 +172,22 @@ in
         example = "775";
         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>.
+          on the directories <xref linkend="opt-services.transmission.settings.download-dir"/>
+          and <xref linkend="opt-services.transmission.settings.incomplete-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 +208,21 @@ 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";
+      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'';
@@ -156,34 +247,6 @@ in
       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";
@@ -228,11 +291,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 +302,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 = "";
@@ -309,25 +372,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 +408,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 +438,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 +446,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=