import path from "node:path"; export interface AppConfig { host: string; port: number; harnessDir: string; thtBin: string; piBin: string; authMode: "none" | "mock" | "upstream"; defaults: { provider?: string; model?: string; thinking?: string }; maxPiProcesses: number; settingsFile: string; dataRoot?: string; ollamaEnsureTimeoutMs: number; secretsFile?: string; secretFiles: Readonly>; modelApiKeyFile?: string; } export function loadConfig(env: Record): AppConfig { const authMode = env.AUTH_MODE ?? "none"; if (!(["none", "mock", "upstream"] as const).includes(authMode as AppConfig["authMode"])) { throw new Error(`unsupported AUTH_MODE=${authMode}; use none, mock, or upstream`); } if (env.THOTH_PUBLIC_EXPOSURE === "true" && authMode !== "upstream") { throw new Error("public exposure requires AUTH_MODE=upstream behind a trusted proxy"); } const modelApiKeyFile = env.THT_MODEL_API_KEY_FILE; if (modelApiKeyFile !== undefined && ( modelApiKeyFile.trim() !== modelApiKeyFile || modelApiKeyFile.length === 0 || modelApiKeyFile.includes("\0") || !path.isAbsolute(modelApiKeyFile) )) { throw new Error("model credential configuration is invalid"); } const secretsFile = env.THT_SECRETS_FILE; if (secretsFile !== undefined && ( secretsFile.trim() !== secretsFile || secretsFile.length === 0 || secretsFile.includes("\0") || !path.isAbsolute(secretsFile) )) throw new Error("secret bundle configuration is invalid"); const secretFiles: Record = {}; for (const name of [ "THT_MODEL_API_KEY_SECRET_FILE", "THT_DWH_API_KEY_SECRET_FILE", "THT_VEC_API_KEY_SECRET_FILE", "THT_VEC_WRITE_API_KEY_SECRET_FILE", "THT_CA_SECRET_FILE", "THT_VECTOR_BOOTSTRAP_PASSWORD_SECRET_FILE", "THT_VECTOR_MIGRATOR_PASSWORD_SECRET_FILE", "THT_VECTOR_READER_PASSWORD_SECRET_FILE", "THT_VECTOR_WRITER_PASSWORD_SECRET_FILE", ]) secretFiles[name] = env[name]; return { host: env.HOST ?? "127.0.0.1", port: Number(env.PORT ?? 8787), harnessDir: env.THT_HARNESS_DIR ?? "../harness", thtBin: env.THT_BIN ?? "tht", piBin: env.PI_BIN ?? "pi", authMode: authMode as AppConfig["authMode"], defaults: { provider: env.PI_PROVIDER, model: env.PI_MODEL, thinking: env.PI_THINKING }, maxPiProcesses: Number(env.MAX_PI_PROCESSES ?? 4), settingsFile: env.SETTINGS_FILE ?? "data/settings.json", dataRoot: env.THT_DATA_ROOT, ollamaEnsureTimeoutMs: Number(env.OLLAMA_ENSURE_TIMEOUT_MS ?? 60000), secretsFile, secretFiles, modelApiKeyFile, }; }