import path from "node:path"; import { fileURLToPath } from "node:url"; import dotenv from "dotenv"; const __dirname = path.dirname(fileURLToPath(import.meta.url)); const backendRoot = path.resolve(__dirname, "../.."); const repoRoot = path.resolve(backendRoot, ".."); dotenv.config({ path: path.join(repoRoot, ".env"), quiet: true }); dotenv.config({ path: path.join(backendRoot, ".env"), override: true, quiet: true }); function stringEnv(name, fallback = "") { const value = process.env[name]; return value && value.trim().length > 0 ? value.trim() : fallback; } function numberEnv(name, fallback) { const value = Number(process.env[name]); return Number.isFinite(value) ? value : fallback; } function boolEnv(name, fallback = false) { const value = process.env[name]; if (value === undefined) return fallback; return ["1", "true", "yes", "on"].includes(value.toLowerCase()); } function firstString(names, fallback = "") { for (const name of names) { const value = stringEnv(name); if (value) return value; } return fallback; } function normalizeBaseUrl(value) { return value.replace(/\/+$/, ""); } const serviceHost = stringEnv("ORPHION_SERVICE_HOST", "127.0.0.1"); const whisperVmIp = firstString(["WHISPER_VM_IP", "WHISPER_HOST"], serviceHost); const whisperVmPort = numberEnv("WHISPER_VM_PORT", numberEnv("WHISPER_PORT", 8000)); const whisperApiUrl = normalizeBaseUrl( firstString(["WHISPER_API_URL"], `http://${whisperVmIp}:${whisperVmPort}`), ); export const env = { appName: "Orphion", nodeEnv: stringEnv("NODE_ENV", "development"), isProduction: stringEnv("NODE_ENV") === "production", host: stringEnv("HOST", "127.0.0.1"), port: numberEnv("PORT", 4000), backendRoot, repoRoot, apiPrefix: "/api/v1", clientOrigin: stringEnv("CLIENT_ORIGIN", "http://localhost:5173"), trustProxy: boolEnv("TRUST_PROXY", false), requestBodyLimit: stringEnv("REQUEST_BODY_LIMIT", "2mb"), database: { host: firstString(["DB_HOST", "MYSQL_HOST"], serviceHost), port: numberEnv("DB_PORT", numberEnv("MYSQL_PORT", 3306)), name: firstString(["DB_NAME", "MYSQL_DATABASE"], "orphion"), user: firstString(["DB_USER", "MYSQL_USER"], "root"), password: firstString(["DB_PASSWORD", "MYSQL_PASSWORD"], ""), connectionLimit: numberEnv("DB_CONNECTION_LIMIT", numberEnv("MYSQL_CONNECTION_LIMIT", 10)), autoMigrate: boolEnv("DB_AUTO_MIGRATE", boolEnv("MYSQL_AUTO_MIGRATE", true)), }, auth: { accessTokenSecret: firstString(["JWT_ACCESS_SECRET", "JWT_SECRET"], "replace-access-secret"), refreshTokenSecret: firstString( ["JWT_REFRESH_SECRET"], "replace-refresh-secret-with-a-different-long-value", ), accessTokenTtl: stringEnv("JWT_ACCESS_EXPIRES_IN", stringEnv("JWT_EXPIRES_IN", "15m")), refreshTokenTtl: stringEnv("JWT_REFRESH_EXPIRES_IN", "30d"), accessCookieName: stringEnv("JWT_COOKIE_NAME", "orphion_access_token"), refreshCookieName: stringEnv("JWT_REFRESH_COOKIE_NAME", "orphion_refresh_token"), cookieSecure: boolEnv("JWT_COOKIE_SECURE", false), cookieSameSite: stringEnv("JWT_COOKIE_SAME_SITE", "lax"), }, upload: { tempDir: path.resolve(backendRoot, stringEnv("UPLOAD_TEMP_DIR", ".tmp/uploads")), maxAudioSizeMb: numberEnv("MAX_AUDIO_SIZE_MB", 200), allowedMimeTypes: stringEnv( "AUDIO_ALLOWED_MIME_TYPES", "audio/webm,audio/wav,audio/mpeg,audio/mp4,audio/ogg,audio/x-m4a,video/webm", ) .split(",") .map((item) => item.trim()) .filter(Boolean), }, storage: { driver: stringEnv("STORAGE_DRIVER", "smb"), basePath: path.resolve( backendRoot, stringEnv("STORAGE_BASE_PATH", stringEnv("REMOTE_STORAGE_PATH", "../storage/audio")), ), publicBaseUrl: normalizeBaseUrl(stringEnv("STORAGE_PUBLIC_BASE_URL", "")), httpBaseUrl: normalizeBaseUrl(stringEnv("STORAGE_HTTP_BASE_URL", "")), httpToken: stringEnv("STORAGE_HTTP_TOKEN", ""), s3: { endpoint: stringEnv("S3_ENDPOINT", ""), region: stringEnv("S3_REGION", "us-east-1"), bucket: stringEnv("S3_BUCKET", ""), accessKeyId: stringEnv("S3_ACCESS_KEY_ID", ""), secretAccessKey: stringEnv("S3_SECRET_ACCESS_KEY", ""), forcePathStyle: boolEnv("S3_FORCE_PATH_STYLE", true), }, }, whisper: { vmIp: whisperVmIp, vmPort: whisperVmPort, apiUrl: whisperApiUrl, transcribePath: stringEnv("WHISPER_TRANSCRIBE_PATH", "/transcribe"), healthPath: stringEnv("WHISPER_HEALTH_PATH", "/health"), fileField: stringEnv("WHISPER_FILE_FIELD", "file"), timeoutMs: numberEnv("WHISPER_TIMEOUT_MS", 900000), retries: numberEnv("WHISPER_RETRIES", 2), retryDelayMs: numberEnv("WHISPER_RETRY_DELAY_MS", 1500), modelName: stringEnv("WHISPER_MODEL_NAME", "faster-whisper-large-v3"), allowMock: boolEnv("WHISPER_ALLOW_MOCK", false), queueConcurrency: numberEnv("TRANSCRIPTION_QUEUE_CONCURRENCY", 1), }, rateLimit: { windowMs: numberEnv("RATE_LIMIT_WINDOW_MS", 15 * 60 * 1000), max: numberEnv("RATE_LIMIT_MAX", 300), authMax: numberEnv("AUTH_RATE_LIMIT_MAX", 30), }, };