python312Packages.dissect-extfs: 3.11 -> 3.12
[NixPkgs.git] / nixos / modules / services / web-servers / send.nix
blob696fbbdc7c80177b67a15a50fbdcf2c855c774c2
2   config,
3   lib,
4   pkgs,
5   ...
6 }:
7 let
8   inherit (lib) mkOption types;
9   cfg = config.services.send;
12   options = {
13     services.send = {
14       enable = lib.mkEnableOption "Send, a file sharing web sevice for ffsend.";
16       package = lib.mkPackageOption pkgs "send" { };
18       environment = mkOption {
19         type =
20           with types;
21           attrsOf (
22             nullOr (oneOf [
23               bool
24               int
25               str
26               (listOf int)
27             ])
28           );
29         description = ''
30           All the available config options and their defaults can be found here: https://github.com/timvisee/send/blob/master/server/config.js,
31           some descriptions can found here: https://github.com/timvisee/send/blob/master/docs/docker.md#environment-variables
33           Values under {option}`services.send.environment` will override the predefined values in the Send service.
34             - Time/duration should be in seconds
35             - Filesize values should be in bytes
36         '';
37         example = {
38           DEFAULT_DOWNLOADS = 1;
39           DETECT_BASE_URL = true;
40           EXPIRE_TIMES_SECONDS = [
41             300
42             3600
43             86400
44             604800
45           ];
46         };
47       };
49       dataDir = lib.mkOption {
50         type = types.path;
51         readOnly = true;
52         default = "/var/lib/send";
53         description = ''
54           Directory for uploaded files.
55           Due to limitations in {option}`systemd.services.send.serviceConfig.DynamicUser`, this item is read only.
56         '';
57       };
59       baseUrl = mkOption {
60         type = types.nullOr types.str;
61         default = null;
62         description = ''
63           Base URL for the Send service.
64           Leave it blank to automatically detect the base url.
65         '';
66       };
68       host = lib.mkOption {
69         type = types.str;
70         default = "127.0.0.1";
71         description = "The hostname or IP address for Send to bind to.";
72       };
74       port = lib.mkOption {
75         type = types.port;
76         default = 1443;
77         description = "Port the Send service listens on.";
78       };
80       openFirewall = lib.mkOption {
81         type = types.bool;
82         default = false;
83         description = "Whether to open firewall ports for send";
84       };
86       redis = {
87         createLocally = lib.mkOption {
88           type = types.bool;
89           default = true;
90           description = "Whether to create a local redis automatically.";
91         };
93         name = lib.mkOption {
94           type = types.str;
95           default = "send";
96           description = ''
97             Name of the redis server.
98             Only used if {option}`services.send.redis.createLocally` is set to true.
99           '';
100         };
102         host = lib.mkOption {
103           type = types.str;
104           default = "localhost";
105           description = "Redis server address.";
106         };
108         port = lib.mkOption {
109           type = types.port;
110           default = 6379;
111           description = "Port of the redis server.";
112         };
114         passwordFile = mkOption {
115           type = types.nullOr types.path;
116           default = null;
117           example = "/run/agenix/send-redis-password";
118           description = ''
119             The path to the file containing the Redis password.
121             If {option}`services.send.redis.createLocally` is set to true,
122             the content of this file will be used as the password for the locally created Redis instance.
124             Leave it blank if no password is required.
125           '';
126         };
127       };
128     };
129   };
131   config = lib.mkIf cfg.enable {
133     services.send.environment.DETECT_BASE_URL = cfg.baseUrl == null;
135     assertions = [
136       {
137         assertion = cfg.redis.createLocally -> cfg.redis.host == "localhost";
138         message = "the redis host must be localhost if services.send.redis.createLocally is set to true";
139       }
140     ];
142     networking.firewall.allowedTCPPorts = lib.optional cfg.openFirewall cfg.port;
144     services.redis = lib.optionalAttrs cfg.redis.createLocally {
145       servers."${cfg.redis.name}" = {
146         enable = true;
147         bind = "localhost";
148         port = cfg.redis.port;
149       };
150     };
152     systemd.services.send = {
153       serviceConfig = {
154         Type = "simple";
155         Restart = "always";
156         StateDirectory = "send";
157         WorkingDirectory = cfg.dataDir;
158         ReadWritePaths = cfg.dataDir;
159         LoadCredential = lib.optionalString (
160           cfg.redis.passwordFile != null
161         ) "redis-password:${cfg.redis.passwordFile}";
163         # Hardening
164         RestrictAddressFamilies = [
165           "AF_UNIX"
166           "AF_INET"
167           "AF_INET6"
168         ];
169         AmbientCapabilities = lib.optionalString (cfg.port < 1024) "cap_net_bind_service";
170         DynamicUser = true;
171         CapabilityBoundingSet = "";
172         NoNewPrivileges = true;
173         RemoveIPC = true;
174         PrivateTmp = true;
175         ProcSubset = "pid";
176         ProtectClock = true;
177         ProtectControlGroups = true;
178         ProtectHome = true;
179         ProtectHostname = true;
180         ProtectKernelLogs = true;
181         ProtectKernelModules = true;
182         ProtectKernelTunables = true;
183         ProtectProc = "invisible";
184         ProtectSystem = "full";
185         RestrictNamespaces = true;
186         RestrictRealtime = true;
187         RestrictSUIDSGID = true;
188         SystemCallArchitectures = "native";
189         UMask = "0077";
190       };
191       environment =
192         {
193           IP_ADDRESS = cfg.host;
194           PORT = toString cfg.port;
195           BASE_URL = if (cfg.baseUrl == null) then "http://${cfg.host}:${toString cfg.port}" else cfg.baseUrl;
196           FILE_DIR = cfg.dataDir + "/uploads";
197           REDIS_HOST = cfg.redis.host;
198           REDIS_PORT = toString cfg.redis.port;
199         }
200         // (lib.mapAttrs (
201           name: value:
202           if lib.isList value then
203             "[" + lib.concatStringsSep ", " (map (x: toString x) value) + "]"
204           else if lib.isBool value then
205             lib.boolToString value
206           else
207             toString value
208         ) cfg.environment);
209       after =
210         [
211           "network.target"
212         ]
213         ++ lib.optionals cfg.redis.createLocally [
214           "redis-${cfg.redis.name}.service"
215         ];
216       description = "Send web service";
217       wantedBy = [ "multi-user.target" ];
218       script = ''
219         ${lib.optionalString (cfg.redis.passwordFile != null) ''
220           export REDIS_PASSWORD="$(cat $CREDENTIALS_DIRECTORY/redis-password)"
221         ''}
222         ${lib.getExe cfg.package}
223       '';
224     };
225   };
227   meta.maintainers = with lib.maintainers; [ moraxyc ];