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 ];
437 cat > /var/lib/peertube/config/local.yaml <<EOF
438 ${lib.optionalString (cfg.secrets.secretsFile != null) ''
440 peertube: '$(cat ${cfg.secrets.secretsFile})'
442 ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) ''
444 password: '$(cat ${cfg.database.passwordFile})'
446 ${lib.optionalString (cfg.redis.passwordFile != null) ''
448 auth: '$(cat ${cfg.redis.passwordFile})'
450 ${lib.optionalString (cfg.smtp.passwordFile != null) ''
452 password: '$(cat ${cfg.smtp.passwordFile})'
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
467 WorkingDirectory = cfg.package;
468 SyslogIdentifier = "peertube";
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;
481 EnvironmentFile = cfg.serviceEnvironmentFile;
483 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ];
484 MemoryDenyWriteExecute = false;
485 # System Call Filtering
486 SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ];
490 services.nginx = lib.mkIf cfg.configureNginx {
492 upstreams."peertube".servers = {
493 "127.0.0.1:${toString cfg.listenHttp}".fail_timeout = "0";
495 virtualHosts."${cfg.localDomain}" = {
496 root = "/var/lib/peertube/www";
500 tryFiles = "/dev/null @api";
504 locations."~ ^/api/v1/videos/(upload-resumable|([^/]+/source/replace-resumable))$" = {
505 tryFiles = "/dev/null @api";
509 client_max_body_size 0;
510 proxy_request_buffering off;
511 '' + nginxCommonHeaders;
514 locations."~ ^/api/v1/videos/(upload|([^/]+/studio/edit))$" = {
515 tryFiles = "/dev/null @api";
516 root = cfg.settings.storage.tmp;
520 limit_except POST HEAD { deny all; }
522 client_max_body_size 12G;
523 add_header X-File-Maximum-Size 8G always;
524 '' + nginxCommonHeaders;
527 locations."~ ^/api/v1/runners/jobs/[^/]+/(update|success)$" = {
528 tryFiles = "/dev/null @api";
529 root = cfg.settings.storage.tmp;
533 client_max_body_size 12G;
534 add_header X-File-Maximum-Size 8G always;
535 '' + nginxCommonHeaders;
538 locations."~ ^/api/v1/(videos|video-playlists|video-channels|users/me)" = {
539 tryFiles = "/dev/null @api";
543 client_max_body_size 6M;
544 add_header X-File-Maximum-Size 4M always;
545 '' + nginxCommonHeaders;
549 proxyPass = "http://peertube";
553 proxy_set_header Host $host;
554 proxy_set_header X-Real-IP $remote_addr;
555 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
557 proxy_connect_timeout 10m;
559 proxy_send_timeout 10m;
560 proxy_read_timeout 10m;
562 client_max_body_size 100k;
564 ''+ nginxCommonHeaders;
568 locations."/socket.io" = {
569 tryFiles = "/dev/null @api_websocket";
573 locations."/tracker/socket" = {
574 tryFiles = "/dev/null @api_websocket";
578 proxy_read_timeout 15m;
582 locations."~ ^/plugins/[^/]+(/[^/]+)?/ws/" = {
583 tryFiles = "/dev/null @api_websocket";
587 locations."@api_websocket" = {
588 proxyPass = "http://peertube";
592 proxy_http_version 1.1;
593 proxy_set_header Upgrade $http_upgrade;
594 proxy_set_header Connection 'upgrade';
595 proxy_set_header Host $host;
596 proxy_set_header X-Real-IP $remote_addr;
597 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
599 '' + nginxCommonHeaders;
602 # Bypass PeerTube for performance reasons.
603 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))$" = {
604 tryFiles = "/client-overrides/$1 /client/$1 $1";
607 extraConfig = nginxCommonHeaders;
610 locations."~ ^/client/(.*\.(js|css|png|svg|woff2|otf|ttf|woff|eot))$" = {
611 alias = "${cfg.package}/client/dist/$1";
614 add_header Cache-Control 'public, max-age=604800, immutable';
615 '' + nginxCommonHeaders;
618 locations."^~ /download/" = {
619 proxyPass = "http://peertube";
622 proxy_set_header Host $host;
623 proxy_set_header X-Real-IP $remote_addr;
624 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
627 '' + nginxCommonHeaders;
630 locations."^~ /static/streaming-playlists/hls/private/" = {
631 proxyPass = "http://peertube";
634 proxy_set_header Host $host;
635 proxy_set_header X-Real-IP $remote_addr;
636 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
639 '' + nginxCommonHeaders;
642 locations."^~ /static/web-videos/private/" = {
643 proxyPass = "http://peertube";
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;
651 '' + nginxCommonHeaders;
654 locations."^~ /static/webseed/private/" = {
655 proxyPass = "http://peertube";
658 proxy_set_header Host $host;
659 proxy_set_header X-Real-IP $remote_addr;
660 proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
663 '' + nginxCommonHeaders;
666 locations."^~ /static/redundancy/" = {
667 tryFiles = "$uri @api";
668 root = cfg.settings.storage.redundancy;
671 set $peertube_limit_rate 800k;
673 if ($request_uri ~ -fragmented.mp4$) {
674 set $peertube_limit_rate 5M;
677 if ($request_method = 'OPTIONS') {
678 ${nginxCommonHeaders}
679 ${nginxCommonHeadersExtra}
680 add_header Access-Control-Max-Age 1728000;
681 add_header Content-Type 'text/plain charset=UTF-8';
682 add_header Content-Length 0;
685 if ($request_method = 'GET') {
686 ${nginxCommonHeaders}
687 ${nginxCommonHeadersExtra}
694 sendfile_max_chunk 1M;
696 limit_rate $peertube_limit_rate;
699 rewrite ^/static/redundancy/(.*)$ /$1 break;
703 locations."^~ /static/streaming-playlists/" = {
704 tryFiles = "$uri @api";
705 root = cfg.settings.storage.streaming_playlists;
708 set $peertube_limit_rate 800k;
710 if ($request_uri ~ -fragmented.mp4$) {
711 set $peertube_limit_rate 5M;
714 if ($request_method = 'OPTIONS') {
715 ${nginxCommonHeaders}
716 ${nginxCommonHeadersExtra}
717 add_header Access-Control-Max-Age 1728000;
718 add_header Content-Type 'text/plain charset=UTF-8';
719 add_header Content-Length 0;
722 if ($request_method = 'GET') {
723 ${nginxCommonHeaders}
724 ${nginxCommonHeadersExtra}
731 sendfile_max_chunk 1M;
733 limit_rate $peertube_limit_rate;
736 rewrite ^/static/streaming-playlists/(.*)$ /$1 break;
740 locations."^~ /static/web-videos/" = {
741 tryFiles = "$uri @api";
742 root = cfg.settings.storage.web_videos;
745 set $peertube_limit_rate 800k;
747 if ($request_uri ~ -fragmented.mp4$) {
748 set $peertube_limit_rate 5M;
751 if ($request_method = 'OPTIONS') {
752 ${nginxCommonHeaders}
753 ${nginxCommonHeadersExtra}
754 add_header Access-Control-Max-Age 1728000;
755 add_header Content-Type 'text/plain charset=UTF-8';
756 add_header Content-Length 0;
759 if ($request_method = 'GET') {
760 ${nginxCommonHeaders}
761 ${nginxCommonHeadersExtra}
768 sendfile_max_chunk 1M;
770 limit_rate $peertube_limit_rate;
773 rewrite ^/static/web-videos/(.*)$ /$1 break;
777 locations."^~ /static/webseed/" = {
778 tryFiles = "$uri @api";
779 root = cfg.settings.storage.web_videos;
782 set $peertube_limit_rate 800k;
784 if ($request_uri ~ -fragmented.mp4$) {
785 set $peertube_limit_rate 5M;
788 if ($request_method = 'OPTIONS') {
789 ${nginxCommonHeaders}
790 ${nginxCommonHeadersExtra}
791 add_header Access-Control-Max-Age 1728000;
792 add_header Content-Type 'text/plain charset=UTF-8';
793 add_header Content-Length 0;
796 if ($request_method = 'GET') {
797 ${nginxCommonHeaders}
798 ${nginxCommonHeadersExtra}
805 sendfile_max_chunk 1M;
807 limit_rate $peertube_limit_rate;
810 rewrite ^/static/webseed/(.*)$ /web-videos/$1 break;
816 services.postgresql = lib.mkIf cfg.database.createLocally {
820 services.redis.servers.peertube = lib.mkMerge [
821 (lib.mkIf cfg.redis.createLocally {
824 (lib.mkIf (cfg.redis.createLocally && !cfg.redis.enableUnixSocket) {
826 port = cfg.redis.port;
828 (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) {
829 unixSocket = "/run/redis-peertube/redis.sock";
830 unixSocketPerm = 660;
834 services.postfix = lib.mkIf cfg.smtp.createLocally {
836 hostname = lib.mkDefault "${cfg.localDomain}";
839 users.users = lib.mkMerge [
840 (lib.mkIf (cfg.user == "peertube") {
847 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ peertubeEnv pkgs.nodejs_18 pkgs.yarn pkgs.ffmpeg-headless ])
848 (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis-peertube" ];})
853 members = lib.optional cfg.configureNginx config.services.nginx.user;