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