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