{ lib, pkgs, config, options, ... }: let cfg = config.services.peertube; opt = options.services.peertube; settingsFormat = pkgs.formats.json {}; configFile = settingsFormat.generate "production.json" cfg.settings; env = { NODE_CONFIG_DIR = "/var/lib/peertube/config"; NODE_ENV = "production"; NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt"; NPM_CONFIG_CACHE = "/var/cache/peertube/.npm"; NPM_CONFIG_PREFIX = cfg.package; HOME = cfg.package; }; systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ]; cfgService = { # Proc filesystem ProcSubset = "pid"; ProtectProc = "invisible"; # Access write directories UMask = "0027"; # Capabilities CapabilityBoundingSet = ""; # Security NoNewPrivileges = true; # Sandboxing ProtectSystem = "strict"; ProtectHome = true; PrivateTmp = true; PrivateDevices = true; PrivateUsers = true; ProtectClock = true; ProtectHostname = true; ProtectKernelLogs = true; ProtectKernelModules = true; ProtectKernelTunables = true; ProtectControlGroups = true; RestrictNamespaces = true; LockPersonality = true; RestrictRealtime = true; RestrictSUIDSGID = true; RemoveIPC = true; PrivateMounts = true; # System Call Filtering SystemCallArchitectures = "native"; }; envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") ( (lib.concatLists (lib.mapAttrsToList (name: value: lib.optional (value != null) ''${name}="${toString value}"'' ) env)))); peertubeEnv = pkgs.writeShellScriptBin "peertube-env" '' set -a source "${envFile}" eval -- "\$@" ''; peertubeCli = pkgs.writeShellScriptBin "peertube" '' 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 (lib.mdDoc "Peertube"); user = lib.mkOption { type = lib.types.str; default = "peertube"; description = lib.mdDoc "User account under which Peertube runs."; }; group = lib.mkOption { type = lib.types.str; default = "peertube"; description = lib.mdDoc "Group under which Peertube runs."; }; localDomain = lib.mkOption { type = lib.types.str; example = "peertube.example.com"; description = lib.mdDoc "The domain serving your PeerTube instance."; }; listenHttp = lib.mkOption { type = lib.types.port; default = 9000; description = lib.mdDoc "The port that the local PeerTube web server will listen on."; }; listenWeb = lib.mkOption { type = lib.types.port; default = 9000; description = lib.mdDoc "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."; }; enableWebHttps = lib.mkOption { type = lib.types.bool; default = false; description = lib.mdDoc "Whether clients will access your PeerTube instance with HTTPS. Does NOT configure the PeerTube webserver itself to listen for incoming HTTPS connections."; }; dataDirs = lib.mkOption { type = lib.types.listOf lib.types.path; default = [ ]; example = [ "/opt/peertube/storage" "/var/cache/peertube" ]; description = lib.mdDoc "Allow access to custom data locations."; }; serviceEnvironmentFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; example = "/run/keys/peertube/password-init-root"; description = lib.mdDoc '' Set environment variables for the service. Mainly useful for setting the initial root password. For example write to file: PT_INITIAL_ROOT_PASSWORD=changeme ''; }; settings = lib.mkOption { type = settingsFormat.type; example = lib.literalExpression '' { listen = { hostname = "0.0.0.0"; }; log = { level = "debug"; }; storage = { tmp = "/opt/data/peertube/storage/tmp/"; logs = "/opt/data/peertube/storage/logs/"; cache = "/opt/data/peertube/storage/cache/"; }; } ''; 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; default = false; description = lib.mdDoc "Configure local PostgreSQL database server for PeerTube."; }; host = lib.mkOption { type = lib.types.str; default = if cfg.database.createLocally then "/run/postgresql" else null; defaultText = lib.literalExpression '' if config.${opt.database.createLocally} then "/run/postgresql" else null ''; example = "192.168.15.47"; description = lib.mdDoc "Database host address or unix socket."; }; port = lib.mkOption { type = lib.types.port; default = 5432; description = lib.mdDoc "Database host port."; }; name = lib.mkOption { type = lib.types.str; default = "peertube"; description = lib.mdDoc "Database name."; }; user = lib.mkOption { type = lib.types.str; default = "peertube"; description = lib.mdDoc "Database user."; }; passwordFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; example = "/run/keys/peertube/password-postgresql"; description = lib.mdDoc "Password for PostgreSQL database."; }; }; redis = { createLocally = lib.mkOption { type = lib.types.bool; default = false; description = lib.mdDoc "Configure local Redis server for PeerTube."; }; host = lib.mkOption { type = lib.types.nullOr lib.types.str; default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null; defaultText = lib.literalExpression '' if config.${opt.redis.createLocally} && !config.${opt.redis.enableUnixSocket} then "127.0.0.1" else null ''; description = lib.mdDoc "Redis host."; }; port = lib.mkOption { type = lib.types.nullOr lib.types.port; default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 31638; defaultText = lib.literalExpression '' if config.${opt.redis.createLocally} && config.${opt.redis.enableUnixSocket} then null else 6379 ''; description = lib.mdDoc "Redis port."; }; passwordFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; example = "/run/keys/peertube/password-redis-db"; description = lib.mdDoc "Password for redis database."; }; enableUnixSocket = lib.mkOption { type = lib.types.bool; default = cfg.redis.createLocally; defaultText = lib.literalExpression "config.${opt.redis.createLocally}"; description = lib.mdDoc "Use Unix socket."; }; }; smtp = { createLocally = lib.mkOption { type = lib.types.bool; default = false; description = lib.mdDoc "Configure local Postfix SMTP server for PeerTube."; }; passwordFile = lib.mkOption { type = lib.types.nullOr lib.types.path; default = null; example = "/run/keys/peertube/password-smtp"; description = lib.mdDoc "Password for smtp server."; }; }; package = lib.mkOption { type = lib.types.package; default = pkgs.peertube; defaultText = lib.literalExpression "pkgs.peertube"; description = lib.mdDoc "PeerTube package to use."; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile; message = '' points to a file in the Nix store. You should use a quoted absolute path to prevent this. ''; } { assertion = cfg.secrets.secretsFile != null; message = '' needs to be set. ''; } { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null)); message = '' and redis network connection ( or ) enabled. Disable either of them. ''; } { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null); message = '' and needs to be set if is not enabled. ''; } { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile; message = '' points to a file in the Nix store. You should use a quoted absolute path to prevent this. ''; } { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile; message = '' points to a file in the Nix store. You should use a quoted absolute path to prevent this. ''; } { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile; message = '' points to a file in the Nix store. You should use a quoted absolute path to prevent this. ''; } ]; services.peertube.settings = lib.mkMerge [ { listen = { port = cfg.listenHttp; }; webserver = { https = (if cfg.enableWebHttps then true else false); hostname = "${cfg.localDomain}"; port = cfg.listenWeb; }; database = { hostname = "${cfg.database.host}"; port = cfg.database.port; name = "${cfg.database.name}"; username = "${cfg.database.user}"; }; redis = { hostname = "${toString cfg.redis.host}"; port = (lib.optionalString (cfg.redis.port != null) cfg.redis.port); }; storage = { tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/"; tmp_persistent = lib.mkDefault "/var/lib/peertube/storage/tmp_persistent/"; bin = lib.mkDefault "/var/lib/peertube/storage/bin/"; avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/"; videos = lib.mkDefault "/var/lib/peertube/storage/videos/"; streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/"; redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/"; logs = lib.mkDefault "/var/lib/peertube/storage/logs/"; previews = lib.mkDefault "/var/lib/peertube/storage/previews/"; thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/"; torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/"; 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 = { videos = { http = { youtube_dl_release = { python_path = "${pkgs.python3}/bin/python"; }; }; }; }; } (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis-peertube/redis.sock"; }; }) ]; 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" ]; requires = [ "postgresql.service" ]; script = let psqlSetupCommands = pkgs.writeText "peertube-init.sql" '' SELECT 'CREATE USER "${cfg.database.user}"' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${cfg.database.user}')\gexec 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 \c '${cfg.database.name}' CREATE EXTENSION IF NOT EXISTS pg_trgm; CREATE EXTENSION IF NOT EXISTS unaccent; ''; in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}"; serviceConfig = { Type = "oneshot"; WorkingDirectory = cfg.package; # User and group User = "postgres"; Group = "postgres"; # Sandboxing RestrictAddressFamilies = [ "AF_UNIX" ]; MemoryDenyWriteExecute = true; # System Call Filtering SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]); } // cfgService; }; systemd.services.peertube = { description = "PeerTube daemon"; after = [ "network.target" ] ++ 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_18 openssl yarn python3 ]; script = '' #!/bin/sh umask 077 cat > /var/lib/peertube/config/local.yaml <