diff --git a/nixos/modules/module-list.nix b/nixos/modules/module-list.nix index 16a2906cda31..d4aaa7ebd221 100644 --- a/nixos/modules/module-list.nix +++ b/nixos/modules/module-list.nix @@ -999,6 +999,7 @@ ./services/web-apps/nexus.nix ./services/web-apps/node-red.nix ./services/web-apps/pict-rs.nix + ./services/web-apps/peertube.nix ./services/web-apps/plantuml-server.nix ./services/web-apps/plausible.nix ./services/web-apps/pgpkeyserver-lite.nix diff --git a/nixos/modules/services/web-apps/peertube.nix b/nixos/modules/services/web-apps/peertube.nix new file mode 100644 index 000000000000..362a3358b793 --- /dev/null +++ b/nixos/modules/services/web-apps/peertube.nix @@ -0,0 +1,447 @@ +{ lib, pkgs, config, ... }: + +let + cfg = config.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_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: + if value != null then [ + "${name}=\"${toString value}\"" + ] else [] + ) env)))); + + peertubeEnv = pkgs.writeShellScriptBin "peertube-env" '' + set -a + source "${envFile}" + eval -- "\$@" + ''; + + peertubeCli = pkgs.writeShellScriptBin "peertube" '' + node ~/dist/server/tools/peertube.js $@ + ''; + +in { + options.services.peertube = { + enable = lib.mkEnableOption "Enable Peertube’s service"; + + user = lib.mkOption { + type = lib.types.str; + default = "peertube"; + description = "User account under which Peertube runs."; + }; + + group = lib.mkOption { + type = lib.types.str; + default = "peertube"; + description = "Group under which Peertube runs."; + }; + + localDomain = lib.mkOption { + type = lib.types.str; + example = "peertube.example.com"; + description = "The domain serving your PeerTube instance."; + }; + + listenHttp = lib.mkOption { + type = lib.types.int; + default = 9000; + description = "listen port for HTTP server."; + }; + + listenWeb = lib.mkOption { + type = lib.types.int; + default = 9000; + description = "listen port for WEB server."; + }; + + enableWebHttps = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Enable or disable HTTPS protocol."; + }; + + dataDirs = lib.mkOption { + type = lib.types.listOf lib.types.path; + default = [ ]; + example = [ "/opt/peertube/storage" "/var/cache/peertube" ]; + description = "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 = '' + 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 = "Configuration for peertube."; + }; + + database = { + createLocally = lib.mkOption { + type = lib.types.bool; + default = false; + description = "Configure local PostgreSQL database server for PeerTube."; + }; + + host = lib.mkOption { + type = lib.types.str; + default = if cfg.database.createLocally then "/run/postgresql" else null; + example = "192.168.15.47"; + description = "Database host address or unix socket."; + }; + + port = lib.mkOption { + type = lib.types.int; + default = 5432; + description = "Database host port."; + }; + + name = lib.mkOption { + type = lib.types.str; + default = "peertube"; + description = "Database name."; + }; + + user = lib.mkOption { + type = lib.types.str; + default = "peertube"; + description = "Database user."; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/keys/peertube/password-posgressql-db"; + description = "Password for PostgreSQL database."; + }; + }; + + redis = { + createLocally = lib.mkOption { + type = lib.types.bool; + default = false; + description = "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; + description = "Redis host."; + }; + + port = lib.mkOption { + type = lib.types.nullOr lib.types.port; + default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 6379; + description = "Redis port."; + }; + + passwordFile = lib.mkOption { + type = lib.types.nullOr lib.types.path; + default = null; + example = "/run/keys/peertube/password-redis-db"; + description = "Password for redis database."; + }; + + enableUnixSocket = lib.mkOption { + type = lib.types.bool; + default = cfg.redis.createLocally; + description = "Use Unix socket."; + }; + }; + + smtp = { + createLocally = lib.mkOption { + type = lib.types.bool; + default = false; + description = "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 = "Password for smtp server."; + }; + }; + + package = lib.mkOption { + type = lib.types.package; + default = pkgs.peertube; + description = "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.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 = (if cfg.redis.port == null then "" else cfg.redis.port); + }; + storage = { + tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/"; + 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/"; + client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/"; + }; + } + (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis/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} - -" + ]; + + 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" ]; + + 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.optionals cfg.redis.createLocally [ "redis.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 youtube-dl ]; + + script = '' + #!/bin/sh + umask 077 + cat > /var/lib/peertube/config/local.yaml <