diff options
Diffstat (limited to 'nixpkgs/nixos/modules/services/web-apps/peertube.nix')
-rw-r--r-- | nixpkgs/nixos/modules/services/web-apps/peertube.nix | 413 |
1 files changed, 401 insertions, 12 deletions
diff --git a/nixpkgs/nixos/modules/services/web-apps/peertube.nix b/nixpkgs/nixos/modules/services/web-apps/peertube.nix index c5a80e2d7d9d..4ef2d7dce532 100644 --- a/nixpkgs/nixos/modules/services/web-apps/peertube.nix +++ b/nixpkgs/nixos/modules/services/web-apps/peertube.nix @@ -67,9 +67,19 @@ let node ~/dist/server/tools/peertube.js $@ ''; + nginxCommonHeaders = lib.optionalString cfg.enableWebHttps '' + add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; + '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' + add_header Alt-Svc 'h3=":443"; ma=86400'; + '' + '' + add_header Access-Control-Allow-Origin '*'; + add_header Access-Control-Allow-Methods 'GET, OPTIONS'; + add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type'; + ''; + in { options.services.peertube = { - enable = lib.mkEnableOption "Enable Peertube’s service"; + enable = lib.mkEnableOption (lib.mdDoc "Peertube"); user = lib.mkOption { type = lib.types.str; @@ -90,13 +100,13 @@ in { }; listenHttp = lib.mkOption { - type = lib.types.int; + type = lib.types.port; default = 9000; description = lib.mdDoc "listen port for HTTP server."; }; listenWeb = lib.mkOption { - type = lib.types.int; + type = lib.types.port; default = 9000; description = lib.mdDoc "listen port for WEB server."; }; @@ -145,6 +155,24 @@ in { description = lib.mdDoc "Configuration for peertube."; }; + configureNginx = lib.mkOption { + type = lib.types.bool; + default = false; + description = lib.mdDoc "Configure nginx as a reverse proxy for peertube."; + }; + + secrets = { + secretsFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/secrets/peertube"; + description = lib.mdDoc '' + Secrets to run PeerTube. + Generate one using `openssl rand -hex 32` + ''; + }; + }; + database = { createLocally = lib.mkOption { type = lib.types.bool; @@ -165,7 +193,7 @@ in { }; port = lib.mkOption { - type = lib.types.int; + type = lib.types.port; default = 5432; description = lib.mdDoc "Database host port."; }; @@ -185,7 +213,7 @@ in { passwordFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; - example = "/run/keys/peertube/password-posgressql-db"; + example = "/run/keys/peertube/password-postgresql"; description = lib.mdDoc "Password for PostgreSQL database."; }; }; @@ -266,6 +294,11 @@ in { prevent this. ''; } + { assertion = cfg.secrets.secretsFile != null; + message = '' + <option>services.peertube.secrets.secretsFile</option> needs to be set. + ''; + } { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null)); message = '' <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. @@ -333,6 +366,7 @@ in { captions = lib.mkDefault "/var/lib/peertube/storage/captions/"; cache = lib.mkDefault "/var/lib/peertube/storage/cache/"; plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/"; + well_known = lib.mkDefault "/var/lib/peertube/storage/well_known/"; client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/"; }; import = { @@ -351,12 +385,14 @@ in { systemd.tmpfiles.rules = [ "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -" "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -" + "d '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -" + "z '/var/lib/peertube/www' 0750 ${cfg.user} ${cfg.group} - -" ]; systemd.services.peertube-init-db = lib.mkIf cfg.database.createLocally { description = "Initialization database for PeerTube daemon"; after = [ "network.target" "postgresql.service" ]; - wantedBy = [ "multi-user.target" ]; + requires = [ "postgresql.service" ]; script = let psqlSetupCommands = pkgs.writeText "peertube-init.sql" '' @@ -385,18 +421,24 @@ in { systemd.services.peertube = { description = "PeerTube daemon"; after = [ "network.target" ] - ++ lib.optionals cfg.redis.createLocally [ "redis.service" ] + ++ lib.optional cfg.redis.createLocally "redis-peertube.service" + ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ]; + requires = lib.optional cfg.redis.createLocally "redis-peertube.service" ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ]; wantedBy = [ "multi-user.target" ]; environment = env; - path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn python3 ]; + path = with pkgs; [ bashInteractive ffmpeg nodejs_18 openssl yarn python3 ]; script = '' #!/bin/sh umask 077 cat > /var/lib/peertube/config/local.yaml <<EOF + ${lib.optionalString (cfg.secrets.secretsFile != null) '' + secrets: + peertube: '$(cat ${cfg.secrets.secretsFile})' + ''} ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) '' database: password: '$(cat ${cfg.database.passwordFile})' @@ -410,8 +452,11 @@ in { password: '$(cat ${cfg.smtp.passwordFile})' ''} EOF - ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml + umask 027 ln -sf ${configFile} /var/lib/peertube/config/production.json + ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml + ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client + ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides npm start ''; serviceConfig = { @@ -420,6 +465,7 @@ in { RestartSec = 20; TimeoutSec = 60; WorkingDirectory = cfg.package; + SyslogIdentifier = "peertube"; # User and group User = cfg.user; Group = cfg.group; @@ -441,6 +487,347 @@ in { } // cfgService; }; + services.nginx = lib.mkIf cfg.configureNginx { + enable = true; + virtualHosts."${cfg.localDomain}" = { + root = "/var/lib/peertube/www"; + + # Application + locations."/" = { + tryFiles = "/dev/null @api"; + priority = 1110; + }; + + locations."= /api/v1/videos/upload-resumable" = { + tryFiles = "/dev/null @api"; + priority = 1120; + + extraConfig = '' + client_max_body_size 0; + proxy_request_buffering off; + ''; + }; + + locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = { + tryFiles = "/dev/null @api"; + root = cfg.settings.storage.tmp; + priority = 1130; + + extraConfig = '' + client_max_body_size 12G; + add_header X-File-Maximum-Size 8G always; + '' + lib.optionalString cfg.enableWebHttps '' + add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; + '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' + add_header Alt-Svc 'h3=":443"; ma=86400'; + ''; + }; + + locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = { + tryFiles = "/dev/null @api"; + priority = 1140; + + extraConfig = '' + client_max_body_size 6M; + add_header X-File-Maximum-Size 4M always; + '' + lib.optionalString cfg.enableWebHttps '' + add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; + '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' + add_header Alt-Svc 'h3=":443"; ma=86400'; + ''; + }; + + locations."@api" = { + proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + priority = 1150; + + extraConfig = '' + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + + proxy_connect_timeout 10m; + + proxy_send_timeout 10m; + proxy_read_timeout 10m; + + client_max_body_size 100k; + send_timeout 10m; + ''; + }; + + # Websocket + locations."/socket.io" = { + tryFiles = "/dev/null @api_websocket"; + priority = 1210; + }; + + locations."/tracker/socket" = { + tryFiles = "/dev/null @api_websocket"; + priority = 1220; + + extraConfig = '' + proxy_read_timeout 15m; + ''; + }; + + locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = { + tryFiles = "/dev/null @api_websocket"; + priority = 1230; + }; + + locations."@api_websocket" = { + proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + priority = 1240; + + extraConfig = '' + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection 'upgrade'; + + proxy_http_version 1.1; + ''; + }; + + # Bypass PeerTube for performance reasons. + 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))$" = { + tryFiles = "/client-overrides/$1 /client/$1 $1"; + priority = 1310; + }; + + locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = { + alias = "${cfg.package}/client/dist/$1"; + priority = 1320; + extraConfig = '' + add_header Cache-Control 'public, max-age=604800, immutable'; + '' + lib.optionalString cfg.enableWebHttps '' + add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; + '' + lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.http3 '' + add_header Alt-Svc 'h3=":443"; ma=86400'; + ''; + }; + + locations."^~ /lazy-static/avatars/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.avatars; + priority = 1330; + extraConfig = '' + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Cache-Control 'no-cache'; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + + ${nginxCommonHeaders} + add_header Cache-Control 'public, max-age=7200'; + + rewrite ^/lazy-static/avatars/(.*)$ /$1 break; + ''; + }; + + locations."^~ /lazy-static/banners/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.avatars; + priority = 1340; + extraConfig = '' + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Cache-Control 'no-cache'; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + + ${nginxCommonHeaders} + add_header Cache-Control 'public, max-age=7200'; + + rewrite ^/lazy-static/banners/(.*)$ /$1 break; + ''; + }; + + locations."^~ /lazy-static/previews/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.previews; + priority = 1350; + extraConfig = '' + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Cache-Control 'no-cache'; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + + ${nginxCommonHeaders} + add_header Cache-Control 'public, max-age=7200'; + + rewrite ^/lazy-static/previews/(.*)$ /$1 break; + ''; + }; + + locations."^~ /static/streaming-playlists/private/" = { + proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + priority = 1410; + extraConfig = '' + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + + proxy_limit_rate 5M; + ''; + }; + + locations."^~ /static/webseed/private/" = { + proxyPass = "http://127.0.0.1:${toString cfg.listenHttp}"; + priority = 1420; + extraConfig = '' + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + + proxy_limit_rate 5M; + ''; + }; + + locations."^~ /static/thumbnails/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.thumbnails; + priority = 1430; + extraConfig = '' + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Cache-Control 'no-cache'; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + + ${nginxCommonHeaders} + add_header Cache-Control 'public, max-age=7200'; + + rewrite ^/static/thumbnails/(.*)$ /$1 break; + ''; + }; + + locations."^~ /static/redundancy/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.redundancy; + priority = 1440; + extraConfig = '' + set $peertube_limit_rate 800k; + + if ($request_uri ~ -fragmented.mp4$) { + set $peertube_limit_rate 5M; + } + + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + if ($request_method = 'GET') { + ${nginxCommonHeaders} + + access_log off; + } + + aio threads; + sendfile on; + sendfile_max_chunk 1M; + + limit_rate $peertube_limit_rate; + limit_rate_after 5M; + + rewrite ^/static/redundancy/(.*)$ /$1 break; + ''; + }; + + locations."^~ /static/streaming-playlists/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.streaming_playlists; + priority = 1450; + extraConfig = '' + set $peertube_limit_rate 800k; + + if ($request_uri ~ -fragmented.mp4$) { + set $peertube_limit_rate 5M; + } + + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + if ($request_method = 'GET') { + ${nginxCommonHeaders} + + access_log off; + } + + aio threads; + sendfile on; + sendfile_max_chunk 1M; + + limit_rate $peertube_limit_rate; + limit_rate_after 5M; + + rewrite ^/static/streaming-playlists/(.*)$ /$1 break; + ''; + }; + + locations."^~ /static/webseed/" = { + tryFiles = "$uri @api"; + root = cfg.settings.storage.videos; + priority = 1460; + extraConfig = '' + set $peertube_limit_rate 800k; + + if ($request_uri ~ -fragmented.mp4$) { + set $peertube_limit_rate 5M; + } + + if ($request_method = 'OPTIONS') { + ${nginxCommonHeaders} + add_header Access-Control-Max-Age 1728000; + add_header Content-Type 'text/plain charset=UTF-8'; + add_header Content-Length 0; + return 204; + } + if ($request_method = 'GET') { + ${nginxCommonHeaders} + + access_log off; + } + + aio threads; + sendfile on; + sendfile_max_chunk 1M; + + limit_rate $peertube_limit_rate; + limit_rate_after 5M; + + rewrite ^/static/webseed/(.*)$ /$1 break; + ''; + }; + + extraConfig = lib.optionalString cfg.enableWebHttps '' + add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains'; + ''; + }; + }; + services.postgresql = lib.mkIf cfg.database.createLocally { enable = true; }; @@ -472,12 +859,14 @@ in { home = cfg.package; }; }) - (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ]) + (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs_18 pkgs.yarn ]) (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];}) ]; - users.groups = lib.optionalAttrs (cfg.group == "peertube") { - peertube = { }; + users.groups = { + ${cfg.group} = { + members = lib.optional cfg.configureNginx config.services.nginx.user; + }; }; }; } |