import { resolve } from "node:path"; export interface AppConfig { databaseUrl: string; cloudflareApiToken: string; jwtSecret: string; jwtTtlHours: number; adminUsername: string; adminPasswordHash: string; serverPort: number; staticDir: string | null; certCheckCron: string; healthCheckCron: string; healthDegradedFailures: number; healthDownFailures: number; healthSuccessRecoveries: number; healthLatencyWarnMs: number; /** Min pause between probes to different physical targets (same IP is probed once). */ healthProbeGapMs: number; healthWorkerUrl: string; healthWorkerToken: string; logLevel: string; /** Portal SSO — when true, require portal JWT with apps includes cfdm */ authRequired: boolean; authIssuer: string; authPortalUrl: string; /** Bearer secret for POST {authPortalUrl}/api/v1/ingest/audit */ authAuditIngestSecret: string | null; } function boolEnv(v: string | undefined, fallback: boolean): boolean { if (v === undefined || v === "") return fallback; return v === "1" || v.toLowerCase() === "true"; } export function loadConfig(): AppConfig { const isProd = process.env.NODE_ENV === "production"; const jwtSecret = process.env.AUTH_JWT_SECRET ?? process.env.JWT_SECRET ?? (isProd ? "" : "dev-secret-change-me"); return { databaseUrl: process.env.DATABASE_URL ?? "sqlite:data/app.db", cloudflareApiToken: (process.env.CLOUDFLARE_API_TOKEN ?? "").trim(), jwtSecret: jwtSecret || "dev-secret-change-me", jwtTtlHours: Number(process.env.JWT_TTL_HOURS ?? "24") || 24, adminUsername: process.env.ADMIN_USERNAME ?? "admin", adminPasswordHash: process.env.ADMIN_PASSWORD_HASH?.trim() || "devplaceholder", serverPort: Number(process.env.SERVER_PORT ?? "8080") || 8080, staticDir: process.env.STATIC_DIR ? resolve(process.env.STATIC_DIR) : null, certCheckCron: process.env.CERT_CHECK_CRON ?? "0 0 */6 * * *", // Default: every 2 minutes (was every 30s — hammered origins / anti-bot). healthCheckCron: process.env.HEALTH_CHECK_CRON ?? "0 */2 * * * *", healthDegradedFailures: Number(process.env.HEALTH_DEGRADED_FAILURES ?? "1") || 1, healthDownFailures: Number(process.env.HEALTH_DOWN_FAILURES ?? "2") || 2, healthSuccessRecoveries: Number(process.env.HEALTH_SUCCESS_RECOVERIES ?? "2") || 2, healthLatencyWarnMs: Number(process.env.HEALTH_LATENCY_WARN_MS ?? "1000") || 1000, healthProbeGapMs: Number(process.env.HEALTH_PROBE_GAP_MS ?? "2000") || 2000, healthWorkerUrl: (process.env.HEALTH_WORKER_URL ?? "").trim(), healthWorkerToken: (process.env.HEALTH_WORKER_TOKEN ?? "").trim(), logLevel: process.env.LOG_LEVEL ?? "info", authRequired: boolEnv(process.env.AUTH_REQUIRED, false), authIssuer: process.env.AUTH_ISSUER ?? process.env.ISSUER ?? "https://auth.shnt.top", authPortalUrl: ( process.env.AUTH_PORTAL_URL ?? process.env.VITE_AUTH_PORTAL_URL ?? "http://localhost:5175" ).replace(/\/$/, ""), authAuditIngestSecret: process.env.AUTH_AUDIT_INGEST_SECRET?.trim() || (!isProd ? "dev-audit-ingest-secret" : null), }; }