Merge #361424: refactor lib.packagesFromDirectoryRecursive (v2)
[NixPkgs.git] / nixos / modules / services / networking / pyload.nix
blob144f97148ef9cd77fc0ec9c40a58fece59bf2a32
2   config,
3   lib,
4   pkgs,
5   utils,
6   ...
7 }:
8 let
9   cfg = config.services.pyload;
11   stateDir = "/var/lib/pyload";
14   meta.maintainers = with lib.maintainers; [ ambroisie ];
16   options = with lib; {
17     services.pyload = {
18       enable = mkEnableOption "pyLoad download manager";
20       package = mkPackageOption pkgs "pyLoad" { default = [ "pyload-ng" ]; };
22       listenAddress = mkOption {
23         type = types.str;
24         default = "localhost";
25         example = "0.0.0.0";
26         description = "Address to listen on for the web UI.";
27       };
29       port = mkOption {
30         type = types.port;
31         default = 8000;
32         example = 9876;
33         description = "Port to listen on for the web UI.";
34       };
36       downloadDirectory = mkOption {
37         type = types.path;
38         default = "${stateDir}/downloads";
39         example = "/mnt/downloads";
40         description = "Directory to store downloads.";
41       };
43       user = mkOption {
44         type = types.str;
45         default = "pyload";
46         description = "User under which pyLoad runs, and which owns the download directory.";
47       };
49       group = mkOption {
50         type = types.str;
51         default = "pyload";
52         description = "Group under which pyLoad runs, and which owns the download directory.";
53       };
55       credentialsFile = mkOption {
56         type = with types; nullOr path;
57         default = null;
58         example = "/run/secrets/pyload-credentials.env";
59         description = ''
60           File containing {env}`PYLOAD_DEFAULT_USERNAME` and
61           {env}`PYLOAD_DEFAULT_PASSWORD` in the format of an `EnvironmentFile=`,
62           as described by {manpage}`systemd.exec(5)`.
64           If not given, they default to the username/password combo of
65           pyload/pyload.
66         '';
67       };
68     };
69   };
71   config = lib.mkIf cfg.enable {
72     systemd.tmpfiles.settings.pyload = {
73       ${cfg.downloadDirectory}.d = { inherit (cfg) user group; };
74     };
76     systemd.services.pyload = {
77       description = "pyLoad download manager";
78       wantedBy = [ "multi-user.target" ];
79       after = [ "network.target" ];
81       # NOTE: unlike what the documentation says, it looks like `HOME` is not
82       # defined with this service definition...
83       # Since pyload tries to do the equivalent of `cd ~`, it needs to be able
84       # to resolve $HOME, which fails when `RootDirectory` is set.
85       # FIXME: check if `SetLoginEnvironment` fixes this issue in version 255
86       environment = {
87         HOME = stateDir;
88         PYLOAD__WEBUI__HOST = cfg.listenAddress;
89         PYLOAD__WEBUI__PORT = builtins.toString cfg.port;
90       };
92       serviceConfig = {
93         ExecStart = utils.escapeSystemdExecArgs [
94           (lib.getExe cfg.package)
95           "--userdir"
96           "${stateDir}/config"
97           "--storagedir"
98           cfg.downloadDirectory
99         ];
101         User = cfg.user;
102         Group = cfg.group;
104         EnvironmentFile = lib.optional (cfg.credentialsFile != null) cfg.credentialsFile;
106         StateDirectory = "pyload";
107         WorkingDirectory = stateDir;
108         RuntimeDirectory = "pyload";
109         RuntimeDirectoryMode = "0700";
110         RootDirectory = "/run/pyload";
111         BindReadOnlyPaths = [
112           builtins.storeDir # Needed to run the python interpreter
113         ];
114         BindPaths = [
115           cfg.downloadDirectory
116         ];
118         # Hardening options
119         LockPersonality = true;
120         NoNewPrivileges = true;
121         PrivateDevices = true;
122         PrivateMounts = true;
123         PrivateTmp = true;
124         PrivateUsers = true;
125         ProcSubset = "pid";
126         ProtectClock = true;
127         ProtectControlGroups = true;
128         ProtectHome = true;
129         ProtectHostname = true;
130         ProtectKernelLogs = true;
131         ProtectKernelModules = true;
132         ProtectKernelTunables = true;
133         ProtectProc = "invisible";
134         ProtectSystem = "strict";
135         RemoveIPC = true;
136         RestrictAddressFamilies = "AF_INET AF_INET6 AF_UNIX";
137         RestrictNamespaces = true;
138         RestrictRealtime = true;
139         RestrictSUIDSGID = true;
140         SystemCallArchitectures = "native";
141         SystemCallFilter = [
142           "@system-service"
143           "~@resources"
144           "~@privileged"
145         ];
146         UMask = "0002";
147         CapabilityBoundingSet = [
148           "~CAP_BLOCK_SUSPEND"
149           "~CAP_BPF"
150           "~CAP_CHOWN"
151           "~CAP_IPC_LOCK"
152           "~CAP_KILL"
153           "~CAP_LEASE"
154           "~CAP_LINUX_IMMUTABLE"
155           "~CAP_NET_ADMIN"
156           "~CAP_SYS_ADMIN"
157           "~CAP_SYS_BOOT"
158           "~CAP_SYS_CHROOT"
159           "~CAP_SYS_NICE"
160           "~CAP_SYS_PACCT"
161           "~CAP_SYS_PTRACE"
162           "~CAP_SYS_RESOURCE"
163           "~CAP_SYS_TTY_CONFIG"
164         ];
165       };
166     };
168     users.users.pyload = lib.mkIf (cfg.user == "pyload") {
169       isSystemUser = true;
170       group = cfg.group;
171       home = stateDir;
172     };
174     users.groups.pyload = lib.mkIf (cfg.group == "pyload") { };
175   };