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 secrets = {
165 secretsFile = lib.mkOption {
166 type = lib.types.nullOr lib.types.path;
167 default = null;
168 example = "/run/secrets/peertube";
169 description = lib.mdDoc ''
170 Secrets to run PeerTube.
171 Generate one using `openssl rand -hex 32`
172 '';
173 };
174 };
175
176 database = {
177 createLocally = lib.mkOption {
178 type = lib.types.bool;
179 default = false;
180 description = lib.mdDoc "Configure local PostgreSQL database server for PeerTube.";
181 };
182
183 host = lib.mkOption {
184 type = lib.types.str;
185 default = if cfg.database.createLocally then "/run/postgresql" else null;
186 defaultText = lib.literalExpression ''
187 if config.${opt.database.createLocally}
188 then "/run/postgresql"
189 else null
190 '';
191 example = "192.168.15.47";
192 description = lib.mdDoc "Database host address or unix socket.";
193 };
194
195 port = lib.mkOption {
196 type = lib.types.port;
197 default = 5432;
198 description = lib.mdDoc "Database host port.";
199 };
200
201 name = lib.mkOption {
202 type = lib.types.str;
203 default = "peertube";
204 description = lib.mdDoc "Database name.";
205 };
206
207 user = lib.mkOption {
208 type = lib.types.str;
209 default = "peertube";
210 description = lib.mdDoc "Database user.";
211 };
212
213 passwordFile = lib.mkOption {
214 type = lib.types.nullOr lib.types.path;
215 default = null;
216 example = "/run/keys/peertube/password-postgresql";
217 description = lib.mdDoc "Password for PostgreSQL database.";
218 };
219 };
220
221 redis = {
222 createLocally = lib.mkOption {
223 type = lib.types.bool;
224 default = false;
225 description = lib.mdDoc "Configure local Redis server for PeerTube.";
226 };
227
228 host = lib.mkOption {
229 type = lib.types.nullOr lib.types.str;
230 default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
231 defaultText = lib.literalExpression ''
232 if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket}
233 then "127.0.0.1"
234 else null
235 '';
236 description = lib.mdDoc "Redis host.";
237 };
238
239 port = lib.mkOption {
240 type = lib.types.nullOr lib.types.port;
241 default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
242 defaultText = lib.literalExpression ''
243 if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
244 then null
245 else 6379
246 '';
247 description = lib.mdDoc "Redis port.";
248 };
249
250 passwordFile = lib.mkOption {
251 type = lib.types.nullOr lib.types.path;
252 default = null;
253 example = "/run/keys/peertube/password-redis-db";
254 description = lib.mdDoc "Password for redis database.";
255 };
256
257 enableUnixSocket = lib.mkOption {
258 type = lib.types.bool;
259 default = cfg.redis.createLocally;
260 defaultText = lib.literalExpression "config.${opt.redis.createLocally}";
261 description = lib.mdDoc "Use Unix socket.";
262 };
263 };
264
265 smtp = {
266 createLocally = lib.mkOption {
267 type = lib.types.bool;
268 default = false;
269 description = lib.mdDoc "Configure local Postfix SMTP server for PeerTube.";
270 };
271
272 passwordFile = lib.mkOption {
273 type = lib.types.nullOr lib.types.path;
274 default = null;
275 example = "/run/keys/peertube/password-smtp";
276 description = lib.mdDoc "Password for smtp server.";
277 };
278 };
279
280 package = lib.mkOption {
281 type = lib.types.package;
282 default = pkgs.peertube;
283 defaultText = lib.literalExpression "pkgs.peertube";
284 description = lib.mdDoc "Peertube package to use.";
285 };
286 };
287
288 config = lib.mkIf cfg.enable {
289 assertions = [
290 { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile;
291 message = ''
292 <option>services.peertube.serviceEnvironmentFile</option> points to
293 a file in the Nix store. You should use a quoted absolute path to
294 prevent this.
295 '';
296 }
297 { assertion = cfg.secrets.secretsFile != null;
298 message = ''
299 <option>services.peertube.secrets.secretsFile</option> needs to be set.
300 '';
301 }
302 { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
303 message = ''
304 <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.
305 '';
306 }
307 { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null);
308 message = ''
309 <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.
310 '';
311 }
312 { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile;
313 message = ''
314 <option>services.peertube.redis.passwordFile</option> points to
315 a file in the Nix store. You should use a quoted absolute path to
316 prevent this.
317 '';
318 }
319 { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile;
320 message = ''
321 <option>services.peertube.database.passwordFile</option> points to
322 a file in the Nix store. You should use a quoted absolute path to
323 prevent this.
324 '';
325 }
326 { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile;
327 message = ''
328 <option>services.peertube.smtp.passwordFile</option> points to
329 a file in the Nix store. You should use a quoted absolute path to
330 prevent this.
331 '';
332 }
333 ];
334
335 services.peertube.settings = lib.mkMerge [
336 {
337 listen = {
338 port = cfg.listenHttp;
339 };
340 webserver = {
341 https = (if cfg.enableWebHttps then true else false);
342 hostname = "${cfg.localDomain}";
343 port = cfg.listenWeb;
344 };
345 database = {
346 hostname = "${cfg.database.host}";
347 port = cfg.database.port;
348 name = "${cfg.database.name}";
349 username = "${cfg.database.user}";
350 };
351 redis = {
352 hostname = "${toString cfg.redis.host}";
353 port = (if cfg.redis.port == null then "" else cfg.redis.port);
354 };
355 storage = {
356 tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
357 bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
358 avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
359 videos = lib.mkDefault "/var/lib/peertube/storage/videos/";
360 streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
361 redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
362 logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
363 previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
364 thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
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 ];
384
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 ];
391
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" ];
396
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}";
406
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 };
420
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" ];
429
430 environment = env;
431
432 path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ];
433
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 npm start
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 };
489
490 services.nginx = lib.mkIf cfg.configureNginx {
491 enable = true;
492 virtualHosts."${cfg.localDomain}" = {
493 root = "/var/lib/peertube/www";
494
495 # Application
496 locations."/" = {
497 tryFiles = "/dev/null @api";
498 priority = 1110;
499 };
500
501 locations."= /api/v1/videos/upload-resumable" = {
502 tryFiles = "/dev/null @api";
503 priority = 1120;
504
505 extraConfig = ''
506 client_max_body_size 0;
507 proxy_request_buffering off;
508 '';
509 };
510
511 locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
512 tryFiles = "/dev/null @api";
513 root = cfg.settings.storage.tmp;
514 priority = 1130;
515
516 extraConfig = ''
517 client_max_body_size 12G;
518 add_header X-File-Maximum-Size 8G always;
519 '' + lib.optionalString cfg.enableWebHttps ''
520 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
521 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
522 add_header Alt-Svc 'h3=":443"; ma=86400';
523 '';
524 };
525
526 locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
527 tryFiles = "/dev/null @api";
528 priority = 1140;
529
530 extraConfig = ''
531 client_max_body_size 6M;
532 add_header X-File-Maximum-Size 4M always;
533 '' + lib.optionalString cfg.enableWebHttps ''
534 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
535 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
536 add_header Alt-Svc 'h3=":443"; ma=86400';
537 '';
538 };
539
540 locations."@api" = {
541 proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
542 priority = 1150;
543
544 extraConfig = ''
545 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
546 proxy_set_header Host $host;
547 proxy_set_header X-Real-IP $remote_addr;
548
549 proxy_connect_timeout 10m;
550
551 proxy_send_timeout 10m;
552 proxy_read_timeout 10m;
553
554 client_max_body_size 100k;
555 send_timeout 10m;
556 '';
557 };
558
559 # Websocket
560 locations."/socket.io" = {
561 tryFiles = "/dev/null @api_websocket";
562 priority = 1210;
563 };
564
565 locations."/tracker/socket" = {
566 tryFiles = "/dev/null @api_websocket";
567 priority = 1220;
568
569 extraConfig = ''
570 proxy_read_timeout 15m;
571 '';
572 };
573
574 locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
575 tryFiles = "/dev/null @api_websocket";
576 priority = 1230;
577 };
578
579 locations."@api_websocket" = {
580 proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
581 priority = 1240;
582
583 extraConfig = ''
584 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
585 proxy_set_header Host $host;
586 proxy_set_header X-Real-IP $remote_addr;
587 proxy_set_header Upgrade $http_upgrade;
588 proxy_set_header Connection 'upgrade';
589
590 proxy_http_version 1.1;
591 '';
592 };
593
594 # Bypass PeerTube for performance reasons.
595 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))$" = {
596 tryFiles = "/client-overrides/$1 /client/$1 $1";
597 priority = 1310;
598 };
599
600 locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
601 alias = "${cfg.package}/client/dist/$1";
602 priority = 1320;
603 extraConfig = ''
604 add_header Cache-Control 'public, max-age=604800, immutable';
605 '' + lib.optionalString cfg.enableWebHttps ''
606 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
607 '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 ''
608 add_header Alt-Svc 'h3=":443"; ma=86400';
609 '';
610 };
611
612 locations."^~ /lazy-static/avatars/" = {
613 tryFiles = "$uri @api";
614 root = cfg.settings.storage.avatars;
615 priority = 1330;
616 extraConfig = ''
617 if ($request_method = 'OPTIONS') {
618 ${nginxCommonHeaders}
619 add_header Access-Control-Max-Age 1728000;
620 add_header Cache-Control 'no-cache';
621 add_header Content-Type 'text/plain charset=UTF-8';
622 add_header Content-Length 0;
623 return 204;
624 }
625
626 ${nginxCommonHeaders}
627 add_header Cache-Control 'public, max-age=7200';
628
629 rewrite ^/lazy-static/avatars/(.*)$ /$1 break;
630 '';
631 };
632
633 locations."^~ /lazy-static/banners/" = {
634 tryFiles = "$uri @api";
635 root = cfg.settings.storage.avatars;
636 priority = 1340;
637 extraConfig = ''
638 if ($request_method = 'OPTIONS') {
639 ${nginxCommonHeaders}
640 add_header Access-Control-Max-Age 1728000;
641 add_header Cache-Control 'no-cache';
642 add_header Content-Type 'text/plain charset=UTF-8';
643 add_header Content-Length 0;
644 return 204;
645 }
646
647 ${nginxCommonHeaders}
648 add_header Cache-Control 'public, max-age=7200';
649
650 rewrite ^/lazy-static/banners/(.*)$ /$1 break;
651 '';
652 };
653
654 locations."^~ /lazy-static/previews/" = {
655 tryFiles = "$uri @api";
656 root = cfg.settings.storage.previews;
657 priority = 1350;
658 extraConfig = ''
659 if ($request_method = 'OPTIONS') {
660 ${nginxCommonHeaders}
661 add_header Access-Control-Max-Age 1728000;
662 add_header Cache-Control 'no-cache';
663 add_header Content-Type 'text/plain charset=UTF-8';
664 add_header Content-Length 0;
665 return 204;
666 }
667
668 ${nginxCommonHeaders}
669 add_header Cache-Control 'public, max-age=7200';
670
671 rewrite ^/lazy-static/previews/(.*)$ /$1 break;
672 '';
673 };
674
675 locations."^~ /static/streaming-playlists/private/" = {
676 proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
677 priority = 1410;
678 extraConfig = ''
679 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
680 proxy_set_header Host $host;
681 proxy_set_header X-Real-IP $remote_addr;
682
683 proxy_limit_rate 5M;
684 '';
685 };
686
687 locations."^~ /static/webseed/private/" = {
688 proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}";
689 priority = 1420;
690 extraConfig = ''
691 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
692 proxy_set_header Host $host;
693 proxy_set_header X-Real-IP $remote_addr;
694
695 proxy_limit_rate 5M;
696 '';
697 };
698
699 locations."^~ /static/thumbnails/" = {
700 tryFiles = "$uri @api";
701 root = cfg.settings.storage.thumbnails;
702 priority = 1430;
703 extraConfig = ''
704 if ($request_method = 'OPTIONS') {
705 ${nginxCommonHeaders}
706 add_header Access-Control-Max-Age 1728000;
707 add_header Cache-Control 'no-cache';
708 add_header Content-Type 'text/plain charset=UTF-8';
709 add_header Content-Length 0;
710 return 204;
711 }
712
713 ${nginxCommonHeaders}
714 add_header Cache-Control 'public, max-age=7200';
715
716 rewrite ^/static/thumbnails/(.*)$ /$1 break;
717 '';
718 };
719
720 locations."^~ /static/redundancy/" = {
721 tryFiles = "$uri @api";
722 root = cfg.settings.storage.redundancy;
723 priority = 1440;
724 extraConfig = ''
725 set $peertube_limit_rate 800k;
726
727 if ($request_uri ~ -fragmented.mp4$) {
728 set $peertube_limit_rate 5M;
729 }
730
731 if ($request_method = 'OPTIONS') {
732 ${nginxCommonHeaders}
733 add_header Access-Control-Max-Age 1728000;
734 add_header Content-Type 'text/plain charset=UTF-8';
735 add_header Content-Length 0;
736 return 204;
737 }
738 if ($request_method = 'GET') {
739 ${nginxCommonHeaders}
740
741 access_log off;
742 }
743
744 aio threads;
745 sendfile on;
746 sendfile_max_chunk 1M;
747
748 limit_rate $peertube_limit_rate;
749 limit_rate_after 5M;
750
751 rewrite ^/static/redundancy/(.*)$ /$1 break;
752 '';
753 };
754
755 locations."^~ /static/streaming-playlists/" = {
756 tryFiles = "$uri @api";
757 root = cfg.settings.storage.streaming_playlists;
758 priority = 1450;
759 extraConfig = ''
760 set $peertube_limit_rate 800k;
761
762 if ($request_uri ~ -fragmented.mp4$) {
763 set $peertube_limit_rate 5M;
764 }
765
766 if ($request_method = 'OPTIONS') {
767 ${nginxCommonHeaders}
768 add_header Access-Control-Max-Age 1728000;
769 add_header Content-Type 'text/plain charset=UTF-8';
770 add_header Content-Length 0;
771 return 204;
772 }
773 if ($request_method = 'GET') {
774 ${nginxCommonHeaders}
775
776 access_log off;
777 }
778
779 aio threads;
780 sendfile on;
781 sendfile_max_chunk 1M;
782
783 limit_rate $peertube_limit_rate;
784 limit_rate_after 5M;
785
786 rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
787 '';
788 };
789
790 locations."^~ /static/webseed/" = {
791 tryFiles = "$uri @api";
792 root = cfg.settings.storage.videos;
793 priority = 1460;
794 extraConfig = ''
795 set $peertube_limit_rate 800k;
796
797 if ($request_uri ~ -fragmented.mp4$) {
798 set $peertube_limit_rate 5M;
799 }
800
801 if ($request_method = 'OPTIONS') {
802 ${nginxCommonHeaders}
803 add_header Access-Control-Max-Age 1728000;
804 add_header Content-Type 'text/plain charset=UTF-8';
805 add_header Content-Length 0;
806 return 204;
807 }
808 if ($request_method = 'GET') {
809 ${nginxCommonHeaders}
810
811 access_log off;
812 }
813
814 aio threads;
815 sendfile on;
816 sendfile_max_chunk 1M;
817
818 limit_rate $peertube_limit_rate;
819 limit_rate_after 5M;
820
821 rewrite ^/static/webseed/(.*)$ /$1 break;
822 '';
823 };
824
825 extraConfig = lib.optionalString cfg.enableWebHttps ''
826 add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains';
827 '';
828 };
829 };
830
831 services.postgresql = lib.mkIf cfg.database.createLocally {
832 enable = true;
833 };
834
835 services.redis.servers.peertube = lib.mkMerge [
836 (lib.mkIf cfg.redis.createLocally {
837 enable = true;
838 })
839 (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
840 bind = "127.0.0.1";
841 port = cfg.redis.port;
842 })
843 (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
844 unixSocket = "/run/redis-peertube/redis.sock";
845 unixSocketPerm = 660;
846 })
847 ];
848
849 services.postfix = lib.mkIf cfg.smtp.createLocally {
850 enable = true;
851 hostname = lib.mkDefault "${cfg.localDomain}";
852 };
853
854 users.users = lib.mkMerge [
855 (lib.mkIf (cfg.user == "peertube") {
856 peertube = {
857 isSystemUser = true;
858 group = cfg.group;
859 home = cfg.package;
860 };
861 })
862 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ])
863 (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
864 ];
865
866 users.groups = {
867 ${cfg.group} = {
868 members = lib.optional cfg.configureNginx config.services.nginx.user;
869 };
870 };
871 };
872}