{ config, lib, pkgs, ... }: let inherit (lib) types; inherit (lib.modules) mkIf; inherit (lib.options) mkOption mkEnableOption mkPackageOption; cfg = config.services.piped; in { options.services.piped = { enable = mkEnableOption "piped"; frontend = { # Enable implies turning on nginx for frontend as it is static files enable = mkOption { type = types.bool; default = cfg.enable; }; package = mkPackageOption pkgs "piped-frontend" {}; domain = mkOption { type = types.str; description = "Domain where the frontend will be displayed"; }; backendDomain = mkOption { type = types.str; default = cfg.backend.domain; description = "Domain of the backend"; }; nginx = { forceSSL = mkOption { type = types.bool; default = true; }; enableACME = mkOption { type = types.bool; default = true; }; }; }; backend = { enable = mkOption { type = types.bool; default = cfg.enable; }; package = mkPackageOption pkgs "piped-backend" {}; domain = mkOption { type = types.str; description = "Domain used for the backend server"; }; internalPort = mkOption { type = types.number; default = 3001; }; nginx = { disableNginx = mkOption { type = types.bool; default = false; description = "Turn off nginx for proxying backend, use backend.internalPort instead"; }; forceSSL = mkOption { type = types.bool; default = true; }; enableACME = mkOption { type = types.bool; default = true; }; }; database = { disablePostgresDB = mkOption { type = types.bool; default = false; description = "Manually configure a database instead"; }; host = mkOption { type = types.str; default = "127.0.0.1"; description = "Database host"; }; port = mkOption { type = types.number; default = 5432; description = "Database port"; }; name = mkOption { type = types.str; default = "piped"; description = "Database name"; }; username = mkOption { type = types.str; default = "piped"; description = "Database username"; }; usePassword = mkOption { type = types.bool; default = true; description = "Should use database password? If false then use passwordless auth or configure cert-based auth in databaseOptions"; }; password = mkOption { type = types.str; default = ""; description = "Database password (default is to allow passwordless auth for samehost for piped when empty)"; }; passwordFile = mkOption { type = types.nullOr types.path; default = null; description = "Database password file, loaded at runtime"; }; databaseOptions = mkOption { type = types.str; default = ""; description = "Appended to end of "; }; driverClass = mkOption { type = types.str; default = "org.postgresql.Driver"; description = "The driver class to be used for the database connection"; }; dialect = mkOption { type = types.str; default = "org.hibernate.dialect.PostgreSQLDialect"; example = "org.hibernate.dialect.CockroachDialect"; description = "The dialect class to be used for the database connection"; }; }; captcha = { enable = mkEnableOption "piped-backend-captcha"; apiURL = mkOption { type = types.str; default = ""; description = "API URL for Captcha (unknown protocol/standard, upstream project uses api.capmonster.cloud in example config)"; }; apiKey = mkOption { type = types.str; default = ""; description = "API Key for Captcha"; }; apiKeyFile = mkOption { type = types.nullOr types.str; default = null; description = "API Key File for Captcha"; }; }; ryd = { disable = mkOption { type = types.bool; default = false; description = "Disables querying a Return YouTube Dislike server"; }; apiURL = mkOption { type = types.str; default = "https://ryd-proxy.kavin.rocks"; description = "API URL for a Return YouTube Dislike server"; }; }; federation = { # for Piped's Federation Shenanigan # https://github.com/TeamPiped/piped-federation#how-to-join enable = mkEnableOption "piped-backend-federation"; matrixServerAddr = mkOption { type = types.str; default = ""; description = "Matrix server address for federation"; }; matrixToken = mkOption { type = types.str; default = ""; description = "Matrix access token"; }; matrixTokenFile = mkOption { type = types.nullOr types.str; default = null; description = "Matrix access token file"; }; }; settings = { extraSettings = mkOption { type = types.attrs; default = {}; description = "extra settings to pass to config.properties"; }; disableRegistrations = mkOption { type = types.bool; default = false; description = "Disable user registrations"; }; httpWorkers = mkOption { type = types.number; default = 2; description = "Number of workers for HTTP backend tasks"; }; feedRetentionDays = mkOption { type = types.number; default = 30; description = "Days feed is stored for"; }; subscriptionRetentionDays = mkOption { type = types.number; default = 30; description = "Days subscriptions are stored for unauthenticated users"; }; sponsorblockServers = mkOption { type = types.listOf types.str; default = ["https://sponsor.ajay.app" "https://sponsorblock.kavin.rocks"]; description = "Days subscriptions are stored for unauthenticated users"; }; disableLBRYStreams = mkOption { type = types.bool; default = false; description = "Disable showing streams provided by LBRY"; }; enableCompromisedPasswordCheck = mkOption { type = types.bool; default = true; description = "Use the haveibeenpwned API to check if user password have been compromised"; }; sentryDSN = mkOption { type = types.nullOr types.str; default = null; description = "Public DSN for sentry error reporting"; }; }; }; proxy = { enable = mkOption { type = types.bool; default = cfg.enable; }; package = mkPackageOption pkgs "piped-proxy" {}; domain = mkOption { type = types.str; description = "Domain used for the proxy server"; }; internalPort = mkOption { type = types.number; default = 3002; }; disallowImageTranscoding = mkOption { type = types.bool; default = false; description = "turns off transcoding thumbnails/other to webp/avif if client adds those to allowed mime types on image requests; may use a lot of CPU depending on how many users"; }; nginx = { disableNginx = mkOption { type = types.bool; default = false; description = "Turn off nginx for proxying proxy, use proxy.internalPort instead, you will need the relevant extra nginx config in proxy.nix or similar for good performance"; }; forceSSL = mkOption { type = types.bool; default = true; }; enableACME = mkOption { type = types.bool; default = true; }; }; proxyIPv4Only = mkOption { type = types.bool; default = false; description = "Only use IPv4 when querying youtube's servers"; }; }; }; config = mkIf (cfg.enable && (cfg.backend.enable || cfg.proxy.enable)) { assertions = [ { assertion = cfg.backend.captcha.enable && cfg.backend.captcha.apiKey != "" && cfg.backend.captcha.apiKeyFile != null -> cfg.certsDir != null; message = "you must use either of services.piped.backend.captcha.{apiKey,apiKeyFile}"; } ]; users.users."piped" = { isSystemUser = true; group = "piped"; }; users.groups.piped = {}; }; imports = [ ./backend.nix ./frontend.nix ./proxy.nix ]; }