let
cfg = config.services.transmission;
inherit (config.environment) etc;
- apparmor = config.security.apparmor.enable;
- stateDir = "/var/lib/transmission";
- # TODO: switch to configGen.json once RFC0042 is implemented
- settingsFile = pkgs.writeText "settings.json" (builtins.toJSON (cfg.settings // {
- download-dir = "${stateDir}/Downloads";
- incomplete-dir = "${stateDir}/.incomplete";
- }));
+ apparmor = config.security.apparmor;
+ rootDir = "/run/transmission";
settingsDir = ".config/transmission-daemon";
- makeAbsolute = base: path:
- if builtins.match "^/.*" path == null
- then base+"/"+path else path;
+ downloadsDir = "Downloads";
+ incompleteDir = ".incomplete";
+ watchDir = "watchdir";
+ 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 ''
- Whether or not to enable the headless Transmission BitTorrent daemon.
+ enable = mkEnableOption ''the headless Transmission BitTorrent daemon.
Transmission daemon can be controlled via the RPC interface using
- transmission-remote, the WebUI (http://${cfg.settings.rpc-bind-address}:${toString cfg.settings.rpc-port}/ by default),
+ transmission-remote, the WebUI (http://127.0.0.1:9091/ by default),
or other clients like stig or tremc.
- Torrents are downloaded to ${cfg.settings.download-dir} by default and are
- accessible to users in the "transmission" group.
- '';
+ 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 = attrs:
- let super = recursiveUpdate default attrs; in
- super // {
- download-dir = makeAbsolute cfg.home super.download-dir;
- incomplete-dir = makeAbsolute cfg.home super.incomplete-dir;
- };
- default =
- {
- download-dir = "${cfg.home}/Downloads";
- incomplete-dir = "${cfg.home}/.incomplete";
- incomplete-dir-enabled = true;
- 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;
- umask = 18; # 0o022 in decimal as expected by Transmission, obtained with: echo $((8#022))
- };
- 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 https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
- for documentation.
+ See <link xlink:href="https://github.com/transmission/transmission/wiki/Editing-Configuration-Files">Transmission's Wiki</link>
+ 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 {
default = "770";
example = "775";
description = ''
- The permissions set by the <literal>systemd-tmpfiles-setup</literal> service
- on <link linkend="opt-services.transmission.settings">settings.download-dir</link>
- and <link linkend="opt-services.transmission.settings">settings.incomplete-dir</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>.
+ The permissions set by <literal>systemd.activationScripts.transmission-daemon</literal>
+ 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
+ <xref linkend="opt-services.transmission.settings.umask"/>.
'';
};
home = mkOption {
type = types.path;
- default = stateDir;
+ default = "/var/lib/transmission";
description = ''
- The directory where Transmission will create <literal>.config/transmission-daemon/</literal>.
- as well as <literal>Downloads/</literal> unless <link linkend="opt-services.transmission.settings">settings.download-dir</link> is changed,
- and <literal>.incomplete/</literal> unless <link linkend="opt-services.transmission.settings">settings.incomplete-dir</link> is changed.
+ The directory where Transmission will create <literal>${settingsDir}</literal>.
+ 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.
'';
};
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>.
+ because it contains sensible data like
+ <xref linkend="opt-services.transmission.settings.rpc-password"/>.
'';
default = "/dev/null";
example = "/var/lib/secrets/transmission/settings.json";
};
- openFirewall = mkOption {
- type = types.bool;
- default = true;
- description = ''
- Whether to automatically open 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
+ <xref linkend="opt-services.transmission.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'';
};
};
config = mkIf cfg.enable {
- systemd.tmpfiles.rules =
- optional (cfg.home != stateDir) "d '${cfg.home}/${settingsDir}' 700 '${cfg.user}' '${cfg.group}' - -"
- ++ [ "d '${cfg.settings.download-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -" ]
- ++ optional cfg.settings.incomplete-dir-enabled
- "d '${cfg.settings.incomplete-dir}' '${cfg.downloadDirPermissions}' '${cfg.user}' '${cfg.group}' - -";
-
- assertions = [
- { assertion = builtins.match "^/.*" cfg.home != null;
- message = "`services.transmission.home' 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; };
+ # Note that using systemd.tmpfiles would not work here
+ # because it would fail when creating a directory
+ # with a different owner than its parent directory, by saying:
+ # Detected unsafe path transition /home/foo → /home/foo/Downloads during canonicalization of /home/foo/Downloads
+ # 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}'
+ '';
systemd.services.transmission = {
description = "Transmission BitTorrent Service";
- after = [ "network.target" ] ++ optional apparmor "apparmor.service";
- requires = optional apparmor "apparmor.service";
+ after = [ "network.target" ] ++ optional apparmor.enable "apparmor.service";
+ requires = optional apparmor.enable "apparmor.service";
wantedBy = [ "multi-user.target" ];
environment.CURL_CA_BUNDLE = etc."ssl/certs/ca-certificates.crt".source;
- preStart = ''
- set -eux
- ${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' >'${stateDir}/${settingsDir}/settings.json'
- '';
serviceConfig = {
- WorkingDirectory = stateDir;
- ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f";
+ # Use "+" because credentialsFile may not be accessible to User= or Group=.
+ ExecStartPre = [("+" + pkgs.writeShellScript "transmission-prestart" ''
+ set -eu${lib.optionalString (cfg.settings.message-level >= 3) "x"}
+ ${pkgs.jq}/bin/jq --slurp add ${settingsFile} '${cfg.credentialsFile}' |
+ install -D -m 600 -o '${cfg.user}' -g '${cfg.group}' /dev/stdin \
+ '${cfg.home}/${settingsDir}/settings.json'
+ '')];
+ ExecStart="${pkgs.transmission}/bin/transmission-daemon -f";
ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
User = cfg.user;
Group = cfg.group;
- StateDirectory = removePrefix "/var/lib/" stateDir + "/" + settingsDir;
- StateDirectoryMode = "0700";
+ # 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";
+ # Using RootDirectory= makes it possible
+ # to use the same paths download-dir/incomplete-dir
+ # (which appear in user's interfaces) without requiring cfg.user
+ # to have access to their parent directories,
+ # by using BindPaths=/BindReadOnlyPaths=.
+ # Note that TemporaryFileSystem= could have been used instead
+ # but not without adding some BindPaths=/BindReadOnlyPaths=
+ # that would only be needed for ExecStartPre=,
+ # because RootDirectoryStartOnly=true would not help.
+ RootDirectory = rootDir;
+ RootDirectoryStartOnly = true;
+ MountAPIVFS = true;
BindPaths =
- optional (cfg.home != stateDir) "${cfg.home}/${settingsDir}:${stateDir}/${settingsDir}"
- ++ [ "${cfg.settings.download-dir}:${stateDir}/Downloads" ]
- ++ optional cfg.settings.incomplete-dir-enabled "${cfg.settings.incomplete-dir}:${stateDir}/.incomplete";
- # The following options give:
+ [ "${cfg.home}/${settingsDir}"
+ cfg.settings.download-dir
+ ] ++
+ optional cfg.settings.incomplete-dir-enabled
+ 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.
+ builtins.storeDir
+ "/etc"
+ "/run"
+ ] ++
+ optional (cfg.settings.script-torrent-done-enabled &&
+ 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
- # → Overall exposure level for transmission.service: 1.5 OK
AmbientCapabilities = "";
CapabilityBoundingSet = "";
+ # ProtectClock= adds DeviceAllow=char-rtc r
+ DeviceAllow = "";
LockPersonality = true;
MemoryDenyWriteExecute = true;
NoNewPrivileges = true;
PrivateDevices = true;
PrivateMounts = true;
- PrivateNetwork = false;
+ PrivateNetwork = mkDefault false;
PrivateTmp = true;
- PrivateUsers = false;
+ PrivateUsers = true;
ProtectClock = true;
ProtectControlGroups = true;
- ProtectHome = mkDefault true;
+ # ProtectHome=true would not allow BindPaths= to work accross /home,
+ # and ProtectHome=tmpfs would break statfs(),
+ # preventing transmission-daemon to report the available free space.
+ # However, RootDirectory= is used, so this is not a security concern
+ # since there would be nothing in /home but any BindPaths= wanted by the user.
+ ProtectHome = "read-only";
ProtectHostname = true;
ProtectKernelLogs = true;
ProtectKernelModules = true;
ProtectKernelTunables = true;
- ProtectSystem = mkDefault "strict";
- ReadWritePaths = [ stateDir ];
+ ProtectSystem = "strict";
RemoveIPC = true;
- RestrictAddressFamilies = [ "AF_INET" "AF_INET6" ];
+ # AF_UNIX may become usable one day:
+ # https://github.com/transmission/transmission/issues/441
+ RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ];
RestrictNamespaces = true;
RestrictRealtime = true;
RestrictSUIDSGID = true;
- # In case transmission crashes with status=31/SYS,
- # having systemd.coredump.enable = true
- # and environment.enableDebugInfo = true
- # enables to use coredumpctl debug to find the denied syscall.
SystemCallFilter = [
- "@default"
- "@aio"
- "@basic-io"
- #"@chown"
- #"@clock"
- #"@cpu-emulation"
- #"@debug"
- "@file-system"
- "@io-event"
- #"@ipc"
- #"@keyring"
- #"@memlock"
- #"@module"
- #"@mount"
- "@network-io"
- #"@obsolete"
- #"@pkey"
- #"@privileged"
- # Reached when querying infos through RPC (eg. with stig)
- "quotactl"
- "@process"
- #"@raw-io"
- #"@reboot"
- #"@resources"
- #"@setuid"
- "@signal"
- #"@swap"
- "@sync"
"@system-service"
- "@timer"
+ # Groups in @system-service which do not contain a syscall
+ # listed by perf stat -e 'syscalls:sys_enter_*' transmission-daemon -f
+ # in tests, and seem likely not necessary for transmission-daemon.
+ "~@aio" "~@chown" "~@keyring" "~@memlock" "~@resources" "~@setuid" "~@timer"
+ # In the @privileged group, but reached when querying infos through RPC (eg. with stig).
+ "quotactl"
];
SystemCallArchitectures = "native";
- UMask = "0077";
+ SystemCallErrorNumber = "EPERM";
};
};
group = cfg.group;
uid = config.ids.uids.transmission;
description = "Transmission BitTorrent user";
- home = stateDir;
- createHome = false;
+ home = cfg.home;
};
});
};
});
- 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 ];
- }
- );
-
- security.apparmor.enforceProfiles = optional apparmor "bin/transmission-daemon";
- security.apparmor.profiles."bin/transmission-daemon" = ''
- #include <tunables/global>
-
- /run/current-system/sw/bin/transmission-daemon {
- #include <abstractions/base>
- #include <abstractions/nameservice>
+ 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 ]; })
+ ];
- ${getLib pkgs.glibc}/lib/*.so* mr,
- ${getLib pkgs.libevent}/lib/libevent*.so* mr,
- ${getLib pkgs.curl}/lib/libcurl*.so* mr,
- ${getLib pkgs.openssl}/lib/libssl*.so* mr,
- ${getLib pkgs.openssl}/lib/libcrypto*.so* mr,
- ${getLib pkgs.zlib}/lib/libz*.so* mr,
- ${getLib pkgs.libssh2}/lib/libssh2*.so* mr,
- ${getLib pkgs.systemd}/lib/libsystemd*.so* mr,
- ${getLib pkgs.xz}/lib/liblzma*.so* mr,
- ${getLib pkgs.libgcrypt}/lib/libgcrypt*.so* mr,
- ${getLib pkgs.libgpgerror}/lib/libgpg-error*.so* mr,
- ${getLib pkgs.nghttp2}/lib/libnghttp2*.so* mr,
- ${getLib pkgs.c-ares}/lib/libcares*.so* mr,
- ${getLib pkgs.libcap}/lib/libcap*.so* mr,
- ${getLib pkgs.attr}/lib/libattr*.so* mr,
- ${getLib pkgs.lz4}/lib/liblz4*.so* mr,
- ${getLib pkgs.libkrb5}/lib/lib*.so* mr,
- ${getLib pkgs.keyutils}/lib/libkeyutils*.so* mr,
- ${getLib pkgs.utillinuxMinimal.out}/lib/libblkid.so* mr,
- ${getLib pkgs.utillinuxMinimal.out}/lib/libmount.so* mr,
- ${getLib pkgs.utillinuxMinimal.out}/lib/libuuid.so* mr,
- ${getLib pkgs.gcc.cc.lib}/lib/libstdc++.so* mr,
- ${getLib pkgs.gcc.cc.lib}/lib/libgcc_s.so* mr,
+ boot.kernel.sysctl = mkMerge [
+ # Transmission uses a single UDP socket in order to implement multiple uTP sockets,
+ # and thus expects large kernel buffers for the UDP socket,
+ # https://trac.transmissionbt.com/browser/trunk/libtransmission/tr-udp.c?rev=11956.
+ # at least up to the values hardcoded here:
+ (mkIf cfg.settings.utp-enabled {
+ "net.core.rmem_max" = mkDefault "4194304"; # 4MB
+ "net.core.wmem_max" = mkDefault "1048576"; # 1MB
+ })
+ (mkIf cfg.performanceNetParameters {
+ # 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" = 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" = mkDefault 60;
+ # Timeout faster established but inactive connections.
+ # Usual default is 432000.
+ "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" = 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" = mkDefault 1048576;
+ })
+ ];
- @{PROC}/sys/kernel/random/uuid r,
- @{PROC}/sys/vm/overcommit_memory r,
- @{PROC}/@{pid}/environ r,
- @{PROC}/@{pid}/mounts r,
- /tmp/tr_session_id_* rwk,
+ security.apparmor.policies."bin.transmission-daemon".profile = ''
+ include <tunables/global>
+ ${pkgs.transmission}/bin/transmission-daemon {
+ include <abstractions/base>
+ include <abstractions/nameservice>
+ include <abstractions/ssl_certs>
+ include "${pkgs.apparmorRulesFromClosure {} [pkgs.transmission]}"
+ include <local/bin.transmission-daemon>
- ${pkgs.openssl.out}/etc/** r,
- ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE} r,
- ${pkgs.transmission}/share/transmission/** r,
+ 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 ${config.systemd.services.transmission.environment.CURL_CA_BUNDLE},
+ r /run/systemd/resolve/stub-resolv.conf,
- owner ${stateDir}/${settingsDir}/** rw,
+ 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}/**,
+ ''}
+ }
- ${stateDir}/Downloads/** rw,
- ${optionalString cfg.settings.incomplete-dir-enabled ''
- ${stateDir}/.incomplete/** rw,
- ''}
- }
+ ${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 ];