1 { lib, pkgs, config, options, ... }:
4 cfg = config.services.peertube;
5 opt = options.services.peertube;
7 settingsFormat = pkgs.formats.json {};
8 configFile = settingsFormat.generate "production.json" cfg.settings;
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;
19 systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ];
24 ProtectProc = "invisible";
25 # Access write directories
28 CapabilityBoundingSet = "";
30 NoNewPrivileges = true;
32 ProtectSystem = "strict";
35 PrivateDevices = 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;
49 # System Call Filtering
50 SystemCallArchitectures = "native";
53 envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") (
54 (lib.concatLists (lib.mapAttrsToList (name: value:
55 lib.optional (value != null) ''${name}="${toString value}"''
58 peertubeEnv = pkgs.writeShellScriptBin "peertube-env" ''
64 nginxCommonHeaders = lib.optionalString config.services.nginx.virtualHosts.${cfg.localDomain}.forceSSL ''
65 add_header Strict-Transport-Security 'max-age=31536000';
66 '' + lib.optionalString (config.services.nginx.virtualHosts.${cfg.localDomain}.quic && config.services.nginx.virtualHosts.${cfg.localDomain}.http3) ''
67 add_header Alt-Svc 'h3=":$server_port"; ma=604800';
70 nginxCommonHeadersExtra = ''
71 add_header Access-Control-Allow-Origin '*';
72 add_header Access-Control-Allow-Methods 'GET, OPTIONS';
73 add_header Access-Control-Allow-Headers 'Range,DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
77 options.services.peertube = {
78 enable = lib.mkEnableOption "Peertube";
83 description = "User account under which Peertube runs.";
86 group = lib.mkOption {
89 description = "Group under which Peertube runs.";
92 localDomain = lib.mkOption {
94 example = "peertube.example.com";
95 description = "The domain serving your PeerTube instance.";
98 listenHttp = lib.mkOption {
99 type = lib.types.port;
101 description = "The port that the local PeerTube web server will listen on.";
104 listenWeb = lib.mkOption {
105 type = lib.types.port;
107 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.";
110 enableWebHttps = lib.mkOption {
111 type = lib.types.bool;
113 description = "Whether clients will access your PeerTube instance with HTTPS. Does NOT configure the PeerTube webserver itself to listen for incoming HTTPS connections.";
116 dataDirs = lib.mkOption {
117 type = lib.types.listOf lib.types.path;
119 example = [ "/opt/peertube/storage" "/var/cache/peertube" ];
120 description = "Allow access to custom data locations.";
123 serviceEnvironmentFile = lib.mkOption {
124 type = lib.types.nullOr lib.types.path;
126 example = "/run/keys/peertube/password-init-root";
128 Set environment variables for the service. Mainly useful for setting the initial root password.
129 For example write to file:
130 PT_INITIAL_ROOT_PASSWORD=changeme
134 settings = lib.mkOption {
135 type = settingsFormat.type;
136 example = lib.literalExpression ''
139 hostname = "0.0.0.0";
145 tmp = "/opt/data/peertube/storage/tmp/";
146 logs = "/opt/data/peertube/storage/logs/";
147 cache = "/opt/data/peertube/storage/cache/";
151 description = "Configuration for peertube.";
154 configureNginx = lib.mkOption {
155 type = lib.types.bool;
157 description = "Configure nginx as a reverse proxy for peertube.";
161 secretsFile = lib.mkOption {
162 type = lib.types.nullOr lib.types.path;
164 example = "/run/secrets/peertube";
166 Secrets to run PeerTube.
167 Generate one using `openssl rand -hex 32`
173 createLocally = lib.mkOption {
174 type = lib.types.bool;
176 description = "Configure local PostgreSQL database server for PeerTube.";
179 host = lib.mkOption {
180 type = lib.types.str;
181 default = if cfg.database.createLocally then "/run/postgresql" else null;
182 defaultText = lib.literalExpression ''
183 if config.${opt.database.createLocally}
184 then "/run/postgresql"
187 example = "192.168.15.47";
188 description = "Database host address or unix socket.";
191 port = lib.mkOption {
192 type = lib.types.port;
194 description = "Database host port.";
197 name = lib.mkOption {
198 type = lib.types.str;
199 default = "peertube";
200 description = "Database name.";
203 user = lib.mkOption {
204 type = lib.types.str;
205 default = "peertube";
206 description = "Database user.";
209 passwordFile = lib.mkOption {
210 type = lib.types.nullOr lib.types.path;
212 example = "/run/keys/peertube/password-postgresql";
213 description = "Password for PostgreSQL database.";
218 createLocally = lib.mkOption {
219 type = lib.types.bool;
221 description = "Configure local Redis server for PeerTube.";
224 host = lib.mkOption {
225 type = lib.types.nullOr lib.types.str;
226 default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null;
227 defaultText = lib.literalExpression ''
228 if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket}
232 description = "Redis host.";
235 port = lib.mkOption {
236 type = lib.types.nullOr lib.types.port;
237 default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638;
238 defaultText = lib.literalExpression ''
239 if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket}
243 description = "Redis port.";
246 passwordFile = lib.mkOption {
247 type = lib.types.nullOr lib.types.path;
249 example = "/run/keys/peertube/password-redis-db";
250 description = "Password for redis database.";
253 enableUnixSocket = lib.mkOption {
254 type = lib.types.bool;
255 default = cfg.redis.createLocally;
256 defaultText = lib.literalExpression "config.${opt.redis.createLocally}";
257 description = "Use Unix socket.";
262 createLocally = lib.mkOption {
263 type = lib.types.bool;
265 description = "Configure local Postfix SMTP server for PeerTube.";
268 passwordFile = lib.mkOption {
269 type = lib.types.nullOr lib.types.path;
271 example = "/run/keys/peertube/password-smtp";
272 description = "Password for smtp server.";
276 package = lib.mkOption {
277 type = lib.types.package;
278 default = pkgs.peertube;
279 defaultText = lib.literalExpression "pkgs.peertube";
280 description = "PeerTube package to use.";
284 config = lib.mkIf cfg.enable {
286 { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile;
288 <option>services.peertube.serviceEnvironmentFile</option> points to
289 a file in the Nix store. You should use a quoted absolute path to
293 { assertion = cfg.secrets.secretsFile != null;
295 <option>services.peertube.secrets.secretsFile</option> needs to be set.
298 { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null));
300 <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.
303 { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null);
305 <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.
308 { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile;
310 <option>services.peertube.redis.passwordFile</option> points to
311 a file in the Nix store. You should use a quoted absolute path to
315 { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile;
317 <option>services.peertube.database.passwordFile</option> points to
318 a file in the Nix store. You should use a quoted absolute path to
322 { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile;
324 <option>services.peertube.smtp.passwordFile</option> points to
325 a file in the Nix store. You should use a quoted absolute path to
331 environment.systemPackages = [ cfg.package.cli ];
333 services.peertube.settings = lib.mkMerge [
336 port = cfg.listenHttp;
339 https = (if cfg.enableWebHttps then true else false);
340 hostname = "${cfg.localDomain}";
341 port = cfg.listenWeb;
344 hostname = "${cfg.database.host}";
345 port = cfg.database.port;
346 name = "${cfg.database.name}";
347 username = "${cfg.database.user}";
350 hostname = "${toString cfg.redis.host}";
351 port = (lib.optionalString (cfg.redis.port != null) cfg.redis.port);
354 tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/";
355 tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/";
356 bin = lib.mkDefault "/var/lib/peertube/storage/bin/";
357 avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/";
358 web_videos = lib.mkDefault "/var/lib/peertube/storage/web-videos/";
359 streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/";
360 redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/";
361 logs = lib.mkDefault "/var/lib/peertube/storage/logs/";
362 previews = lib.mkDefault "/var/lib/peertube/storage/previews/";
363 thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/";
364 storyboards = lib.mkDefault "/var/lib/peertube/storage/storyboards/";
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/";
375 youtube_dl_release = {
376 python_path = "${pkgs.python3}/bin/python";
382 (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; })
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} - -"
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" ];
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;
405 in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}";
409 WorkingDirectory = cfg.package;
414 RestrictAddressFamilies = [ "AF_UNIX" ];
415 MemoryDenyWriteExecute = true;
416 # System Call Filtering
417 SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]);
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" ];
432 path = with pkgs; [ nodejs_18 yarn ffmpeg-headless openssl ];
436 cat > /var/lib/peertube/config/local.yaml <<EOF
437 ${lib.optionalString (cfg.secrets.secretsFile != null) ''
439 peertube: '$(cat ${cfg.secrets.secretsFile})'
441 ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
443 password: '$(cat ${cfg.database.passwordFile})'
445 ${lib.optionalString (cfg.redis.passwordFile != null) ''
447 auth: '$(cat ${cfg.redis.passwordFile})'
449 ${lib.optionalString (cfg.smtp.passwordFile != null) ''
451 password: '$(cat ${cfg.smtp.passwordFile})'
455 ln -sf ${configFile} /var/lib/peertube/config/production.json
456 ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml
457 ln -sf ${cfg.package}/client/dist -T /var/lib/peertube/www/client
458 ln -sf ${cfg.settings.storage.client_overrides} -T /var/lib/peertube/www/client-overrides
459 exec node dist/server
466 WorkingDirectory = cfg.package;
467 SyslogIdentifier = "peertube";
471 # State directory and mode
472 StateDirectory = "peertube";
473 StateDirectoryMode = "0750";
474 # Cache directory and mode
475 CacheDirectory = "peertube";
476 CacheDirectoryMode = "0750";
477 # Access write directories
478 ReadWritePaths = cfg.dataDirs;
480 EnvironmentFile = cfg.serviceEnvironmentFile;
482 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
483 MemoryDenyWriteExecute = false;
484 # System Call Filtering
485 SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ];
489 services.nginx = lib.mkIf cfg.configureNginx {
491 upstreams."peertube".servers = {
492 "127.0.0.1:${toString cfg.listenHttp}".fail_timeout = "0";
494 virtualHosts."${cfg.localDomain}" = {
495 root = "/var/lib/peertube/www";
499 tryFiles = "/dev/null @api";
503 locations."~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$" = {
504 tryFiles = "/dev/null @api";
508 client_max_body_size 0;
509 proxy_request_buffering off;
510 '' + nginxCommonHeaders;
513 locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
514 tryFiles = "/dev/null @api";
515 root = cfg.settings.storage.tmp;
519 limit_except POST HEAD { deny all; }
521 client_max_body_size 12G;
522 add_header X-File-Maximum-Size 8G always;
523 '' + nginxCommonHeaders;
526 locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
527 tryFiles = "/dev/null @api";
528 root = cfg.settings.storage.tmp;
532 client_max_body_size 12G;
533 add_header X-File-Maximum-Size 8G always;
534 '' + nginxCommonHeaders;
537 locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
538 tryFiles = "/dev/null @api";
542 client_max_body_size 6M;
543 add_header X-File-Maximum-Size 4M always;
544 '' + nginxCommonHeaders;
548 proxyPass = "http://peertube";
552 proxy_set_header Host $host;
553 proxy_set_header X-Real-IP $remote_addr;
554 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
556 proxy_connect_timeout 10m;
558 proxy_send_timeout 10m;
559 proxy_read_timeout 10m;
561 client_max_body_size 100k;
563 ''+ nginxCommonHeaders;
567 locations."/socket.io" = {
568 tryFiles = "/dev/null @api_websocket";
572 locations."/tracker/socket" = {
573 tryFiles = "/dev/null @api_websocket";
577 proxy_read_timeout 15m;
581 locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
582 tryFiles = "/dev/null @api_websocket";
586 locations."@api_websocket" = {
587 proxyPass = "http://peertube";
591 proxy_http_version 1.1;
592 proxy_set_header Upgrade $http_upgrade;
593 proxy_set_header Connection 'upgrade';
594 proxy_set_header Host $host;
595 proxy_set_header X-Real-IP $remote_addr;
596 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
598 '' + nginxCommonHeaders;
601 # Bypass PeerTube for performance reasons.
602 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))$" = {
603 tryFiles = "/client-overrides/$1 /client/$1 $1";
606 extraConfig = nginxCommonHeaders;
609 locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
610 alias = "${cfg.package}/client/dist/$1";
613 add_header Cache-Control 'public, max-age=604800, immutable';
614 '' + nginxCommonHeaders;
617 locations."^~ /download/" = {
618 proxyPass = "http://peertube";
621 proxy_set_header Host $host;
622 proxy_set_header X-Real-IP $remote_addr;
623 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
626 '' + nginxCommonHeaders;
629 locations."^~ /static/streaming-playlists/hls/private/" = {
630 proxyPass = "http://peertube";
633 proxy_set_header Host $host;
634 proxy_set_header X-Real-IP $remote_addr;
635 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
638 '' + nginxCommonHeaders;
641 locations."^~ /static/web-videos/private/" = {
642 proxyPass = "http://peertube";
645 proxy_set_header Host $host;
646 proxy_set_header X-Real-IP $remote_addr;
647 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
650 '' + nginxCommonHeaders;
653 locations."^~ /static/webseed/private/" = {
654 proxyPass = "http://peertube";
657 proxy_set_header Host $host;
658 proxy_set_header X-Real-IP $remote_addr;
659 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
662 '' + nginxCommonHeaders;
665 locations."^~ /static/redundancy/" = {
666 tryFiles = "$uri @api";
667 root = cfg.settings.storage.redundancy;
670 set $peertube_limit_rate 800k;
672 if ($request_uri ~ -fragmented.mp4$) {
673 set $peertube_limit_rate 5M;
676 if ($request_method = 'OPTIONS') {
677 ${nginxCommonHeaders}
678 ${nginxCommonHeadersExtra}
679 add_header Access-Control-Max-Age 1728000;
680 add_header Content-Type 'text/plain charset=UTF-8';
681 add_header Content-Length 0;
684 if ($request_method = 'GET') {
685 ${nginxCommonHeaders}
686 ${nginxCommonHeadersExtra}
693 sendfile_max_chunk 1M;
695 limit_rate $peertube_limit_rate;
698 rewrite ^/static/redundancy/(.*)$ /$1 break;
702 locations."^~ /static/streaming-playlists/" = {
703 tryFiles = "$uri @api";
704 root = cfg.settings.storage.streaming_playlists;
707 set $peertube_limit_rate 800k;
709 if ($request_uri ~ -fragmented.mp4$) {
710 set $peertube_limit_rate 5M;
713 if ($request_method = 'OPTIONS') {
714 ${nginxCommonHeaders}
715 ${nginxCommonHeadersExtra}
716 add_header Access-Control-Max-Age 1728000;
717 add_header Content-Type 'text/plain charset=UTF-8';
718 add_header Content-Length 0;
721 if ($request_method = 'GET') {
722 ${nginxCommonHeaders}
723 ${nginxCommonHeadersExtra}
730 sendfile_max_chunk 1M;
732 limit_rate $peertube_limit_rate;
735 rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
739 locations."^~ /static/web-videos/" = {
740 tryFiles = "$uri @api";
741 root = cfg.settings.storage.web_videos;
744 set $peertube_limit_rate 800k;
746 if ($request_uri ~ -fragmented.mp4$) {
747 set $peertube_limit_rate 5M;
750 if ($request_method = 'OPTIONS') {
751 ${nginxCommonHeaders}
752 ${nginxCommonHeadersExtra}
753 add_header Access-Control-Max-Age 1728000;
754 add_header Content-Type 'text/plain charset=UTF-8';
755 add_header Content-Length 0;
758 if ($request_method = 'GET') {
759 ${nginxCommonHeaders}
760 ${nginxCommonHeadersExtra}
767 sendfile_max_chunk 1M;
769 limit_rate $peertube_limit_rate;
772 rewrite ^/static/web-videos/(.*)$ /$1 break;
776 locations."^~ /static/webseed/" = {
777 tryFiles = "$uri @api";
778 root = cfg.settings.storage.web_videos;
781 set $peertube_limit_rate 800k;
783 if ($request_uri ~ -fragmented.mp4$) {
784 set $peertube_limit_rate 5M;
787 if ($request_method = 'OPTIONS') {
788 ${nginxCommonHeaders}
789 ${nginxCommonHeadersExtra}
790 add_header Access-Control-Max-Age 1728000;
791 add_header Content-Type 'text/plain charset=UTF-8';
792 add_header Content-Length 0;
795 if ($request_method = 'GET') {
796 ${nginxCommonHeaders}
797 ${nginxCommonHeadersExtra}
804 sendfile_max_chunk 1M;
806 limit_rate $peertube_limit_rate;
809 rewrite ^/static/webseed/(.*)$ /web-videos/$1 break;
815 services.postgresql = lib.mkIf cfg.database.createLocally {
819 services.redis.servers.peertube = lib.mkMerge [
820 (lib.mkIf cfg.redis.createLocally {
823 (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
825 port = cfg.redis.port;
827 (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
828 unixSocket = "/run/redis-peertube/redis.sock";
829 unixSocketPerm = 660;
833 services.postfix = lib.mkIf cfg.smtp.createLocally {
835 hostname = lib.mkDefault "${cfg.localDomain}";
838 users.users = lib.mkMerge [
839 (lib.mkIf (cfg.user == "peertube") {
846 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ peertubeEnv pkgs.nodejs_18 pkgs.yarn pkgs.ffmpeg-headless ])
847 (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
852 members = lib.optional cfg.configureNginx config.services.nginx.user;