at 22.05-pre 15 kB view raw
1{ lib, pkgs, config, ... }: 2 3let 4 cfg = config.services.peertube; 5 6 settingsFormat = pkgs.formats.json {}; 7 configFile = settingsFormat.generate "production.json" cfg.settings; 8 9 env = { 10 NODE_CONFIG_DIR = "/var/lib/peertube/config"; 11 NODE_ENV = "production"; 12 NODE_EXTRA_CA_CERTS = "/etc/ssl/certs/ca-certificates.crt"; 13 NPM_CONFIG_PREFIX = cfg.package; 14 HOME = cfg.package; 15 }; 16 17 systemCallsList = [ "@cpu-emulation" "@debug" "@keyring" "@ipc" "@memlock" "@mount" "@obsolete" "@privileged" "@setuid" ]; 18 19 cfgService = { 20 # Proc filesystem 21 ProcSubset = "pid"; 22 ProtectProc = "invisible"; 23 # Access write directories 24 UMask = "0027"; 25 # Capabilities 26 CapabilityBoundingSet = ""; 27 # Security 28 NoNewPrivileges = true; 29 # Sandboxing 30 ProtectSystem = "strict"; 31 ProtectHome = true; 32 PrivateTmp = true; 33 PrivateDevices = true; 34 PrivateUsers = true; 35 ProtectClock = true; 36 ProtectHostname = true; 37 ProtectKernelLogs = true; 38 ProtectKernelModules = true; 39 ProtectKernelTunables = true; 40 ProtectControlGroups = true; 41 RestrictNamespaces = true; 42 LockPersonality = true; 43 RestrictRealtime = true; 44 RestrictSUIDSGID = true; 45 RemoveIPC = true; 46 PrivateMounts = true; 47 # System Call Filtering 48 SystemCallArchitectures = "native"; 49 }; 50 51 envFile = pkgs.writeText "peertube.env" (lib.concatMapStrings (s: s + "\n") ( 52 (lib.concatLists (lib.mapAttrsToList (name: value: 53 if value != null then [ 54 "${name}=\"${toString value}\"" 55 ] else [] 56 ) env)))); 57 58 peertubeEnv = pkgs.writeShellScriptBin "peertube-env" '' 59 set -a 60 source "${envFile}" 61 eval -- "\$@" 62 ''; 63 64 peertubeCli = pkgs.writeShellScriptBin "peertube" '' 65 node ~/dist/server/tools/peertube.js $@ 66 ''; 67 68in { 69 options.services.peertube = { 70 enable = lib.mkEnableOption "Enable Peertubes service"; 71 72 user = lib.mkOption { 73 type = lib.types.str; 74 default = "peertube"; 75 description = "User account under which Peertube runs."; 76 }; 77 78 group = lib.mkOption { 79 type = lib.types.str; 80 default = "peertube"; 81 description = "Group under which Peertube runs."; 82 }; 83 84 localDomain = lib.mkOption { 85 type = lib.types.str; 86 example = "peertube.example.com"; 87 description = "The domain serving your PeerTube instance."; 88 }; 89 90 listenHttp = lib.mkOption { 91 type = lib.types.int; 92 default = 9000; 93 description = "listen port for HTTP server."; 94 }; 95 96 listenWeb = lib.mkOption { 97 type = lib.types.int; 98 default = 9000; 99 description = "listen port for WEB server."; 100 }; 101 102 enableWebHttps = lib.mkOption { 103 type = lib.types.bool; 104 default = false; 105 description = "Enable or disable HTTPS protocol."; 106 }; 107 108 dataDirs = lib.mkOption { 109 type = lib.types.listOf lib.types.path; 110 default = [ ]; 111 example = [ "/opt/peertube/storage" "/var/cache/peertube" ]; 112 description = "Allow access to custom data locations."; 113 }; 114 115 serviceEnvironmentFile = lib.mkOption { 116 type = lib.types.nullOr lib.types.path; 117 default = null; 118 example = "/run/keys/peertube/password-init-root"; 119 description = '' 120 Set environment variables for the service. Mainly useful for setting the initial root password. 121 For example write to file: 122 PT_INITIAL_ROOT_PASSWORD=changeme 123 ''; 124 }; 125 126 settings = lib.mkOption { 127 type = settingsFormat.type; 128 example = lib.literalExpression '' 129 { 130 listen = { 131 hostname = "0.0.0.0"; 132 }; 133 log = { 134 level = "debug"; 135 }; 136 storage = { 137 tmp = "/opt/data/peertube/storage/tmp/"; 138 logs = "/opt/data/peertube/storage/logs/"; 139 cache = "/opt/data/peertube/storage/cache/"; 140 }; 141 } 142 ''; 143 description = "Configuration for peertube."; 144 }; 145 146 database = { 147 createLocally = lib.mkOption { 148 type = lib.types.bool; 149 default = false; 150 description = "Configure local PostgreSQL database server for PeerTube."; 151 }; 152 153 host = lib.mkOption { 154 type = lib.types.str; 155 default = if cfg.database.createLocally then "/run/postgresql" else null; 156 example = "192.168.15.47"; 157 description = "Database host address or unix socket."; 158 }; 159 160 port = lib.mkOption { 161 type = lib.types.int; 162 default = 5432; 163 description = "Database host port."; 164 }; 165 166 name = lib.mkOption { 167 type = lib.types.str; 168 default = "peertube"; 169 description = "Database name."; 170 }; 171 172 user = lib.mkOption { 173 type = lib.types.str; 174 default = "peertube"; 175 description = "Database user."; 176 }; 177 178 passwordFile = lib.mkOption { 179 type = lib.types.nullOr lib.types.path; 180 default = null; 181 example = "/run/keys/peertube/password-posgressql-db"; 182 description = "Password for PostgreSQL database."; 183 }; 184 }; 185 186 redis = { 187 createLocally = lib.mkOption { 188 type = lib.types.bool; 189 default = false; 190 description = "Configure local Redis server for PeerTube."; 191 }; 192 193 host = lib.mkOption { 194 type = lib.types.nullOr lib.types.str; 195 default = if cfg.redis.createLocally && !cfg.redis.enableUnixSocket then "127.0.0.1" else null; 196 description = "Redis host."; 197 }; 198 199 port = lib.mkOption { 200 type = lib.types.nullOr lib.types.port; 201 default = if cfg.redis.createLocally && cfg.redis.enableUnixSocket then null else 6379; 202 description = "Redis port."; 203 }; 204 205 passwordFile = lib.mkOption { 206 type = lib.types.nullOr lib.types.path; 207 default = null; 208 example = "/run/keys/peertube/password-redis-db"; 209 description = "Password for redis database."; 210 }; 211 212 enableUnixSocket = lib.mkOption { 213 type = lib.types.bool; 214 default = cfg.redis.createLocally; 215 description = "Use Unix socket."; 216 }; 217 }; 218 219 smtp = { 220 createLocally = lib.mkOption { 221 type = lib.types.bool; 222 default = false; 223 description = "Configure local Postfix SMTP server for PeerTube."; 224 }; 225 226 passwordFile = lib.mkOption { 227 type = lib.types.nullOr lib.types.path; 228 default = null; 229 example = "/run/keys/peertube/password-smtp"; 230 description = "Password for smtp server."; 231 }; 232 }; 233 234 package = lib.mkOption { 235 type = lib.types.package; 236 default = pkgs.peertube; 237 description = "Peertube package to use."; 238 }; 239 }; 240 241 config = lib.mkIf cfg.enable { 242 assertions = [ 243 { assertion = cfg.serviceEnvironmentFile == null || !lib.hasPrefix builtins.storeDir cfg.serviceEnvironmentFile; 244 message = '' 245 <option>services.peertube.serviceEnvironmentFile</option> points to 246 a file in the Nix store. You should use a quoted absolute path to 247 prevent this. 248 ''; 249 } 250 { assertion = !(cfg.redis.enableUnixSocket && (cfg.redis.host != null || cfg.redis.port != null)); 251 message = '' 252 <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. 253 ''; 254 } 255 { assertion = cfg.redis.enableUnixSocket || (cfg.redis.host != null && cfg.redis.port != null); 256 message = '' 257 <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. 258 ''; 259 } 260 { assertion = cfg.redis.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.redis.passwordFile; 261 message = '' 262 <option>services.peertube.redis.passwordFile</option> points to 263 a file in the Nix store. You should use a quoted absolute path to 264 prevent this. 265 ''; 266 } 267 { assertion = cfg.database.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.database.passwordFile; 268 message = '' 269 <option>services.peertube.database.passwordFile</option> points to 270 a file in the Nix store. You should use a quoted absolute path to 271 prevent this. 272 ''; 273 } 274 { assertion = cfg.smtp.passwordFile == null || !lib.hasPrefix builtins.storeDir cfg.smtp.passwordFile; 275 message = '' 276 <option>services.peertube.smtp.passwordFile</option> points to 277 a file in the Nix store. You should use a quoted absolute path to 278 prevent this. 279 ''; 280 } 281 ]; 282 283 services.peertube.settings = lib.mkMerge [ 284 { 285 listen = { 286 port = cfg.listenHttp; 287 }; 288 webserver = { 289 https = (if cfg.enableWebHttps then true else false); 290 hostname = "${cfg.localDomain}"; 291 port = cfg.listenWeb; 292 }; 293 database = { 294 hostname = "${cfg.database.host}"; 295 port = cfg.database.port; 296 name = "${cfg.database.name}"; 297 username = "${cfg.database.user}"; 298 }; 299 redis = { 300 hostname = "${toString cfg.redis.host}"; 301 port = (if cfg.redis.port == null then "" else cfg.redis.port); 302 }; 303 storage = { 304 tmp = lib.mkDefault "/var/lib/peertube/storage/tmp/"; 305 avatars = lib.mkDefault "/var/lib/peertube/storage/avatars/"; 306 videos = lib.mkDefault "/var/lib/peertube/storage/videos/"; 307 streaming_playlists = lib.mkDefault "/var/lib/peertube/storage/streaming-playlists/"; 308 redundancy = lib.mkDefault "/var/lib/peertube/storage/redundancy/"; 309 logs = lib.mkDefault "/var/lib/peertube/storage/logs/"; 310 previews = lib.mkDefault "/var/lib/peertube/storage/previews/"; 311 thumbnails = lib.mkDefault "/var/lib/peertube/storage/thumbnails/"; 312 torrents = lib.mkDefault "/var/lib/peertube/storage/torrents/"; 313 captions = lib.mkDefault "/var/lib/peertube/storage/captions/"; 314 cache = lib.mkDefault "/var/lib/peertube/storage/cache/"; 315 plugins = lib.mkDefault "/var/lib/peertube/storage/plugins/"; 316 client_overrides = lib.mkDefault "/var/lib/peertube/storage/client-overrides/"; 317 }; 318 } 319 (lib.mkIf cfg.redis.enableUnixSocket { redis = { socket = "/run/redis/redis.sock"; }; }) 320 ]; 321 322 systemd.tmpfiles.rules = [ 323 "d '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -" 324 "z '/var/lib/peertube/config' 0700 ${cfg.user} ${cfg.group} - -" 325 ]; 326 327 systemd.services.peertube-init-db = lib.mkIf cfg.database.createLocally { 328 description = "Initialization database for PeerTube daemon"; 329 after = [ "network.target" "postgresql.service" ]; 330 wantedBy = [ "multi-user.target" ]; 331 332 script = let 333 psqlSetupCommands = pkgs.writeText "peertube-init.sql" '' 334 SELECT 'CREATE USER "${cfg.database.user}"' WHERE NOT EXISTS (SELECT FROM pg_roles WHERE rolname = '${cfg.database.user}')\gexec 335 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 336 \c '${cfg.database.name}' 337 CREATE EXTENSION IF NOT EXISTS pg_trgm; 338 CREATE EXTENSION IF NOT EXISTS unaccent; 339 ''; 340 in "${config.services.postgresql.package}/bin/psql -f ${psqlSetupCommands}"; 341 342 serviceConfig = { 343 Type = "oneshot"; 344 WorkingDirectory = cfg.package; 345 # User and group 346 User = "postgres"; 347 Group = "postgres"; 348 # Sandboxing 349 RestrictAddressFamilies = [ "AF_UNIX" ]; 350 MemoryDenyWriteExecute = true; 351 # System Call Filtering 352 SystemCallFilter = "~" + lib.concatStringsSep " " (systemCallsList ++ [ "@resources" ]); 353 } // cfgService; 354 }; 355 356 systemd.services.peertube = { 357 description = "PeerTube daemon"; 358 after = [ "network.target" ] 359 ++ lib.optionals cfg.redis.createLocally [ "redis.service" ] 360 ++ lib.optionals cfg.database.createLocally [ "postgresql.service" "peertube-init-db.service" ]; 361 wantedBy = [ "multi-user.target" ]; 362 363 environment = env; 364 365 path = with pkgs; [ bashInteractive ffmpeg nodejs-16_x openssl yarn youtube-dl ]; 366 367 script = '' 368 #!/bin/sh 369 umask 077 370 cat > /var/lib/peertube/config/local.yaml <<EOF 371 ${lib.optionalString ((!cfg.database.createLocally) && (cfg.database.passwordFile != null)) '' 372 database: 373 password: '$(cat ${cfg.database.passwordFile})' 374 ''} 375 ${lib.optionalString (cfg.redis.passwordFile != null) '' 376 redis: 377 auth: '$(cat ${cfg.redis.passwordFile})' 378 ''} 379 ${lib.optionalString (cfg.smtp.passwordFile != null) '' 380 smtp: 381 password: '$(cat ${cfg.smtp.passwordFile})' 382 ''} 383 EOF 384 ln -sf ${cfg.package}/config/default.yaml /var/lib/peertube/config/default.yaml 385 ln -sf ${configFile} /var/lib/peertube/config/production.json 386 npm start 387 ''; 388 serviceConfig = { 389 Type = "simple"; 390 Restart = "always"; 391 RestartSec = 20; 392 TimeoutSec = 60; 393 WorkingDirectory = cfg.package; 394 # User and group 395 User = cfg.user; 396 Group = cfg.group; 397 # State directory and mode 398 StateDirectory = "peertube"; 399 StateDirectoryMode = "0750"; 400 # Access write directories 401 ReadWritePaths = cfg.dataDirs; 402 # Environment 403 EnvironmentFile = cfg.serviceEnvironmentFile; 404 # Sandboxing 405 RestrictAddressFamilies = [ "AF_UNIX" "AF_INET" "AF_INET6" "AF_NETLINK" ]; 406 MemoryDenyWriteExecute = false; 407 # System Call Filtering 408 SystemCallFilter = [ ("~" + lib.concatStringsSep " " systemCallsList) "pipe" "pipe2" ]; 409 } // cfgService; 410 }; 411 412 services.postgresql = lib.mkIf cfg.database.createLocally { 413 enable = true; 414 }; 415 416 services.redis = lib.mkMerge [ 417 (lib.mkIf cfg.redis.createLocally { 418 enable = true; 419 }) 420 (lib.mkIf (cfg.redis.createLocally && cfg.redis.enableUnixSocket) { 421 unixSocket = "/run/redis/redis.sock"; 422 unixSocketPerm = 770; 423 }) 424 ]; 425 426 services.postfix = lib.mkIf cfg.smtp.createLocally { 427 enable = true; 428 hostname = lib.mkDefault "${cfg.localDomain}"; 429 }; 430 431 users.users = lib.mkMerge [ 432 (lib.mkIf (cfg.user == "peertube") { 433 peertube = { 434 isSystemUser = true; 435 group = cfg.group; 436 home = cfg.package; 437 }; 438 }) 439 (lib.attrsets.setAttrByPath [ cfg.user "packages" ] [ cfg.package peertubeEnv peertubeCli pkgs.ffmpeg pkgs.nodejs-16_x pkgs.yarn ]) 440 (lib.mkIf cfg.redis.enableUnixSocket {${config.services.peertube.user}.extraGroups = [ "redis" ];}) 441 ]; 442 443 users.groups = lib.optionalAttrs (cfg.group == "peertube") { 444 peertube = { }; 445 }; 446 }; 447}