--- /dev/null
+{ config, lib, pkgs, ... }:
+
+with lib;
+
+let
+ cfg = config.services.transmission;
+ apparmor = config.security.apparmor.enable;
+ # TODO: switch to configGen.json once RFC42 is implemented
+ settingsFile = pkgs.writeText "settings.json" (builtins.toJSON cfg.settings);
+ stateDir = "/var/lib/transmission";
+ settingsDir = ".config/transmission-daemon";
+in
+{
+ imports = [
+ (mkRemovedOptionModule [ "services" "transmission" "port" ]
+ "Instead, use the option `services.transmission.settings.rpc-port'.")
+ (mkRemovedOptionModule [ "services" "transmission" "home" ]
+ "Instead, use systemd's StateDirectory: `${stateDir}'.")
+ (mkRemovedOptionModule [ "services" "transmission" "downloadDirPermissions" ] ''
+ Instead, use the option `services.transmission.settings.umask'
+ (which currently is: ${toString cfg.settings.umask}) (in decimal, as expected by Transmission)
+ and `systemd.services.transmission.serviceConfig.StateDirectoryMode'
+ (which currently is: ${config.systemd.services.transmission.serviceConfig.StateDirectoryMode}).
+ '')
+ ];
+ options = {
+ services.transmission = {
+ enable = mkEnableOption ''
+ Whether or not to enable 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),
+ or other clients like stig or tremc.
+
+ Torrents are downloaded to ${stateDir}/${cfg.settings.download-dir} by default and are
+ accessible to users in the "transmission" group.
+ '';
+
+ settings = mkOption rec {
+ # TODO: switch to types.config.json as prescribed by RFC42 once it's implemented
+ type = types.attrs;
+ apply = recursiveUpdate default;
+ default =
+ {
+ download-dir = "Downloads";
+ incomplete-dir = ".incomplete";
+ incomplete-dir-enabled = true;
+ rpc-port = 9091;
+ umask = 63; # echo $((8#077))
+ };
+ example =
+ {
+ download-dir = "Torrents";
+ incomplete-dir = ".Torrents";
+ incomplete-dir-enabled = true;
+ rpc-whitelist = "127.0.0.1,192.168.*.*";
+ };
+ description = ''
+ Attribute set whose fields overwrites fields in settings.json (each
+ time the service starts). String values must be quoted, integer and
+ boolean values must not.
+
+ See https://github.com/transmission/transmission/wiki/Editing-Configuration-Files
+ for documentation.
+ '';
+ };
+
+ user = mkOption {
+ type = types.str;
+ default = "transmission";
+ description = "User account under which Transmission runs.";
+ };
+
+ group = mkOption {
+ type = types.str;
+ default = "transmission";
+ description = "Group account under which Transmission runs.";
+ };
+ };
+ };
+
+ config = mkIf cfg.enable {
+ assertions = [
+ { assertion = builtins.match "^/.*" cfg.settings.download-dir == null;
+ message = "`services.transmission.settings.download-dir' " +
+ "can no longer be an absolute path, it must be relative to `${stateDir}'"; }
+ { assertion = builtins.match "^/.*" cfg.settings.incomplete-dir == null;
+ message = "`services.transmission.settings.incomplete-dir' " +
+ "can no longer be an absolute path, it must be relative to `${stateDir}'"; }
+ ];
+
+ systemd.services.transmission = {
+ description = "Transmission BitTorrent Service";
+ after = [ "network.target" ] ++ optional apparmor "apparmor.service";
+ requires = mkIf apparmor [ "apparmor.service" ];
+ wantedBy = [ "multi-user.target" ];
+ preStart = ''
+ chmod 755 '${stateDir}'
+ chmod 0700 '${stateDir}/${settingsDir}'
+ cp -f ${settingsFile} ${settingsDir}/settings.json
+ '';
+
+ serviceConfig = {
+ WorkingDirectory = stateDir;
+ StateDirectory = concatMapStringsSep " " (p: "transmission/${p}")
+ ([ settingsDir cfg.settings.download-dir ]
+ ++ optional cfg.settings.incomplete-dir-enabled cfg.settings.incomplete-dir);
+ StateDirectoryMode = mkDefault "770";
+ ExecStart = "${pkgs.transmission}/bin/transmission-daemon -f --config-dir ${settingsDir}";
+ ExecReload = "${pkgs.coreutils}/bin/kill -HUP $MAINPID";
+ User = cfg.user;
+ Group = cfg.group;
+ UMask = "0007";
+
+ # Hardening options
+ #DevicePolicy = "closed";
+ #LockPersonality = true;
+ #MemoryDenyWriteExecute = true;
+ #NoNewPrivileges = true;
+ #PrivateDevices = true;
+ #PrivateTmp = true;
+ #ProtectControlGroups = true;
+ #ProtectHome = true;
+ #ProtectKernelModules = true;
+ #ProtectKernelTunables = true;
+ #ProtectSystem = "strict";
+ #RestrictAddressFamilies = "AF_UNIX AF_INET AF_INET6 AF_NETLINK";
+ #RestrictNamespaces = true;
+ #RestrictRealtime = true;
+ #RestrictSUIDSGID = true;
+ };
+ };
+
+ # It's useful to have transmission in path, e.g. for remote control
+ environment.systemPackages = [ pkgs.transmission ];
+
+ users.users = optionalAttrs (cfg.user == "transmission") ({
+ transmission = {
+ group = cfg.group;
+ uid = config.ids.uids.transmission;
+ description = "Transmission BitTorrent user";
+ home = stateDir;
+ createHome = true;
+ };
+ });
+
+ users.groups = optionalAttrs (cfg.group == "transmission") ({
+ transmission = {
+ gid = config.ids.gids.transmission;
+ };
+ });
+
+ # AppArmor profile
+ security.apparmor.profiles = mkIf apparmor [
+ (pkgs.writeText "apparmor-transmission-daemon" ''
+ #include <tunables/global>
+
+ ${pkgs.transmission}/bin/transmission-daemon {
+ #include <abstractions/base>
+ #include <abstractions/nameservice>
+
+ ${getLib pkgs.gcc-unwrapped}/lib/*.so* mr,
+ ${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,
+
+ @{PROC}/sys/kernel/random/uuid r,
+ @{PROC}/sys/vm/overcommit_memory r,
+
+ ${pkgs.openssl.out}/etc/** r,
+ ${pkgs.transmission}/share/transmission/** r,
+
+ owner ${stateDir}/${settingsDir}/** rw,
+
+ ${stateDir}/${cfg.settings.download-dir}/** rw,
+ ${optionalString cfg.settings.incomplete-dir-enabled ''
+ ${stateDir}/${cfg.settings.incomplete-dir}/** rw,
+ ''}
+ }
+ '')
+ ];
+ };
+
+}