transmission: improve the service module
authorJulien Moutinho <julm@sourcephile.fr>
Mon, 29 Jun 2020 01:56:48 +0000 (03:56 +0200)
committerJulien Moutinho <julm@sourcephile.fr>
Mon, 29 Jun 2020 01:59:06 +0000 (03:59 +0200)
nixos/modules.nix
nixos/modules/services/torrent/transmission.nix [new file with mode: 0644]
servers/losurdo/fileSystems.nix
servers/losurdo/transmission.nix

index 93dcd456019d837b9e15a16cfc2d3657b5644551..17e7a6f7a5fd897bad37a2d2045c1035743d8453 100644 (file)
@@ -10,10 +10,12 @@ imports = [
   modules/services/databases/openldap.nix
   modules/services/mail/public-inbox.nix
   #modules/services/mail/mlmmj.nix
+  modules/services/torrent/transmission.nix
 ];
 disabledModules = [
   "services/mail/public-inbox.nix"
   "services/mail/mlmmj.nix"
+  "services/torrent/transmission.nix"
 ];
 }
 
diff --git a/nixos/modules/services/torrent/transmission.nix b/nixos/modules/services/torrent/transmission.nix
new file mode 100644 (file)
index 0000000..a916380
--- /dev/null
@@ -0,0 +1,202 @@
+{ 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,
+          ''}
+        }
+      '')
+    ];
+  };
+
+}
index b1df24bda2e96783b1b9492fd0be2269e9bff9e4..b14b974654addca8f1649d90970d4d4813aa181c 100644 (file)
@@ -68,7 +68,7 @@ fileSystems."/var/tmp" =
     fsType = "zfs";
   };
 
-fileSystems."/var/torrents" =
+fileSystems."/var/lib/transmission" =
   { device = "losurdo_nvme/var/torrents";
     fsType = "zfs";
   };
index d61b469150418da2c1eff559d30e82d45afb3e0c..76a69e59a88f49d1bc6788db755d37710677a817 100644 (file)
@@ -14,10 +14,9 @@ networking.nftables.ruleset = ''
 '';
 services.transmission = {
   enable = true;
-  home = "/var/torrents";
-  downloadDirPermissions = "770";
   settings = {
     dht-enabled = true;
+    download-dir = "Downloads";
     incomplete-dir-enabled = false;
     peer-port = 6882;
     peer-port-random-on-start = false;
@@ -30,6 +29,7 @@ services.transmission = {
     rpc-whitelist-enabled = true;
     speed-limit-up = 10;
     speed-limit-up-enabled = true;
+    umask = 63;
   };
 };
 }