vuls: init at 0.27.0 (#348530)
[NixPkgs.git] / nixos / modules / services / web-apps / peertube.nix
blobe3f15f4f438cc3e987797af1be149adaa0e6f6f7
1 { lib, pkgs, config, options, ... }:
3 let
4   cfg = config.services.peertube;
5   opt = options.services.peertube;
7   settingsFormat = pkgs.formats.json {};
8   configFile = settingsFormat.generate "production.json" cfg.settings;
10   env = {
11     NODE_CONFIG_DIR = "/var/lib/peertube/config";
12     NODE_ENV = "production";
13     NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt";
14     NPM_CONFIG_CACHE = "/var/cache/peertube/.npm";
15     NPM_CONFIG_PREFIX = cfg.package;
16     HOME = cfg.package;
17   };
19   systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ];
21   cfgService = {
22     # Proc filesystem
23     ProcSubset = "pid";
24     ProtectProc = "invisible";
25     # Access write directories
26     UMask = "0027";
27     # Capabilities
28     CapabilityBoundingSet = "";
29     # Security
30     NoNewPrivileges = true;
31     # Sandboxing
32     ProtectSystem = "strict";
33     ProtectHome = true;
34     PrivateTmp = true;
35     PrivateDevices = true;
36     PrivateUsers = true;
37     ProtectClock = true;
38     ProtectHostname = true;
39     ProtectKernelLogs = true;
40     ProtectKernelModules = true;
41     ProtectKernelTunables = true;
42     ProtectControlGroups = true;
43     RestrictNamespaces = true;
44     LockPersonality = true;
45     RestrictRealtime = true;
46     RestrictSUIDSGID = true;
47     RemoveIPC = true;
48     PrivateMounts = true;
49     # System Call Filtering
50     SystemCallArchitectures = "native";
51   };
53   envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") (
54     (lib.concatLists (lib.mapAttrsToList (name: value:
55       lib.optional (value != null) ''${name}="${toString value}"''
56     ) env))));
58   peertubeEnv = pkgs.writeShellScriptBin "peertube-env" ''
59     set -a
60     source "${envFile}"
61     eval -- "\$@"
62   '';
64   nginxCommonHeaders = lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.forceSSL ''
65     add_header Strict-Transport-Security 'max-age=31536000';
66   '' + lib.optionalString (config.services.nginx.virtualHosts.${cfg.localDomain}.quic && config.services.nginx.virtualHosts.${cfg.localDomain}.http3) ''
67     add_header Alt-Svc 'h3=":$server_port"; ma=604800';
68   '';
70   nginxCommonHeadersExtra = ''
71     add_header Access-Control-Allow-Origin '*';
72     add_header Access-Control-Allow-Methods 'GET, OPTIONS';
73     add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
74   '';
76 in {
77   options.services.peertube = {
78     enable = lib.mkEnableOption "Peertube";
80     user = lib.mkOption {
81       type = lib.types.str;
82       default = "peertube";
83       description = "User account under which Peertube runs.";
84     };
86     group = lib.mkOption {
87       type = lib.types.str;
88       default = "peertube";
89       description = "Group under which Peertube runs.";
90     };
92     localDomain = lib.mkOption {
93       type = lib.types.str;
94       example = "peertube.example.com";
95       description = "The domain serving your PeerTube instance.";
96     };
98     listenHttp = lib.mkOption {
99       type = lib.types.port;
100       default = 9000;
101       description = "The port that the local PeerTube web server will listen on.";
102     };
104     listenWeb = lib.mkOption {
105       type = lib.types.port;
106       default = 9000;
107       description = "The public-facing port that PeerTube will be accessible at (likely 80 or 443 if running behind a reverse proxy). Clients will try to access PeerTube at this port.";
108     };
110     enableWebHttps = lib.mkOption {
111       type = lib.types.bool;
112       default = false;
113       description = "Whether clients will access your PeerTube instance with HTTPS. Does NOT configure the PeerTube webserver itself to listen for incoming HTTPS connections.";
114     };
116     dataDirs = lib.mkOption {
117       type = lib.types.listOf lib.types.path;
118       default = [ ];
119       example = [ "/opt/peertube/storage" "/var/cache/peertube" ];
120       description = "Allow access to custom data locations.";
121     };
123     serviceEnvironmentFile = lib.mkOption {
124       type = lib.types.nullOr lib.types.path;
125       default = null;
126       example = "/run/keys/peertube/password-init-root";
127       description = ''
128         Set environment variables for the service. Mainly useful for setting the initial root password.
129         For example write to file:
130         PT_INITIAL_ROOT_PASSWORD=changeme
131       '';
132     };
134     settings = lib.mkOption {
135       type = settingsFormat.type;
136       example = lib.literalExpression ''
137         {
138           listen = {
139             hostname = "0.0.0.0";
140           };
141           log = {
142             level = "debug";
143           };
144           storage = {
145             tmp = "/opt/data/peertube/storage/tmp/";
146             logs = "/opt/data/peertube/storage/logs/";
147             cache = "/opt/data/peertube/storage/cache/";
148           };
149         }
150       '';
151       description = "Configuration for peertube.";
152     };
154     configureNginx = lib.mkOption {
155       type = lib.types.bool;
156       default = false;
157       description = "Configure nginx as a reverse proxy for peertube.";
158     };
160     secrets = {
161       secretsFile = lib.mkOption {
162         type = lib.types.nullOr lib.types.path;
163         default = null;
164         example = "/run/secrets/peertube";
165         description = ''
166           Secrets to run PeerTube.
167           Generate one using `openssl rand -hex 32`
168         '';
169       };
170     };
172     database = {
173       createLocally = lib.mkOption {
174         type = lib.types.bool;
175         default = false;
176         description = "Configure local PostgreSQL database server for PeerTube.";
177       };
179       host = lib.mkOption {
180         type = lib.types.str;
181         default = if cfg.database.createLocally then "/run/postgresql" else null;
182         defaultText = lib.literalExpression ''
183           if config.${opt.database.createLocally}
184           then "/run/postgresql"
185           else null
186         '';
187         example = "192.168.15.47";
188         description = "Database host address or unix socket.";
189       };
191       port = lib.mkOption {
192         type = lib.types.port;
193         default = 5432;
194         description = "Database host port.";
195       };
197       name = lib.mkOption {
198         type = lib.types.str;
199         default = "peertube";
200         description = "Database name.";
201       };
203       user = lib.mkOption {
204         type = lib.types.str;
205         default = "peertube";
206         description = "Database user.";
207       };
209       passwordFile = lib.mkOption {
210         type = lib.types.nullOr lib.types.path;
211         default = null;
212         example = "/run/keys/peertube/password-postgresql";
213         description = "Password for PostgreSQL database.";
214       };
215     };
217     redis = {
218       createLocally = lib.mkOption {
219         type = lib.types.bool;
220         default = false;
221         description = "Configure local Redis server for PeerTube.";
222       };
224       host = lib.mkOption {
225         type = lib.types.nullOr lib.types.str;
226         default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
227         defaultText = lib.literalExpression ''
228           if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket}
229           then "127.0.0.1"
230           else null
231         '';
232         description = "Redis host.";
233       };
235       port = lib.mkOption {
236         type = lib.types.nullOr lib.types.port;
237         default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
238         defaultText = lib.literalExpression ''
239           if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
240           then null
241           else 6379
242         '';
243         description = "Redis port.";
244       };
246       passwordFile = lib.mkOption {
247         type = lib.types.nullOr lib.types.path;
248         default = null;
249         example = "/run/keys/peertube/password-redis-db";
250         description = "Password for redis database.";
251       };
253       enableUnixSocket = lib.mkOption {
254         type = lib.types.bool;
255         default = cfg.redis.createLocally;
256         defaultText = lib.literalExpression "config.${opt.redis.createLocally}";
257         description = "Use Unix socket.";
258       };
259     };
261     smtp = {
262       createLocally = lib.mkOption {
263         type = lib.types.bool;
264         default = false;
265         description = "Configure local Postfix SMTP server for PeerTube.";
266       };
268       passwordFile = lib.mkOption {
269         type = lib.types.nullOr lib.types.path;
270         default = null;
271         example = "/run/keys/peertube/password-smtp";
272         description = "Password for smtp server.";
273       };
274     };
276     package = lib.mkOption {
277       type = lib.types.package;
278       default = pkgs.peertube;
279       defaultText = lib.literalExpression "pkgs.peertube";
280       description = "PeerTube package to use.";
281     };
282   };
284   config = lib.mkIf cfg.enable {
285     assertions = [
286       { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile;
287           message = ''
288             <option>services.peertube.serviceEnvironmentFile</option> points to
289             a file in the Nix store. You should use a quoted absolute path to
290             prevent this.
291           '';
292       }
293       { assertion = cfg.secrets.secretsFile != null;
294           message = ''
295             <option>services.peertube.secrets.secretsFile</option> needs to be set.
296           '';
297       }
298       { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
299           message = ''
300             <option>services.peertube.redis.createLocally</option> and redis network connection (<option>services.peertube.redis.host</option> or <option>services.peertube.redis.port</option>) enabled. Disable either of them.
301         '';
302       }
303       { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null);
304           message = ''
305             <option>services.peertube.redis.host</option> and <option>services.peertube.redis.port</option> needs to be set if <option>services.peertube.redis.enableUnixSocket</option> is not enabled.
306         '';
307       }
308       { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile;
309           message = ''
310             <option>services.peertube.redis.passwordFile</option> points to
311             a file in the Nix store. You should use a quoted absolute path to
312             prevent this.
313           '';
314       }
315       { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile;
316           message = ''
317             <option>services.peertube.database.passwordFile</option> points to
318             a file in the Nix store. You should use a quoted absolute path to
319             prevent this.
320           '';
321       }
322       { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile;
323           message = ''
324             <option>services.peertube.smtp.passwordFile</option> points to
325             a file in the Nix store. You should use a quoted absolute path to
326             prevent this.
327           '';
328       }
329     ];
331     environment.systemPackages = [ cfg.package.cli ];
333     services.peertube.settings = lib.mkMerge [
334       {
335         listen = {
336           port = cfg.listenHttp;
337         };
338         webserver = {
339           https = (if cfg.enableWebHttps then true else false);
340           hostname = "${cfg.localDomain}";
341           port = cfg.listenWeb;
342         };
343         database = {
344           hostname = "${cfg.database.host}";
345           port = cfg.database.port;
346           name = "${cfg.database.name}";
347           username = "${cfg.database.user}";
348         };
349         redis = {
350           hostname = "${toString cfg.redis.host}";
351           port = (lib.optionalString (cfg.redis.port != null) cfg.redis.port);
352         };
353         storage = {
354           tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
355           tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/";
356           bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
357           avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
358           web_videos = lib.mkDefault "/var/lib/peertube/storage/web-videos/";
359           streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
360           redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
361           logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
362           previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
363           thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
364           storyboards = lib.mkDefault "/var/lib/peertube/storage/storyboards/";
365           torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/";
366           captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
367           cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
368           plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
369           well_known = lib.mkDefault "/var/lib/peertube/storage/well_known/";
370           client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
371         };
372         import = {
373           videos = {
374             http = {
375               youtube_dl_release = {
376                 python_path = "${pkgs.python3}/bin/python";
377               };
378             };
379           };
380         };
381       }
382       (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; })
383     ];
385     systemd.tmpfiles.rules = [
386       "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
387       "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
388       "d '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
389       "z '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
390     ];
392     systemd.services.peertube-init-db = lib.mkIf cfg.database.createLocally {
393       description = "Initialization database for PeerTube daemon";
394       after = [ "network.target" "postgresql.service" ];
395       requires = [ "postgresql.service" ];
397       script = let
398         psqlSetupCommands = pkgs.writeText "peertube-init.sql" ''
399           SELECT 'CREATE USER "${cfg.database.user}"' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${cfg.database.user}')\gexec
400           SELECT 'CREATE DATABASE "${cfg.database.name}" OWNER "${cfg.database.user}" TEMPLATE template0 ENCODING UTF8' WHERE NOT EXISTS (SELECT FROM pg_database WHERE datname = '${cfg.database.name}')\gexec
401           \c '${cfg.database.name}'
402           CREATE EXTENSION IF NOT EXISTS pg_trgm;
403           CREATE EXTENSION IF NOT EXISTS unaccent;
404         '';
405       in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}";
407       serviceConfig = {
408         Type = "oneshot";
409         WorkingDirectory = cfg.package;
410         # User and group
411         User = "postgres";
412         Group = "postgres";
413         # Sandboxing
414         RestrictAddressFamilies = [ "AF_UNIX" ];
415         MemoryDenyWriteExecute = true;
416         # System Call Filtering
417         SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
418       } // cfgService;
419     };
421     systemd.services.peertube = {
422       description = "PeerTube daemon";
423       after = [ "network.target" ]
424         ++ lib.optional cfg.redis.createLocally "redis-peertube.service"
425         ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
426       requires = lib.optional cfg.redis.createLocally "redis-peertube.service"
427         ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
428       wantedBy = [ "multi-user.target" ];
430       environment = env;
432       path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ];
434       script = ''
435         #!/bin/sh
436         umask 077
437         cat > /var/lib/peertube/config/local.yaml <<EOF
438         ${lib.optionalString (cfg.secrets.secretsFile != null) ''
439         secrets:
440           peertube: '$(cat ${cfg.secrets.secretsFile})'
441         ''}
442         ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
443         database:
444           password: '$(cat ${cfg.database.passwordFile})'
445         ''}
446         ${lib.optionalString (cfg.redis.passwordFile != null) ''
447         redis:
448           auth: '$(cat ${cfg.redis.passwordFile})'
449         ''}
450         ${lib.optionalString (cfg.smtp.passwordFile != null) ''
451         smtp:
452           password: '$(cat ${cfg.smtp.passwordFile})'
453         ''}
454         EOF
455         umask 027
456         ln -sf ${configFile} /var/lib/peertube/config/production.json
457         ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
458         ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client
459         ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides
460         node dist/server
461       '';
462       serviceConfig = {
463         Type = "simple";
464         Restart = "always";
465         RestartSec = 20;
466         TimeoutSec = 60;
467         WorkingDirectory = cfg.package;
468         SyslogIdentifier = "peertube";
469         # User and group
470         User = cfg.user;
471         Group = cfg.group;
472         # State directory and mode
473         StateDirectory = "peertube";
474         StateDirectoryMode = "0750";
475         # Cache directory and mode
476         CacheDirectory = "peertube";
477         CacheDirectoryMode = "0750";
478         # Access write directories
479         ReadWritePaths = cfg.dataDirs;
480         # Environment
481         EnvironmentFile = cfg.serviceEnvironmentFile;
482         # Sandboxing
483         RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
484         MemoryDenyWriteExecute = false;
485         # System Call Filtering
486         SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ];
487       } // cfgService;
488     };
490     services.nginx = lib.mkIf cfg.configureNginx {
491       enable = true;
492       upstreams."peertube".servers = {
493         "127.0.0.1:${toString cfg.listenHttp}".fail_timeout = "0";
494       };
495       virtualHosts."${cfg.localDomain}" = {
496         root = "/var/lib/peertube/www";
498         # Application
499         locations."/" = {
500           tryFiles = "/dev/null @api";
501           priority = 1110;
502         };
504         locations."~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$" = {
505           tryFiles = "/dev/null @api";
506           priority = 1120;
508           extraConfig = ''
509             client_max_body_size 0;
510             proxy_request_buffering off;
511           '' + nginxCommonHeaders;
512         };
514         locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
515           tryFiles = "/dev/null @api";
516           root = cfg.settings.storage.tmp;
517           priority = 1130;
519           extraConfig = ''
520             limit_except POST HEAD { deny all; }
522             client_max_body_size 12G;
523             add_header X-File-Maximum-Size 8G always;
524           '' + nginxCommonHeaders;
525         };
527         locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
528           tryFiles = "/dev/null @api";
529           root = cfg.settings.storage.tmp;
530           priority = 1135;
532           extraConfig = ''
533             client_max_body_size 12G;
534             add_header X-File-Maximum-Size 8G always;
535           '' + nginxCommonHeaders;
536         };
538         locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
539           tryFiles = "/dev/null @api";
540           priority = 1140;
542           extraConfig = ''
543             client_max_body_size 6M;
544             add_header X-File-Maximum-Size 4M always;
545           '' + nginxCommonHeaders;
546         };
548         locations."@api" = {
549           proxyPass = "http://peertube";
550           priority = 1150;
552           extraConfig = ''
553             proxy_set_header Host $host;
554             proxy_set_header X-Real-IP $remote_addr;
555             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
557             proxy_connect_timeout 10m;
559             proxy_send_timeout 10m;
560             proxy_read_timeout 10m;
562             client_max_body_size 100k;
563             send_timeout 10m;
564           ''+ nginxCommonHeaders;
565         };
567         # Websocket
568         locations."/socket.io" = {
569           tryFiles = "/dev/null @api_websocket";
570           priority = 1210;
571         };
573         locations."/tracker/socket" = {
574           tryFiles = "/dev/null @api_websocket";
575           priority = 1220;
577           extraConfig = ''
578             proxy_read_timeout 15m;
579           '';
580         };
582         locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
583           tryFiles = "/dev/null @api_websocket";
584           priority = 1230;
585         };
587         locations."@api_websocket" = {
588           proxyPass = "http://peertube";
589           priority = 1240;
591           extraConfig = ''
592             proxy_http_version 1.1;
593             proxy_set_header Upgrade $http_upgrade;
594             proxy_set_header Connection 'upgrade';
595             proxy_set_header Host $host;
596             proxy_set_header X-Real-IP $remote_addr;
597             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
599           '' + nginxCommonHeaders;
600         };
602         # Bypass PeerTube for performance reasons.
603         locations."~ ^/client/(assets/images/(icons/icon-36x36\.png|icons/icon-48x48\.png|icons/icon-72x72\.png|icons/icon-96x96\.png|icons/icon-144x144\.png|icons/icon-192x192\.png|icons/icon-512x512\.png|logo\.svg|favicon\.png|default-playlist\.jpg|default-avatar-account\.png|default-avatar-account-48x48\.png|default-avatar-video-channel\.png|default-avatar-video-channel-48x48\.png))$" = {
604           tryFiles = "/client-overrides/$1 /client/$1 $1";
605           priority = 1310;
607           extraConfig = nginxCommonHeaders;
608         };
610         locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
611           alias = "${cfg.package}/client/dist/$1";
612           priority = 1320;
613           extraConfig = ''
614             add_header Cache-Control 'public, max-age=604800, immutable';
615           '' + nginxCommonHeaders;
616         };
618         locations."^~ /download/" = {
619           proxyPass = "http://peertube";
620           priority = 1410;
621           extraConfig = ''
622             proxy_set_header Host $host;
623             proxy_set_header X-Real-IP $remote_addr;
624             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
626             proxy_limit_rate 5M;
627           '' + nginxCommonHeaders;
628         };
630         locations."^~ /static/streaming-playlists/hls/private/" = {
631           proxyPass = "http://peertube";
632           priority = 1420;
633           extraConfig = ''
634             proxy_set_header Host $host;
635             proxy_set_header X-Real-IP $remote_addr;
636             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
638             proxy_limit_rate 5M;
639           '' + nginxCommonHeaders;
640         };
642         locations."^~ /static/web-videos/private/" = {
643           proxyPass = "http://peertube";
644           priority = 1430;
645           extraConfig = ''
646             proxy_set_header Host $host;
647             proxy_set_header X-Real-IP $remote_addr;
648             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
650             proxy_limit_rate 5M;
651           '' + nginxCommonHeaders;
652         };
654         locations."^~ /static/webseed/private/" = {
655           proxyPass = "http://peertube";
656           priority = 1440;
657           extraConfig = ''
658             proxy_set_header Host $host;
659             proxy_set_header X-Real-IP $remote_addr;
660             proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
662             proxy_limit_rate 5M;
663           '' + nginxCommonHeaders;
664         };
666         locations."^~ /static/redundancy/" = {
667           tryFiles = "$uri @api";
668           root = cfg.settings.storage.redundancy;
669           priority = 1450;
670           extraConfig = ''
671             set $peertube_limit_rate 800k;
673             if ($request_uri ~ -fragmented.mp4$) {
674               set $peertube_limit_rate 5M;
675             }
677             if ($request_method = 'OPTIONS') {
678               ${nginxCommonHeaders}
679               ${nginxCommonHeadersExtra}
680               add_header Access-Control-Max-Age 1728000;
681               add_header Content-Type 'text/plain charset=UTF-8';
682               add_header Content-Length 0;
683               return 204;
684             }
685             if ($request_method = 'GET') {
686               ${nginxCommonHeaders}
687               ${nginxCommonHeadersExtra}
689               access_log off;
690             }
692             aio threads;
693             sendfile on;
694             sendfile_max_chunk 1M;
696             limit_rate $peertube_limit_rate;
697             limit_rate_after 5M;
699             rewrite ^/static/redundancy/(.*)$ /$1 break;
700           '';
701         };
703         locations."^~ /static/streaming-playlists/" = {
704           tryFiles = "$uri @api";
705           root = cfg.settings.storage.streaming_playlists;
706           priority = 1460;
707           extraConfig = ''
708             set $peertube_limit_rate 800k;
710             if ($request_uri ~ -fragmented.mp4$) {
711               set $peertube_limit_rate 5M;
712             }
714             if ($request_method = 'OPTIONS') {
715               ${nginxCommonHeaders}
716               ${nginxCommonHeadersExtra}
717               add_header Access-Control-Max-Age 1728000;
718               add_header Content-Type 'text/plain charset=UTF-8';
719               add_header Content-Length 0;
720               return 204;
721             }
722             if ($request_method = 'GET') {
723               ${nginxCommonHeaders}
724               ${nginxCommonHeadersExtra}
726               access_log off;
727             }
729             aio threads;
730             sendfile on;
731             sendfile_max_chunk 1M;
733             limit_rate $peertube_limit_rate;
734             limit_rate_after 5M;
736             rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
737           '';
738         };
740         locations."^~ /static/web-videos/" = {
741           tryFiles = "$uri @api";
742           root = cfg.settings.storage.web_videos;
743           priority = 1470;
744           extraConfig = ''
745             set $peertube_limit_rate 800k;
747             if ($request_uri ~ -fragmented.mp4$) {
748               set $peertube_limit_rate 5M;
749             }
751             if ($request_method = 'OPTIONS') {
752               ${nginxCommonHeaders}
753               ${nginxCommonHeadersExtra}
754               add_header Access-Control-Max-Age 1728000;
755               add_header Content-Type 'text/plain charset=UTF-8';
756               add_header Content-Length 0;
757               return 204;
758             }
759             if ($request_method = 'GET') {
760               ${nginxCommonHeaders}
761               ${nginxCommonHeadersExtra}
763               access_log off;
764             }
766             aio threads;
767             sendfile on;
768             sendfile_max_chunk 1M;
770             limit_rate $peertube_limit_rate;
771             limit_rate_after 5M;
773             rewrite ^/static/web-videos/(.*)$ /$1 break;
774           '';
775         };
777         locations."^~ /static/webseed/" = {
778           tryFiles = "$uri @api";
779           root = cfg.settings.storage.web_videos;
780           priority = 1480;
781           extraConfig = ''
782             set $peertube_limit_rate 800k;
784             if ($request_uri ~ -fragmented.mp4$) {
785               set $peertube_limit_rate 5M;
786             }
788             if ($request_method = 'OPTIONS') {
789               ${nginxCommonHeaders}
790               ${nginxCommonHeadersExtra}
791               add_header Access-Control-Max-Age 1728000;
792               add_header Content-Type 'text/plain charset=UTF-8';
793               add_header Content-Length 0;
794               return 204;
795             }
796             if ($request_method = 'GET') {
797               ${nginxCommonHeaders}
798               ${nginxCommonHeadersExtra}
800               access_log off;
801             }
803             aio threads;
804             sendfile on;
805             sendfile_max_chunk 1M;
807             limit_rate $peertube_limit_rate;
808             limit_rate_after 5M;
810             rewrite ^/static/webseed/(.*)$ /web-videos/$1 break;
811           '';
812         };
813       };
814     };
816     services.postgresql = lib.mkIf cfg.database.createLocally {
817       enable = true;
818     };
820     services.redis.servers.peertube = lib.mkMerge [
821       (lib.mkIf cfg.redis.createLocally {
822         enable = true;
823       })
824       (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
825         bind = "127.0.0.1";
826         port = cfg.redis.port;
827       })
828       (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
829         unixSocket = "/run/redis-peertube/redis.sock";
830         unixSocketPerm = 660;
831       })
832     ];
834     services.postfix = lib.mkIf cfg.smtp.createLocally {
835       enable = true;
836       hostname = lib.mkDefault "${cfg.localDomain}";
837     };
839     users.users = lib.mkMerge [
840       (lib.mkIf (cfg.user == "peertube") {
841         peertube = {
842           isSystemUser = true;
843           group = cfg.group;
844           home = cfg.package;
845         };
846       })
847       (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ peertubeEnv pkgs.nodejs_18 pkgs.yarn pkgs.ffmpeg-headless ])
848       (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
849     ];
851     users.groups = {
852       ${cfg.group} = {
853         members = lib.optional cfg.configureNginx config.services.nginx.user;
854       };
855     };
856   };