1{ lib, pkgs, config, options, ... }:
2
3let
4 cfg = config.services.peertube;
5 opt = options.services.peertube;
6
7 settingsFormat = pkgs.formats.json {};
8 configFile = settingsFormat.generate "production.json" cfg.settings;
9
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 };
18
19 systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ];
20
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 };
52
53 envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") (
54 (lib.concatLists (lib.mapAttrsToList (name: value:
55 if value != null then [
56 "${name}=\"${toString value}\""
57 ] else []
58 ) env))));
59
60 peertubeEnv = pkgs.writeShellScriptBin "peertube-env" ''
61 set -a
62 source "${envFile}"
63 eval -- "\$@"
64 '';
65
66 peertubeCli = pkgs.writeShellScriptBin "peertube" ''
67 node ~/dist/server/tools/peertube.js $@
68 '';
69
70 nginxCommonHeaders = lib.optionalString cfg.enableWebHttps ''
71 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
72 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
73 add_header Alt-Svc 'h3=":443"; ma=86400';
74 '' + ''
75 add_header Access-Control-Allow-Origin '*';
76 add_header Access-Control-Allow-Methods 'GET, OPTIONS';
77 add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
78 '';
79
80in {
81 options.services.peertube = {
82 enable = lib.mkEnableOption (lib.mdDoc "Peertube");
83
84 user = lib.mkOption {
85 type = lib.types.str;
86 default = "peertube";
87 description = lib.mdDoc "User account under which Peertube runs.";
88 };
89
90 group = lib.mkOption {
91 type = lib.types.str;
92 default = "peertube";
93 description = lib.mdDoc "Group under which Peertube runs.";
94 };
95
96 localDomain = lib.mkOption {
97 type = lib.types.str;
98 example = "peertube.example.com";
99 description = lib.mdDoc "The domain serving your PeerTube instance.";
100 };
101
102 listenHttp = lib.mkOption {
103 type = lib.types.port;
104 default = 9000;
105 description = lib.mdDoc "listen port for HTTP server.";
106 };
107
108 listenWeb = lib.mkOption {
109 type = lib.types.port;
110 default = 9000;
111 description = lib.mdDoc "listen port for WEB server.";
112 };
113
114 enableWebHttps = lib.mkOption {
115 type = lib.types.bool;
116 default = false;
117 description = lib.mdDoc "Enable or disable HTTPS protocol.";
118 };
119
120 dataDirs = lib.mkOption {
121 type = lib.types.listOf lib.types.path;
122 default = [ ];
123 example = [ "/opt/peertube/storage" "/var/cache/peertube" ];
124 description = lib.mdDoc "Allow access to custom data locations.";
125 };
126
127 serviceEnvironmentFile = lib.mkOption {
128 type = lib.types.nullOr lib.types.path;
129 default = null;
130 example = "/run/keys/peertube/password-init-root";
131 description = lib.mdDoc ''
132 Set environment variables for the service. Mainly useful for setting the initial root password.
133 For example write to file:
134 PT_INITIAL_ROOT_PASSWORD=changeme
135 '';
136 };
137
138 settings = lib.mkOption {
139 type = settingsFormat.type;
140 example = lib.literalExpression ''
141 {
142 listen = {
143 hostname = "0.0.0.0";
144 };
145 log = {
146 level = "debug";
147 };
148 storage = {
149 tmp = "/opt/data/peertube/storage/tmp/";
150 logs = "/opt/data/peertube/storage/logs/";
151 cache = "/opt/data/peertube/storage/cache/";
152 };
153 }
154 '';
155 description = lib.mdDoc "Configuration for peertube.";
156 };
157
158 configureNginx = lib.mkOption {
159 type = lib.types.bool;
160 default = false;
161 description = lib.mdDoc "Configure nginx as a reverse proxy for peertube.";
162 };
163
164 database = {
165 createLocally = lib.mkOption {
166 type = lib.types.bool;
167 default = false;
168 description = lib.mdDoc "Configure local PostgreSQL database server for PeerTube.";
169 };
170
171 host = lib.mkOption {
172 type = lib.types.str;
173 default = if cfg.database.createLocally then "/run/postgresql" else null;
174 defaultText = lib.literalExpression ''
175 if config.${opt.database.createLocally}
176 then "/run/postgresql"
177 else null
178 '';
179 example = "192.168.15.47";
180 description = lib.mdDoc "Database host address or unix socket.";
181 };
182
183 port = lib.mkOption {
184 type = lib.types.port;
185 default = 5432;
186 description = lib.mdDoc "Database host port.";
187 };
188
189 name = lib.mkOption {
190 type = lib.types.str;
191 default = "peertube";
192 description = lib.mdDoc "Database name.";
193 };
194
195 user = lib.mkOption {
196 type = lib.types.str;
197 default = "peertube";
198 description = lib.mdDoc "Database user.";
199 };
200
201 passwordFile = lib.mkOption {
202 type = lib.types.nullOr lib.types.path;
203 default = null;
204 example = "/run/keys/peertube/password-posgressql-db";
205 description = lib.mdDoc "Password for PostgreSQL database.";
206 };
207 };
208
209 redis = {
210 createLocally = lib.mkOption {
211 type = lib.types.bool;
212 default = false;
213 description = lib.mdDoc "Configure local Redis server for PeerTube.";
214 };
215
216 host = lib.mkOption {
217 type = lib.types.nullOr lib.types.str;
218 default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
219 defaultText = lib.literalExpression ''
220 if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket}
221 then "127.0.0.1"
222 else null
223 '';
224 description = lib.mdDoc "Redis host.";
225 };
226
227 port = lib.mkOption {
228 type = lib.types.nullOr lib.types.port;
229 default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
230 defaultText = lib.literalExpression ''
231 if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
232 then null
233 else 6379
234 '';
235 description = lib.mdDoc "Redis port.";
236 };
237
238 passwordFile = lib.mkOption {
239 type = lib.types.nullOr lib.types.path;
240 default = null;
241 example = "/run/keys/peertube/password-redis-db";
242 description = lib.mdDoc "Password for redis database.";
243 };
244
245 enableUnixSocket = lib.mkOption {
246 type = lib.types.bool;
247 default = cfg.redis.createLocally;
248 defaultText = lib.literalExpression "config.${opt.redis.createLocally}";
249 description = lib.mdDoc "Use Unix socket.";
250 };
251 };
252
253 smtp = {
254 createLocally = lib.mkOption {
255 type = lib.types.bool;
256 default = false;
257 description = lib.mdDoc "Configure local Postfix SMTP server for PeerTube.";
258 };
259
260 passwordFile = lib.mkOption {
261 type = lib.types.nullOr lib.types.path;
262 default = null;
263 example = "/run/keys/peertube/password-smtp";
264 description = lib.mdDoc "Password for smtp server.";
265 };
266 };
267
268 package = lib.mkOption {
269 type = lib.types.package;
270 default = pkgs.peertube;
271 defaultText = lib.literalExpression "pkgs.peertube";
272 description = lib.mdDoc "Peertube package to use.";
273 };
274 };
275
276 config = lib.mkIf cfg.enable {
277 assertions = [
278 { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile;
279 message = ''
280 <option>services.peertube.serviceEnvironmentFile</option> points to
281 a file in the Nix store. You should use a quoted absolute path to
282 prevent this.
283 '';
284 }
285 { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
286 message = ''
287 <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.
288 '';
289 }
290 { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null);
291 message = ''
292 <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.
293 '';
294 }
295 { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile;
296 message = ''
297 <option>services.peertube.redis.passwordFile</option> points to
298 a file in the Nix store. You should use a quoted absolute path to
299 prevent this.
300 '';
301 }
302 { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile;
303 message = ''
304 <option>services.peertube.database.passwordFile</option> points to
305 a file in the Nix store. You should use a quoted absolute path to
306 prevent this.
307 '';
308 }
309 { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile;
310 message = ''
311 <option>services.peertube.smtp.passwordFile</option> points to
312 a file in the Nix store. You should use a quoted absolute path to
313 prevent this.
314 '';
315 }
316 ];
317
318 services.peertube.settings = lib.mkMerge [
319 {
320 listen = {
321 port = cfg.listenHttp;
322 };
323 webserver = {
324 https = (if cfg.enableWebHttps then true else false);
325 hostname = "${cfg.localDomain}";
326 port = cfg.listenWeb;
327 };
328 database = {
329 hostname = "${cfg.database.host}";
330 port = cfg.database.port;
331 name = "${cfg.database.name}";
332 username = "${cfg.database.user}";
333 };
334 redis = {
335 hostname = "${toString cfg.redis.host}";
336 port = (if cfg.redis.port == null then "" else cfg.redis.port);
337 };
338 storage = {
339 tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
340 bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
341 avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
342 videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
343 streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
344 redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
345 logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
346 previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
347 thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
348 torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/";
349 captions = lib.mkDefault "/var/lib/peertube/storage/captions/";
350 cache = lib.mkDefault "/var/lib/peertube/storage/cache/";
351 plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/";
352 client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/";
353 };
354 import = {
355 videos = {
356 http = {
357 youtube_dl_release = {
358 python_path = "${pkgs.python3}/bin/python";
359 };
360 };
361 };
362 };
363 }
364 (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; })
365 ];
366
367 systemd.tmpfiles.rules = [
368 "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
369 "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -"
370 "d '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
371 "z '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -"
372 ];
373
374 systemd.services.peertube-init-db = lib.mkIf cfg.database.createLocally {
375 description = "Initialization database for PeerTube daemon";
376 after = [ "network.target" "postgresql.service" ];
377 requires = [ "postgresql.service" ];
378
379 script = let
380 psqlSetupCommands = pkgs.writeText "peertube-init.sql" ''
381 SELECT 'CREATE USER "${cfg.database.user}"' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${cfg.database.user}')\gexec
382 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
383 \c '${cfg.database.name}'
384 CREATE EXTENSION IF NOT EXISTS pg_trgm;
385 CREATE EXTENSION IF NOT EXISTS unaccent;
386 '';
387 in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}";
388
389 serviceConfig = {
390 Type = "oneshot";
391 WorkingDirectory = cfg.package;
392 # User and group
393 User = "postgres";
394 Group = "postgres";
395 # Sandboxing
396 RestrictAddressFamilies = [ "AF_UNIX" ];
397 MemoryDenyWriteExecute = true;
398 # System Call Filtering
399 SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
400 } // cfgService;
401 };
402
403 systemd.services.peertube = {
404 description = "PeerTube daemon";
405 after = [ "network.target" ]
406 ++ lib.optional cfg.redis.createLocally "redis-peertube.service"
407 ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
408 requires = lib.optional cfg.redis.createLocally "redis-peertube.service"
409 ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ];
410 wantedBy = [ "multi-user.target" ];
411
412 environment = env;
413
414 path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn python3 ];
415
416 script = ''
417 #!/bin/sh
418 umask 077
419 cat > /var/lib/peertube/config/local.yaml <<EOF
420 ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
421 database:
422 password: '$(cat ${cfg.database.passwordFile})'
423 ''}
424 ${lib.optionalString (cfg.redis.passwordFile != null) ''
425 redis:
426 auth: '$(cat ${cfg.redis.passwordFile})'
427 ''}
428 ${lib.optionalString (cfg.smtp.passwordFile != null) ''
429 smtp:
430 password: '$(cat ${cfg.smtp.passwordFile})'
431 ''}
432 EOF
433 umask 027
434 ln -sf ${configFile} /var/lib/peertube/config/production.json
435 ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
436 ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client
437 ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides
438 npm start
439 '';
440 serviceConfig = {
441 Type = "simple";
442 Restart = "always";
443 RestartSec = 20;
444 TimeoutSec = 60;
445 WorkingDirectory = cfg.package;
446 # User and group
447 User = cfg.user;
448 Group = cfg.group;
449 # State directory and mode
450 StateDirectory = "peertube";
451 StateDirectoryMode = "0750";
452 # Cache directory and mode
453 CacheDirectory = "peertube";
454 CacheDirectoryMode = "0750";
455 # Access write directories
456 ReadWritePaths = cfg.dataDirs;
457 # Environment
458 EnvironmentFile = cfg.serviceEnvironmentFile;
459 # Sandboxing
460 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
461 MemoryDenyWriteExecute = false;
462 # System Call Filtering
463 SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ];
464 } // cfgService;
465 };
466
467 services.nginx = lib.mkIf cfg.configureNginx {
468 enable = true;
469 virtualHosts."${cfg.localDomain}" = {
470 root = "/var/lib/peertube";
471
472 # Application
473 locations."/" = {
474 tryFiles = "/dev/null @api";
475 priority = 1110;
476 };
477
478 locations."= /api/v1/videos/upload-resumable" = {
479 tryFiles = "/dev/null @api";
480 priority = 1120;
481
482 extraConfig = ''
483 client_max_body_size 0;
484 proxy_request_buffering off;
485 '';
486 };
487
488 locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
489 tryFiles = "/dev/null @api";
490 root = cfg.settings.storage.tmp;
491 priority = 1130;
492
493 extraConfig = ''
494 client_max_body_size 12G;
495 add_header X-File-Maximum-Size 8G always;
496 '' + lib.optionalString cfg.enableWebHttps ''
497 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
498 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
499 add_header Alt-Svc 'h3=":443"; ma=86400';
500 '';
501 };
502
503 locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
504 tryFiles = "/dev/null @api";
505 priority = 1140;
506
507 extraConfig = ''
508 client_max_body_size 6M;
509 add_header X-File-Maximum-Size 4M always;
510 '' + lib.optionalString cfg.enableWebHttps ''
511 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
512 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
513 add_header Alt-Svc 'h3=":443"; ma=86400';
514 '';
515 };
516
517 locations."@api" = {
518 proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
519 priority = 1150;
520
521 extraConfig = ''
522 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
523 proxy_set_header Host $host;
524 proxy_set_header X-Real-IP $remote_addr;
525
526 proxy_connect_timeout 10m;
527
528 proxy_send_timeout 10m;
529 proxy_read_timeout 10m;
530
531 client_max_body_size 100k;
532 send_timeout 10m;
533 '';
534 };
535
536 # Websocket
537 locations."/socket.io" = {
538 tryFiles = "/dev/null @api_websocket";
539 priority = 1210;
540 };
541
542 locations."/tracker/socket" = {
543 tryFiles = "/dev/null @api_websocket";
544 priority = 1220;
545
546 extraConfig = ''
547 proxy_read_timeout 15m;
548 '';
549 };
550
551 locations."@api_websocket" = {
552 proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
553 priority = 1230;
554
555 extraConfig = ''
556 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
557 proxy_set_header Host $host;
558 proxy_set_header X-Real-IP $remote_addr;
559 proxy_set_header Upgrade $http_upgrade;
560 proxy_set_header Connection 'upgrade';
561
562 proxy_http_version 1.1;
563 '';
564 };
565
566 # Bypass PeerTube for performance reasons.
567 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))$" = {
568 tryFiles = "/www/client-overrides/$1 /www/client/$1 $1";
569 priority = 1310;
570 };
571
572 locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
573 alias = "${cfg.package}/client/dist/$1";
574 priority = 1320;
575 extraConfig = ''
576 add_header Cache-Control 'public, max-age=604800, immutable';
577 '' + lib.optionalString cfg.enableWebHttps ''
578 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
579 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
580 add_header Alt-Svc 'h3=":443"; ma=86400';
581 '';
582 };
583
584 locations."~ ^/lazy-static/(avatars|banners)/" = {
585 tryFiles = "$uri @api";
586 root = cfg.settings.storage.avatars;
587 priority = 1330;
588 extraConfig = ''
589 if ($request_method = 'OPTIONS') {
590 ${nginxCommonHeaders}
591 add_header Access-Control-Max-Age 1728000;
592 add_header Cache-Control 'no-cache';
593 add_header Content-Type 'text/plain charset=UTF-8';
594 add_header Content-Length 0;
595 return 204;
596 }
597
598 ${nginxCommonHeaders}
599 add_header Cache-Control 'public, max-age=7200';
600
601 rewrite ^/lazy-static/avatars/(.*)$ /$1 break;
602 rewrite ^/lazy-static/banners/(.*)$ /$1 break;
603 '';
604 };
605
606 locations."^~ /lazy-static/previews/" = {
607 tryFiles = "$uri @api";
608 root = cfg.settings.storage.previews;
609 priority = 1340;
610 extraConfig = ''
611 if ($request_method = 'OPTIONS') {
612 ${nginxCommonHeaders}
613 add_header Access-Control-Max-Age 1728000;
614 add_header Cache-Control 'no-cache';
615 add_header Content-Type 'text/plain charset=UTF-8';
616 add_header Content-Length 0;
617 return 204;
618 }
619
620 ${nginxCommonHeaders}
621 add_header Cache-Control 'public, max-age=7200';
622
623 rewrite ^/lazy-static/previews/(.*)$ /$1 break;
624 '';
625 };
626
627 locations."^~ /static/thumbnails/" = {
628 tryFiles = "$uri @api";
629 root = cfg.settings.storage.thumbnails;
630 priority = 1350;
631 extraConfig = ''
632 if ($request_method = 'OPTIONS') {
633 ${nginxCommonHeaders}
634 add_header Access-Control-Max-Age 1728000;
635 add_header Cache-Control 'no-cache';
636 add_header Content-Type 'text/plain charset=UTF-8';
637 add_header Content-Length 0;
638 return 204;
639 }
640
641 ${nginxCommonHeaders}
642 add_header Cache-Control 'public, max-age=7200';
643
644 rewrite ^/static/thumbnails/(.*)$ /$1 break;
645 '';
646 };
647
648 locations."^~ /static/redundancy/" = {
649 tryFiles = "$uri @api";
650 root = cfg.settings.storage.redundancy;
651 priority = 1360;
652 extraConfig = ''
653 if ($request_method = 'OPTIONS') {
654 ${nginxCommonHeaders}
655 add_header Access-Control-Max-Age 1728000;
656 add_header Content-Type 'text/plain charset=UTF-8';
657 add_header Content-Length 0;
658 return 204;
659 }
660 if ($request_method = 'GET') {
661 ${nginxCommonHeaders}
662
663 access_log off;
664 }
665 aio threads;
666 sendfile on;
667 sendfile_max_chunk 1M;
668
669 limit_rate_after 5M;
670
671 set $peertube_limit_rate 800k;
672 set $limit_rate $peertube_limit_rate;
673
674 rewrite ^/static/redundancy/(.*)$ /$1 break;
675 '';
676 };
677
678 locations."^~ /static/streaming-playlists/" = {
679 tryFiles = "$uri @api";
680 root = cfg.settings.storage.streaming_playlists;
681 priority = 1370;
682 extraConfig = ''
683 if ($request_method = 'OPTIONS') {
684 ${nginxCommonHeaders}
685 add_header Access-Control-Max-Age 1728000;
686 add_header Content-Type 'text/plain charset=UTF-8';
687 add_header Content-Length 0;
688 return 204;
689 }
690 if ($request_method = 'GET') {
691 ${nginxCommonHeaders}
692
693 access_log off;
694 }
695
696 aio threads;
697 sendfile on;
698 sendfile_max_chunk 1M;
699
700 limit_rate_after 5M;
701
702 set $peertube_limit_rate 5M;
703 set $limit_rate $peertube_limit_rate;
704
705 rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
706 '';
707 };
708
709 locations."~ ^/static/webseed/" = {
710 tryFiles = "$uri @api";
711 root = cfg.settings.storage.videos;
712 priority = 1380;
713 extraConfig = ''
714 if ($request_method = 'OPTIONS') {
715 ${nginxCommonHeaders}
716 add_header Access-Control-Max-Age 1728000;
717 add_header Content-Type 'text/plain charset=UTF-8';
718 add_header Content-Length 0;
719 return 204;
720 }
721 if ($request_method = 'GET') {
722 ${nginxCommonHeaders}
723
724 access_log off;
725 }
726
727 aio threads;
728 sendfile on;
729 sendfile_max_chunk 1M;
730
731 limit_rate_after 5M;
732
733 set $peertube_limit_rate 800k;
734 set $limit_rate $peertube_limit_rate;
735
736 rewrite ^/static/webseed/(.*)$ /$1 break;
737 '';
738 };
739
740 extraConfig = lib.optionalString cfg.enableWebHttps ''
741 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
742 '';
743 };
744 };
745
746 services.postgresql = lib.mkIf cfg.database.createLocally {
747 enable = true;
748 };
749
750 services.redis.servers.peertube = lib.mkMerge [
751 (lib.mkIf cfg.redis.createLocally {
752 enable = true;
753 })
754 (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
755 bind = "127.0.0.1";
756 port = cfg.redis.port;
757 })
758 (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
759 unixSocket = "/run/redis-peertube/redis.sock";
760 unixSocketPerm = 660;
761 })
762 ];
763
764 services.postfix = lib.mkIf cfg.smtp.createLocally {
765 enable = true;
766 hostname = lib.mkDefault "${cfg.localDomain}";
767 };
768
769 users.users = lib.mkMerge [
770 (lib.mkIf (cfg.user == "peertube") {
771 peertube = {
772 isSystemUser = true;
773 group = cfg.group;
774 home = cfg.package;
775 };
776 })
777 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ])
778 (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
779 ];
780
781 users.groups = {
782 ${cfg.group} = {
783 members = lib.optional cfg.configureNginx config.services.nginx.user;
784 };
785 };
786 };
787}