diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix index e2daf3e0179dd..23f94d92042fc 100644 --- a/nixos/modules/services/torrent/transmission.nix +++ b/nixos/modules/services/torrent/transmission.nix @@ -1,10 +1,28 @@ -{ config, lib, pkgs, options, ... }: +{ + config, + lib, + pkgs, + options, + ... +}: let - inherit (lib) mkRenamedOptionModule mkAliasOptionModuleMD mkEnableOption - mkOption types literalExpression mkPackageOption mkIf - optionalString optional mkDefault escapeShellArgs optionalAttrs - mkMerge; + inherit (lib) + mkRenamedOptionModule + mkAliasOptionModuleMD + mkEnableOption + mkOption + types + literalExpression + mkPackageOption + mkIf + optionalString + optional + mkDefault + escapeShellArgs + optionalAttrs + mkMerge + ; cfg = config.services.transmission; opt = options.services.transmission; @@ -15,15 +33,36 @@ let downloadsDir = "Downloads"; incompleteDir = ".incomplete"; watchDir = "watchdir"; - settingsFormat = pkgs.formats.json {}; + settingsFormat = pkgs.formats.json { }; settingsFile = settingsFormat.generate "settings.json" cfg.settings; in { imports = [ - (mkRenamedOptionModule ["services" "transmission" "port"] - ["services" "transmission" "settings" "rpc-port"]) - (mkAliasOptionModuleMD ["services" "transmission" "openFirewall"] - ["services" "transmission" "openPeerPorts"]) + (mkRenamedOptionModule + [ + "services" + "transmission" + "port" + ] + [ + "services" + "transmission" + "settings" + "rpc-port" + ] + ) + (mkAliasOptionModuleMD + [ + "services" + "transmission" + "openFirewall" + ] + [ + "services" + "transmission" + "openPeerPorts" + ] + ) ]; options = { services.transmission = { @@ -49,7 +88,7 @@ in See [Transmission's Wiki](https://github.com/transmission/transmission/wiki/Editing-Configuration-Files) for documentation of settings not explicitly covered by this module. ''; - default = {}; + default = { }; type = types.submodule { freeformType = settingsFormat.type; options = { @@ -162,15 +201,17 @@ in watch-dir-enabled = mkOption { type = types.bool; default = false; - description = ''Whether to enable the - [](#opt-services.transmission.settings.watch-dir). + description = '' + Whether to enable the + [](#opt-services.transmission.settings.watch-dir). ''; }; trash-original-torrent-files = mkOption { type = types.bool; default = false; - description = ''Whether to delete torrents added from the - [](#opt-services.transmission.settings.watch-dir). + description = '' + Whether to delete torrents added from the + [](#opt-services.transmission.settings.watch-dir). ''; }; }; @@ -245,7 +286,7 @@ in extraFlags = mkOption { type = types.listOf types.str; - default = []; + default = [ ]; example = [ "--log-debug" ]; description = '' Extra flags passed to the transmission command in the service definition. @@ -321,18 +362,23 @@ in serviceConfig = { # 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="${cfg.package}/bin/transmission-daemon -f -g ${cfg.home}/${settingsDir} ${escapeShellArgs cfg.extraFlags}"; + 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 = "${cfg.package}/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)]; + RuntimeDirectory = [ (baseNameOf rootDir) ]; RuntimeDirectoryMode = "755"; # This is for BindPaths= and BindReadOnlyPaths= # to allow traversal of directories they create in RootDirectory=. @@ -350,27 +396,30 @@ in RootDirectoryStartOnly = true; MountAPIVFS = true; BindPaths = - [ "${cfg.home}/${settingsDir}" + [ + "${cfg.home}/${settingsDir}" cfg.settings.download-dir # Transmission may need to read in the host's /run (eg. /run/systemd/resolve) # or write in its private /run (eg. /run/host). "/run" - ] ++ - 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" - ] ++ - 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; + ] + ++ 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" + ] + ++ 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; StateDirectory = [ "transmission" "transmission/${settingsDir}" @@ -409,7 +458,11 @@ in RemoveIPC = true; # AF_UNIX may become usable one day: # https://github.com/transmission/transmission/issues/441 - RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" ]; + RestrictAddressFamilies = [ + "AF_UNIX" + "AF_INET" + "AF_INET6" + ]; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; @@ -418,7 +471,13 @@ in # 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" + "~@aio" + "~@chown" + "~@keyring" + "~@memlock" + "~@resources" + "~@setuid" + "~@timer" # In the @privileged group, but reached when querying infos through RPC (eg. with stig). "quotactl" ]; @@ -446,21 +505,24 @@ in 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; - } - ]; + 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 ]; + { + allowedTCPPorts = [ cfg.settings.peer-port ]; allowedUDPPorts = [ cfg.settings.peer-port ]; } )) @@ -522,14 +584,16 @@ in ''} } - ${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}, - ''} + ${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}, + '' + } ${optionalString (cfg.webHome != null) '' r ${cfg.webHome}/**,