{ config, lib, pkgs, ... }: let cfg = config.services.pyload; stateDir = "/var/lib/pyload"; userDir = "${stateDir}/config"; in { options = with lib; { services.pyload = { enable = mkEnableOption "pyload download manager"; package = mkPackageOption pkgs "pyload-ng" { }; listenAddress = mkOption { type = types.str; default = "localhost"; example = "0.0.0.0"; description = "Address to listen on for the web UI."; }; port = mkOption { type = types.port; default = 8000; example = 9876; description = "Port to listen on for the web UI."; }; downloadDirectory = mkOption { type = types.path; default = "${stateDir}/downloads"; example = "/mnt/downloads"; description = "Directory to store downloads"; }; credentialsFile = mkOption { type = with types; nullOr path; default = null; example = "/run/secrets/pyload-credentials.env"; description = '' File containing PYLOAD_DEFAULT_USERNAME and PYLOAD_DEFAULT_PASSWORD in the format of an EnvironmentFile=, as described by systemd.exec(5). If not given, they default to the username/password combo of pyload/pyload. ''; }; }; }; config = lib.mkIf cfg.enable { systemd.tmpfiles.rules = [ "d ${cfg.downloadDirectory}" ]; systemd.services.pyload = { description = "pyload service"; wantedBy = [ "multi-user.target" ]; after = [ "network.target" ]; # NOTE: unlike what the documentation says, it looks like `HOME` is not # defined with this service definition... # Since pyload tries to do the equivalent of `cd ~`, it needs to be able # to resolve $HOME, which fails when `RootDirectory` is set. # FIXME: check if `SetLoginEnvironment` fixes this issue in version 255 environment = { HOME = stateDir; PYLOAD__WEBUI__HOST = cfg.listenAddress; PYLOAD__WEBUI__PORT = builtins.toString cfg.port; }; serviceConfig = { # FIXME: use getExe ExecStart = "${lib.getExe' cfg.package "pyload"} ${lib.escapeShellArgs [ "--userdir" userDir "--storagedir" cfg.downloadDirectory ]}"; User = "pyload"; Group = "pyload"; DynamicUser = true; EnvironmentFile = lib.optional (cfg.credentialsFile != null) cfg.credentialsFile; StateDirectory = "pyload"; WorkingDirectory = stateDir; RuntimeDirectory = "pyload"; RuntimeDirectoryMode = "0700"; RootDirectory = "/run/pyload"; BindReadOnlyPaths = [ builtins.storeDir # Needed to run the python interpreter ]; BindPaths = [ cfg.downloadDirectory ]; # Hardening options LockPersonality = true; MemoryDenyWriteExecute = true; NoNewPrivileges = true; PrivateDevices = true; PrivateMounts = true; PrivateTmp = true; PrivateUsers = true; ProcSubset = "pid"; ProtectClock = true; ProtectControlGroups = true; ProtectHome = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectProc = "invisible"; ProtectSystem = "strict"; RemoveIPC = true; RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX"; RestrictNamespaces = true; RestrictRealtime = true; RestrictSUIDSGID = true; SystemCallArchitectures = "native"; SystemCallFilter = [ "@system-service" "~@resources" "~@privileged" ]; UMask = "0002"; CapabilityBoundingSet = [ "~CAP_BLOCK_SUSPEND" "~CAP_BPF" "~CAP_CHOWN" "~CAP_IPC_LOCK" "~CAP_KILL" "~CAP_LEASE" "~CAP_LINUX_IMMUTABLE" "~CAP_NET_ADMIN" "~CAP_SYS_ADMIN" "~CAP_SYS_BOOT" "~CAP_SYS_CHROOT" "~CAP_SYS_NICE" "~CAP_SYS_PACCT" "~CAP_SYS_PTRACE" "~CAP_SYS_RESOURCE" "~CAP_SYS_TTY_CONFIG" ]; }; }; }; }