From 068cae3986972f5bd6f100bfbf1341d64aff38d3 Mon Sep 17 00:00:00 2001 From: jj Date: Sat, 24 May 2025 14:47:01 +0000 Subject: [PATCH] api: move env loading into separate file --- api/src/config.js | 97 ++---------------------------------------- api/src/core/env.js | 101 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 104 insertions(+), 94 deletions(-) create mode 100644 api/src/core/env.js diff --git a/api/src/config.js b/api/src/config.js index 24cc3b52..e7e29652 100644 --- a/api/src/config.js +++ b/api/src/config.js @@ -1,76 +1,9 @@ -import { Constants } from "youtubei.js"; import { getVersion } from "@imput/version-info"; -import { services } from "./processing/service-config.js"; -import { supportsReusePort } from "./misc/cluster.js"; +import { loadEnvs, validateEnvs } from "./core/env.js"; const version = await getVersion(); -const disabledServices = process.env.DISABLED_SERVICES?.split(',') || []; -const enabledServices = new Set(Object.keys(services).filter(e => { - if (!disabledServices.includes(e)) { - return e; - } -})); - -const forceLocalProcessingOptions = ["never", "session", "always"]; - -const env = { - apiURL: process.env.API_URL || '', - apiPort: process.env.API_PORT || 9000, - tunnelPort: process.env.API_PORT || 9000, - - listenAddress: process.env.API_LISTEN_ADDRESS, - freebindCIDR: process.platform === 'linux' && process.env.FREEBIND_CIDR, - - corsWildcard: process.env.CORS_WILDCARD !== '0', - corsURL: process.env.CORS_URL, - - cookiePath: process.env.COOKIE_PATH, - - rateLimitWindow: (process.env.RATELIMIT_WINDOW && parseInt(process.env.RATELIMIT_WINDOW)) || 60, - rateLimitMax: (process.env.RATELIMIT_MAX && parseInt(process.env.RATELIMIT_MAX)) || 20, - - tunnelRateLimitWindow: (process.env.TUNNEL_RATELIMIT_WINDOW && parseInt(process.env.TUNNEL_RATELIMIT_WINDOW)) || 60, - tunnelRateLimitMax: (process.env.TUNNEL_RATELIMIT_MAX && parseInt(process.env.TUNNEL_RATELIMIT_MAX)) || 40, - - sessionRateLimitWindow: (process.env.SESSION_RATELIMIT_WINDOW && parseInt(process.env.SESSION_RATELIMIT_WINDOW)) || 60, - sessionRateLimit: (process.env.SESSION_RATELIMIT && parseInt(process.env.SESSION_RATELIMIT)) || 10, - - durationLimit: (process.env.DURATION_LIMIT && parseInt(process.env.DURATION_LIMIT)) || 10800, - streamLifespan: (process.env.TUNNEL_LIFESPAN && parseInt(process.env.TUNNEL_LIFESPAN)) || 90, - - processingPriority: process.platform !== 'win32' - && process.env.PROCESSING_PRIORITY - && parseInt(process.env.PROCESSING_PRIORITY), - - externalProxy: process.env.API_EXTERNAL_PROXY, - - turnstileSitekey: process.env.TURNSTILE_SITEKEY, - turnstileSecret: process.env.TURNSTILE_SECRET, - jwtSecret: process.env.JWT_SECRET, - jwtLifetime: process.env.JWT_EXPIRY || 120, - - sessionEnabled: process.env.TURNSTILE_SITEKEY - && process.env.TURNSTILE_SECRET - && process.env.JWT_SECRET, - - apiKeyURL: process.env.API_KEY_URL && new URL(process.env.API_KEY_URL), - authRequired: process.env.API_AUTH_REQUIRED === '1', - redisURL: process.env.API_REDIS_URL, - instanceCount: (process.env.API_INSTANCE_COUNT && parseInt(process.env.API_INSTANCE_COUNT)) || 1, - keyReloadInterval: 900, - - enabledServices, - - customInnertubeClient: process.env.CUSTOM_INNERTUBE_CLIENT, - ytSessionServer: process.env.YOUTUBE_SESSION_SERVER, - ytSessionReloadInterval: 300, - ytSessionInnertubeClient: process.env.YOUTUBE_SESSION_INNERTUBE_CLIENT, - ytAllowBetterAudio: process.env.YOUTUBE_ALLOW_BETTER_AUDIO !== "0", - - // "never" | "session" | "always" - forceLocalProcessing: process.env.FORCE_LOCAL_PROCESSING ?? "never", -} +let env = loadEnvs(); const genericUserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/114.0.0.0 Safari/537.36"; const cobaltUserAgent = `cobalt/${version} (+https://github.com/imputnet/cobalt)`; @@ -78,31 +11,7 @@ const cobaltUserAgent = `cobalt/${version} (+https://github.com/imputnet/cobalt) export const setTunnelPort = (port) => env.tunnelPort = port; export const isCluster = env.instanceCount > 1; -if (env.sessionEnabled && env.jwtSecret.length < 16) { - throw new Error("JWT_SECRET env is too short (must be at least 16 characters long)"); -} - -if (env.instanceCount > 1 && !env.redisURL) { - throw new Error("API_REDIS_URL is required when API_INSTANCE_COUNT is >= 2"); -} else if (env.instanceCount > 1 && !await supportsReusePort()) { - console.error('API_INSTANCE_COUNT is not supported in your environment. to use this env, your node.js'); - console.error('version must be >= 23.1.0, and you must be running a recent enough version of linux'); - console.error('(or other OS that supports it). for more info, see `reusePort` option on'); - console.error('https://nodejs.org/api/net.html#serverlistenoptions-callback'); - throw new Error('SO_REUSEPORT is not supported'); -} - -if (env.customInnertubeClient && !Constants.SUPPORTED_CLIENTS.includes(env.customInnertubeClient)) { - console.error("CUSTOM_INNERTUBE_CLIENT is invalid. Provided client is not supported."); - console.error(`Supported clients are: ${Constants.SUPPORTED_CLIENTS.join(', ')}\n`); - throw new Error("Invalid CUSTOM_INNERTUBE_CLIENT"); -} - -if (env.forceLocalProcessing && !forceLocalProcessingOptions.includes(env.forceLocalProcessing)) { - console.error("FORCE_LOCAL_PROCESSING is invalid."); - console.error(`Supported options are are: ${forceLocalProcessingOptions.join(', ')}\n`); - throw new Error("Invalid FORCE_LOCAL_PROCESSING"); -} +await validateEnvs(env); export { env, diff --git a/api/src/core/env.js b/api/src/core/env.js new file mode 100644 index 00000000..c670447d --- /dev/null +++ b/api/src/core/env.js @@ -0,0 +1,101 @@ + +import { Constants } from "youtubei.js"; +import { supportsReusePort } from "../misc/cluster.js"; +import { services } from "../processing/service-config.js"; + +const forceLocalProcessingOptions = ["never", "session", "always"]; + +export const loadEnvs = (env = process.env) => { + const disabledServices = env.DISABLED_SERVICES?.split(',') || []; + const enabledServices = new Set(Object.keys(services).filter(e => { + if (!disabledServices.includes(e)) { + return e; + } + })); + + return { + apiURL: env.API_URL || '', + apiPort: env.API_PORT || 9000, + tunnelPort: env.API_PORT || 9000, + + listenAddress: env.API_LISTEN_ADDRESS, + freebindCIDR: process.platform === 'linux' && env.FREEBIND_CIDR, + + corsWildcard: env.CORS_WILDCARD !== '0', + corsURL: env.CORS_URL, + + cookiePath: env.COOKIE_PATH, + + rateLimitWindow: (env.RATELIMIT_WINDOW && parseInt(env.RATELIMIT_WINDOW)) || 60, + rateLimitMax: (env.RATELIMIT_MAX && parseInt(env.RATELIMIT_MAX)) || 20, + + tunnelRateLimitWindow: (env.TUNNEL_RATELIMIT_WINDOW && parseInt(env.TUNNEL_RATELIMIT_WINDOW)) || 60, + tunnelRateLimitMax: (env.TUNNEL_RATELIMIT_MAX && parseInt(env.TUNNEL_RATELIMIT_MAX)) || 40, + + sessionRateLimitWindow: (env.SESSION_RATELIMIT_WINDOW && parseInt(env.SESSION_RATELIMIT_WINDOW)) || 60, + sessionRateLimit: (env.SESSION_RATELIMIT && parseInt(env.SESSION_RATELIMIT)) || 10, + + durationLimit: (env.DURATION_LIMIT && parseInt(env.DURATION_LIMIT)) || 10800, + streamLifespan: (env.TUNNEL_LIFESPAN && parseInt(env.TUNNEL_LIFESPAN)) || 90, + + processingPriority: process.platform !== 'win32' + && env.PROCESSING_PRIORITY + && parseInt(env.PROCESSING_PRIORITY), + + externalProxy: env.API_EXTERNAL_PROXY, + + turnstileSitekey: env.TURNSTILE_SITEKEY, + turnstileSecret: env.TURNSTILE_SECRET, + jwtSecret: env.JWT_SECRET, + jwtLifetime: env.JWT_EXPIRY || 120, + + sessionEnabled: env.TURNSTILE_SITEKEY + && env.TURNSTILE_SECRET + && env.JWT_SECRET, + + apiKeyURL: env.API_KEY_URL && new URL(env.API_KEY_URL), + authRequired: env.API_AUTH_REQUIRED === '1', + redisURL: env.API_REDIS_URL, + instanceCount: (env.API_INSTANCE_COUNT && parseInt(env.API_INSTANCE_COUNT)) || 1, + keyReloadInterval: 900, + + enabledServices, + + customInnertubeClient: env.CUSTOM_INNERTUBE_CLIENT, + ytSessionServer: env.YOUTUBE_SESSION_SERVER, + ytSessionReloadInterval: 300, + ytSessionInnertubeClient: env.YOUTUBE_SESSION_INNERTUBE_CLIENT, + ytAllowBetterAudio: env.YOUTUBE_ALLOW_BETTER_AUDIO !== "0", + + // "never" | "session" | "always" + forceLocalProcessing: env.FORCE_LOCAL_PROCESSING ?? "never", + }; +} + +export const validateEnvs = async (env) => { + if (env.sessionEnabled && env.jwtSecret.length < 16) { + throw new Error("JWT_SECRET env is too short (must be at least 16 characters long)"); + } + + if (env.instanceCount > 1 && !env.redisURL) { + throw new Error("API_REDIS_URL is required when API_INSTANCE_COUNT is >= 2"); + } else if (env.instanceCount > 1 && !await supportsReusePort()) { + console.error('API_INSTANCE_COUNT is not supported in your environment. to use this env, your node.js'); + console.error('version must be >= 23.1.0, and you must be running a recent enough version of linux'); + console.error('(or other OS that supports it). for more info, see `reusePort` option on'); + console.error('https://nodejs.org/api/net.html#serverlistenoptions-callback'); + throw new Error('SO_REUSEPORT is not supported'); + } + + if (env.customInnertubeClient && !Constants.SUPPORTED_CLIENTS.includes(env.customInnertubeClient)) { + console.error("CUSTOM_INNERTUBE_CLIENT is invalid. Provided client is not supported."); + console.error(`Supported clients are: ${Constants.SUPPORTED_CLIENTS.join(', ')}\n`); + throw new Error("Invalid CUSTOM_INNERTUBE_CLIENT"); + } + + if (env.forceLocalProcessing && !forceLocalProcessingOptions.includes(env.forceLocalProcessing)) { + console.error("FORCE_LOCAL_PROCESSING is invalid."); + console.error(`Supported options are are: ${forceLocalProcessingOptions.join(', ')}\n`); + throw new Error("Invalid FORCE_LOCAL_PROCESSING"); + } +}