| import { z, ZodError } from "zod"; |
| import { parse } from "@std/toml"; |
|
|
| export const ConfigSchema = z.object({ |
| server: z.object({ |
| port: z.number().default(Number(Deno.env.get("PORT")) || 8282), |
| host: z.string().default(Deno.env.get("HOST") || "127.0.0.1"), |
| use_unix_socket: z.boolean().default( |
| Deno.env.get("SERVER_USE_UNIX_SOCKET") === "true" || false, |
| ), |
| unix_socket_path: z.string().default( |
| Deno.env.get("SERVER_UNIX_SOCKET_PATH") || |
| "/tmp/invidious-companion.sock", |
| ), |
| base_path: z.string() |
| .default(Deno.env.get("SERVER_BASE_PATH") || "/companion") |
| .refine( |
| (path) => path.startsWith("/"), |
| { |
| message: |
| "SERVER_BASE_PATH must start with a forward slash (/). Example: '/companion'", |
| }, |
| ) |
| .refine( |
| (path) => !path.endsWith("/") || path === "/", |
| { |
| message: |
| "SERVER_BASE_PATH must not end with a forward slash (/) unless it's the root path. Example: '/companion' not '/companion/'", |
| }, |
| ) |
| .refine( |
| (path) => !path.includes("//"), |
| { |
| message: |
| "SERVER_BASE_PATH must not contain double slashes (//). Example: '/companion' not '//companion' or '/comp//anion'", |
| }, |
| ), |
| secret_key: z.preprocess( |
| (val) => |
| val === undefined |
| ? Deno.env.get("SERVER_SECRET_KEY") || "" |
| : val, |
| z.string().min(1), |
| ).default(undefined), |
| verify_requests: z.boolean().default( |
| Deno.env.get("SERVER_VERIFY_REQUESTS") === "true" || false, |
| ), |
| encrypt_query_params: z.boolean().default( |
| Deno.env.get("SERVER_ENCRYPT_QUERY_PARAMS") === "true" || false, |
| ), |
| enable_metrics: z.boolean().default( |
| Deno.env.get("SERVER_ENABLE_METRICS") === "true" || false, |
| ), |
| }).strict().default({}), |
| cache: z.object({ |
| enabled: z.boolean().default( |
| Deno.env.get("CACHE_ENABLED") === "false" ? false : true, |
| ), |
| directory: z.string().default( |
| Deno.env.get("CACHE_DIRECTORY") || "/var/tmp", |
| ), |
| }).strict().default({}), |
| networking: z.object({ |
| proxy: z.string().nullable().default(Deno.env.get("PROXY") || null), |
| auto_proxy: z.boolean().default( |
| Deno.env.get("NETWORKING_AUTO_PROXY") === "true" || false, |
| ), |
| vpn_source: z.number().default( |
| Number(Deno.env.get("NETWORKING_VPN_SOURCE")) || 1, |
| ), |
| ipv6_block: z.string().nullable().default( |
| Deno.env.get("NETWORKING_IPV6_BLOCK") || null, |
| ), |
| fetch: z.object({ |
| timeout_ms: z.number().default( |
| Number(Deno.env.get("NETWORKING_FETCH_TIMEOUT_MS")) || 30_000, |
| ), |
| retry: z.object({ |
| enabled: z.boolean().default( |
| Deno.env.get("NETWORKING_FETCH_RETRY_ENABLED") === "true" || |
| false, |
| ), |
| times: z.number().optional().default( |
| Number(Deno.env.get("NETWORKING_FETCH_RETRY_TIMES")) || 1, |
| ), |
| initial_debounce: z.number().optional().default( |
| Number( |
| Deno.env.get("NETWORKING_FETCH_RETRY_INITIAL_DEBOUNCE"), |
| ) || 0, |
| ), |
| debounce_multiplier: z.number().optional().default( |
| Number( |
| Deno.env.get( |
| "NETWORKING_FETCH_RETRY_DEBOUNCE_MULTIPLIER", |
| ), |
| ) || 0, |
| ), |
| }).strict().default({}), |
| }).strict().default({}), |
| videoplayback: z.object({ |
| ump: z.boolean().default( |
| Deno.env.get("NETWORKING_VIDEOPLAYBACK_UMP") === "true" || |
| false, |
| ), |
| video_fetch_chunk_size_mb: z.number().default( |
| Number( |
| Deno.env.get( |
| "NETWORKING_VIDEOPLAYBACK_VIDEO_FETCH_CHUNK_SIZE_MB", |
| ), |
| ) || 5, |
| ), |
| }).strict().default({}), |
| }).strict().default({}), |
| jobs: z.object({ |
| youtube_session: z.object({ |
| po_token_enabled: z.boolean().default( |
| Deno.env.get("JOBS_YOUTUBE_SESSION_PO_TOKEN_ENABLED") === |
| "false" |
| ? false |
| : true, |
| ), |
| frequency: z.string().default( |
| Deno.env.get("JOBS_YOUTUBE_SESSION_FREQUENCY") || "*/5 * * * *", |
| ), |
| }).strict().default({}), |
| }).strict().default({}), |
| youtube_session: z.object({ |
| oauth_enabled: z.boolean().default( |
| Deno.env.get("YOUTUBE_SESSION_OAUTH_ENABLED") === "true" || false, |
| ), |
| cookies: z.string().default( |
| Deno.env.get("YOUTUBE_SESSION_COOKIES") || "", |
| ), |
| }).strict().default({}), |
| }).strict(); |
|
|
| export type Config = z.infer<typeof ConfigSchema>; |
|
|
| export async function parseConfig() { |
| const configFileName = Deno.env.get("CONFIG_FILE") || "config/config.toml"; |
| const configFileContents = await Deno.readTextFile(configFileName).catch( |
| () => null, |
| ); |
| if (configFileContents) { |
| console.log("[INFO] Using custom settings local file"); |
| } else { |
| console.log( |
| "[INFO] No local config file found, using default config", |
| ); |
| } |
|
|
| try { |
| const rawConfig = configFileContents ? parse(configFileContents) : {}; |
| const validatedConfig = ConfigSchema.parse(rawConfig); |
|
|
| console.log("Loaded Configuration", validatedConfig); |
|
|
| return validatedConfig; |
| } catch (err) { |
| let errorMessage = |
| "There is an error in your configuration, check your environment variables"; |
| if (configFileContents) { |
| errorMessage += |
| ` or in your configuration file located at ${configFileName}`; |
| } |
| console.log(errorMessage); |
| if (err instanceof ZodError) { |
| console.log(err.issues); |
| |
| const detailedMessage = err.issues.map((issue) => |
| `${issue.path.join(".")}: ${issue.message}` |
| ).join("; "); |
| throw new Error( |
| `Failed to parse configuration file: ${detailedMessage}`, |
| ); |
| } |
| |
| throw err; |
| } |
| } |
|
|