1 diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
2 index 014a22bb5a8..6f6d5a05a2a 100644
3 --- a/nixos/modules/services/torrent/transmission.nix
4 +++ b/nixos/modules/services/torrent/transmission.nix
5 @@ -12,10 +12,15 @@ let
6 downloadsDir = "Downloads";
7 incompleteDir = ".incomplete";
9 - # TODO: switch to configGen.json once RFC0042 is implemented
10 settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
14 + (mkRenamedOptionModule ["services" "transmission" "port"]
15 + ["services" "transmission" "settings" "rpc-port"])
16 + (mkRenamedOptionModule ["services" "transmission" "openFirewall"]
17 + ["services" "transmission" "openPeerPorts"])
20 services.transmission = {
21 enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
22 @@ -27,45 +32,140 @@ in
23 Torrents are downloaded to ${homeDir}/${downloadsDir} by default and are
24 accessible to users in the "transmission" group'';
26 - settings = mkOption rec {
27 - # TODO: switch to types.config.json as prescribed by RFC0042 once it's implemented
29 - apply = recursiveUpdate default;
32 - download-dir = "${cfg.home}/${downloadsDir}";
33 - incomplete-dir = "${cfg.home}/${incompleteDir}";
34 - incomplete-dir-enabled = true;
35 - watch-dir = "${cfg.home}/${watchDir}";
36 - watch-dir-enabled = false;
39 - peer-port-random-high = 65535;
40 - peer-port-random-low = 49152;
41 - peer-port-random-on-start = false;
42 - rpc-bind-address = "127.0.0.1";
44 - script-torrent-done-enabled = false;
45 - script-torrent-done-filename = "";
46 - umask = 2; # 0o002 in decimal as expected by Transmission
51 - download-dir = "/srv/torrents/";
52 - incomplete-dir = "/srv/torrents/.incomplete/";
53 - incomplete-dir-enabled = true;
54 - rpc-whitelist = "127.0.0.1,192.168.*.*";
56 + settings = mkOption {
58 - Attribute set whose fields overwrites fields in
59 + Settings whose fields overwrites fields in
60 <literal>.config/transmission-daemon/settings.json</literal>
61 - (each time the service starts). String values must be quoted, integer and
62 - boolean values must not.
63 + (each time the service starts).
65 See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
67 + for documentation of settings not explicitely covered by this module.
70 + type = types.submodule {
71 + freeformType = with types;
72 + (attrsOf (nullOr (oneOf [str int bool]))) // {
73 + description = "setting option";
75 + options.download-dir = mkOption {
77 + default = "${cfg.home}/${downloadsDir}";
78 + description = "Directory where to download torrents.";
80 + options.incomplete-dir = mkOption {
82 + default = "${cfg.home}/${incompleteDir}";
85 + <link linkend="opt-services.transmission.settings.incomplete-dir-enabled">incomplete-dir-enabled</link>,
86 + new torrents will download the files to this directory.
87 + When complete, the files will be moved to download-dir
88 + <link linkend="opt-services.transmission.settings.download-dir">download-dir</link>.
91 + options.incomplete-dir-enabled = mkOption {
96 + options.message-level = mkOption {
97 + type = types.ints.between 0 2;
99 + description = "Set verbosity of transmission messages.";
101 + options.peer-port = mkOption {
104 + description = "The peer port to listen for incoming connections.";
106 + options.peer-port-random-high = mkOption {
110 + The maximum peer port to listen to for incoming connections
111 + when <link linkend="opt-services.transmission.settings.peer-port-random-on-start">peer-port-random-on-start</link> is enabled.
114 + options.peer-port-random-low = mkOption {
118 + The minimal peer port to listen to for incoming connections
119 + when <link linkend="opt-services.transmission.settings.peer-port-random-on-start">peer-port-random-on-start</link> is enabled.
122 + options.peer-port-random-on-start = mkOption {
125 + description = "Randomize the peer port.";
127 + options.rpc-bind-address = mkOption {
129 + default = "127.0.0.1";
130 + example = "0.0.0.0";
132 + Where to listen for RPC connections.
133 + Use \"0.0.0.0\" to listen on all interfaces.
136 + options.rpc-port = mkOption {
139 + description = "The RPC port to listen to.";
141 + options.script-torrent-done-enabled = mkOption {
146 + <link linkend="opt-services.transmission.settings.script-torrent-done-filename">script-torrent-done-filename</link>
147 + at torrent completion.
150 + options.script-torrent-done-filename = mkOption {
151 + type = types.nullOr types.path;
153 + description = "Executable to be run at torrent completion.";
155 + options.umask = mkOption {
159 + Sets transmission's file mode creation mask.
160 + See the umask(2) manpage for more information.
161 + Users who want their saved torrents to be world-writable
162 + may want to set this value to 0.
163 + Bear in mind that the json markup language only accepts numbers in base 10,
164 + so the standard umask(2) octal notation "022" is written in settings.json as 18.
167 + options.utp-enabled = mkOption {
171 + Whether to enable <link xlink:href="http://en.wikipedia.org/wiki/Micro_Transport_Protocol">Micro Transport Protocol (µTP)</link>.
174 + options.watch-dir = mkOption {
176 + default = "${cfg.home}/${watchDir}";
177 + description = "Watch a directory for torrent files and add them to transmission.";
179 + options.watch-dir-enabled = mkOption {
182 + description = ''Whether to enable the
183 + <link linkend="opt-services.transmission.settings.watch-dir">watch-dir</link>.
186 + options.trash-original-torrent-files = mkOption {
189 + description = ''Whether to delete torrents added from the
190 + <link linkend="opt-services.transmission.settings.watch-dir">watch-dir</link>.
196 downloadDirPermissions = mkOption {
197 @@ -81,17 +181,6 @@ in
204 - TCP port number to run the RPC/web interface.
206 - If instead you want to change the peer port,
207 - use <link linkend="opt-services.transmission.settings">settings.peer-port</link>
208 - or <link linkend="opt-services.transmission.settings">settings.peer-port-random-on-start</link>.
215 @@ -125,7 +214,9 @@ in
216 example = "/var/lib/secrets/transmission/settings.json";
219 - openFirewall = mkEnableOption "opening of the peer port(s) in the firewall";
220 + openPeerPorts = mkEnableOption "opening of the peer port(s) in the firewall";
222 + openRPCPort = mkEnableOption "opening of the RPC port in the firewall";
224 performanceNetParameters = mkEnableOption ''tweaking of kernel parameters
225 to open many more connections at the same time.
226 @@ -152,36 +243,10 @@ in
227 install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.download-dir}'
228 '' + optionalString cfg.settings.incomplete-dir-enabled ''
229 install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.incomplete-dir}'
230 + '' + optionalString cfg.settings.watch-dir-enabled ''
231 + install -d -m '${cfg.downloadDirPermissions}' -o '${cfg.user}' -g '${cfg.group}' '${cfg.settings.watch-dir}'
235 - { assertion = builtins.match "^/.*" cfg.home != null;
236 - message = "`services.transmission.home' must be an absolute path.";
238 - { assertion = types.path.check cfg.settings.download-dir;
239 - message = "`services.transmission.settings.download-dir' must be an absolute path.";
241 - { assertion = types.path.check cfg.settings.incomplete-dir;
242 - message = "`services.transmission.settings.incomplete-dir' must be an absolute path.";
244 - { assertion = types.path.check cfg.settings.watch-dir;
245 - message = "`services.transmission.settings.watch-dir' must be an absolute path.";
247 - { assertion = cfg.settings.script-torrent-done-filename == "" || types.path.check cfg.settings.script-torrent-done-filename;
248 - message = "`services.transmission.settings.script-torrent-done-filename' must be an absolute path.";
250 - { assertion = types.port.check cfg.settings.rpc-port;
251 - message = "${toString cfg.settings.rpc-port} is not a valid port number for `services.transmission.settings.rpc-port`.";
253 - # In case both port and settings.rpc-port are explicitely defined: they must be the same.
254 - { assertion = !options.services.transmission.port.isDefined || cfg.port == cfg.settings.rpc-port;
255 - message = "`services.transmission.port' is not equal to `services.transmission.settings.rpc-port'";
259 - services.transmission.settings =
260 - optionalAttrs options.services.transmission.port.isDefined { rpc-port = cfg.port; };
262 systemd.services.transmission = {
263 description = "Transmission BitTorrent Service";
264 after = [ "network.target" ] ++ optional apparmor "apparmor.service";
265 @@ -226,11 +291,9 @@ in
266 cfg.settings.download-dir
268 optional cfg.settings.incomplete-dir-enabled
269 - cfg.settings.incomplete-dir
271 - optional cfg.settings.watch-dir-enabled
272 - cfg.settings.watch-dir
274 + cfg.settings.incomplete-dir ++
275 + optional (cfg.settings.watch-dir-enabled && cfg.settings.trash-original-torrent-files)
276 + cfg.settings.watch-dir;
277 BindReadOnlyPaths = [
278 # No confinement done of /nix/store here like in systemd-confinement.nix,
279 # an AppArmor profile is provided to get a confinement based upon paths and rights.
280 @@ -238,8 +301,10 @@ in
283 optional (cfg.settings.script-torrent-done-enabled &&
284 - cfg.settings.script-torrent-done-filename != "")
285 - cfg.settings.script-torrent-done-filename;
286 + cfg.settings.script-torrent-done-filename != null)
287 + cfg.settings.script-torrent-done-filename ++
288 + optional (cfg.settings.watch-dir-enabled && !cfg.settings.trash-original-torrent-files)
289 + cfg.settings.watch-dir;
290 # The following options are only for optimizing:
291 # systemd-analyze security transmission
292 AmbientCapabilities = "";
293 @@ -306,25 +371,28 @@ in
297 - networking.firewall = mkIf cfg.openFirewall (
298 - if cfg.settings.peer-port-random-on-start
300 - { allowedTCPPortRanges =
301 - [ { from = cfg.settings.peer-port-random-low;
302 - to = cfg.settings.peer-port-random-high;
305 - allowedUDPPortRanges =
306 - [ { from = cfg.settings.peer-port-random-low;
307 - to = cfg.settings.peer-port-random-high;
312 - { allowedTCPPorts = [ cfg.settings.peer-port ];
313 - allowedUDPPorts = [ cfg.settings.peer-port ];
316 + networking.firewall = mkMerge [
317 + (mkIf cfg.openPeerPorts (
318 + if cfg.settings.peer-port-random-on-start
320 + { allowedTCPPortRanges =
321 + [ { from = cfg.settings.peer-port-random-low;
322 + to = cfg.settings.peer-port-random-high;
325 + allowedUDPPortRanges =
326 + [ { from = cfg.settings.peer-port-random-low;
327 + to = cfg.settings.peer-port-random-high;
332 + { allowedTCPPorts = [ cfg.settings.peer-port ];
333 + allowedUDPPorts = [ cfg.settings.peer-port ];
336 + (mkIf cfg.openRPCPort { allowedTCPPorts = [ cfg.settings.rpc-port ]; })
339 boot.kernel.sysctl = mkMerge [
340 # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
341 @@ -419,7 +487,7 @@ in
342 rw ${cfg.settings.incomplete-dir}/**,
344 ${optionalString cfg.settings.watch-dir-enabled ''
345 - rw ${cfg.settings.watch-dir}/**,
346 + r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
349 rw ${cfg.settings.download-dir}/**,
350 @@ -427,12 +495,12 @@ in
351 rw ${cfg.settings.incomplete-dir}/**,
353 ${optionalString cfg.settings.watch-dir-enabled ''
354 - rw ${cfg.settings.watch-dir}/**,
355 + r${optionalString cfg.settings.trash-original-torrent-files "w"} ${cfg.settings.watch-dir}/**,
359 ${optionalString (cfg.settings.script-torrent-done-enabled &&
360 - cfg.settings.script-torrent-done-filename != "") ''
361 + cfg.settings.script-torrent-done-filename != null) ''
362 # Stack transmission_directories profile on top of
363 # any existing profile for script-torrent-done-filename
364 # FIXME: to be tested as I'm not sure it works well with NoNewPrivileges=