import { readFileSync } from "node:fs"; import { parse } from "yaml"; import type { SshHostConfig } from "../collectors/sshHost.js"; interface RawSshHost { id: string; displayName: string; group: string; host: string; port?: number; username: string; diskPaths?: { path: string; label: string }[]; } interface RawKnownDevice { ip: string; name: string; } interface RawDeepCheck { host: string; port?: number; username?: string; } interface RawHomeAssistant { url: string; } export interface HostsConfig { proxmox: { host: string; node: string; }; sshHosts?: RawSshHost[]; knownDevices?: RawKnownDevice[]; deepCheck?: RawDeepCheck; homeAssistant?: RawHomeAssistant; } export interface AppConfig { port: number; dbPath: string; pollIntervalSeconds: number; snapshotRetentionHours: number; sessionSecret: string; // Only set once a TLS-terminating reverse proxy sits in front (see issue #13) — // browsers silently drop `secure` cookies over plain HTTP. cookieSecure: boolean; adminUsername: string; adminPassword: string | undefined; proxmox: { host: string; node: string; tokenId: string; tokenSecret: string; }; sshHosts: SshHostConfig[]; knownDevices: Map; discoveryFilePath: string; oidc: OidcConfig | undefined; deepCheck: DeepCheckHostConfig | undefined; fingerbankApiKey: string | undefined; homeAssistant: HomeAssistantConfig | undefined; hosts: HostsConfig; } export interface HomeAssistantConfig { url: string; token: string; } export interface DeepCheckHostConfig { host: string; port: number; username: string; privateKeyPath: string; } export interface OidcConfig { issuerUrl: string; clientId: string; clientSecret: string; redirectUri: string; // Authentik's cert is self-signed on the LAN, same as Proxmox's. allowInsecureTls: boolean; } function required(name: string): string { const v = process.env[name]; if (!v) throw new Error(`Missing required env var: ${name}`); return v; } export function loadConfig(hostsConfigPath: string): AppConfig { const hosts = parse(readFileSync(hostsConfigPath, "utf8")) as HostsConfig; const sshPrivateKeyPath = process.env.SSH_PRIVATE_KEY_PATH ?? "./ssh/monitor_ed25519"; // Local auth (bcrypt + session) is always available. OIDC is an *additional* // sign-in option when configured, not a replacement — see issue #12 comments // for why (don't want to lock out the already-saved local admin password). const oidc: OidcConfig | undefined = process.env.OIDC_ENABLED === "true" ? { issuerUrl: required("OIDC_ISSUER_URL"), clientId: required("OIDC_CLIENT_ID"), clientSecret: required("OIDC_CLIENT_SECRET"), redirectUri: required("OIDC_REDIRECT_URI"), allowInsecureTls: process.env.OIDC_ALLOW_INSECURE_TLS === "true", } : undefined; const sshHosts: SshHostConfig[] = (hosts.sshHosts ?? []).map((h) => ({ id: h.id, displayName: h.displayName, group: h.group, host: h.host, port: h.port ?? 22, username: h.username, privateKeyPath: sshPrivateKeyPath, diskPaths: h.diskPaths ?? [{ path: "/", label: "root" }], })); return { port: Number(process.env.PORT ?? 3000), dbPath: process.env.DB_PATH ?? "./data/monitor.db", pollIntervalSeconds: Number(process.env.POLL_INTERVAL_SECONDS ?? 30), snapshotRetentionHours: Number(process.env.SNAPSHOT_RETENTION_HOURS ?? 24), sessionSecret: required("SESSION_SECRET"), cookieSecure: process.env.COOKIE_SECURE === "true", adminUsername: process.env.ADMIN_USERNAME ?? "admin", adminPassword: process.env.ADMIN_PASSWORD, proxmox: { host: hosts.proxmox.host, node: hosts.proxmox.node, tokenId: required("PROXMOX_TOKEN_ID"), tokenSecret: required("PROXMOX_TOKEN_SECRET"), }, sshHosts, knownDevices: new Map((hosts.knownDevices ?? []).map((d) => [d.ip, d.name])), discoveryFilePath: process.env.DISCOVERY_FILE_PATH ?? "./data/devices-raw.json", oidc, deepCheck: hosts.deepCheck ? { host: hosts.deepCheck.host, port: hosts.deepCheck.port ?? 22, username: hosts.deepCheck.username ?? "root", privateKeyPath: sshPrivateKeyPath, } : undefined, fingerbankApiKey: process.env.FINGERBANK_API_KEY, homeAssistant: hosts.homeAssistant && process.env.HOME_ASSISTANT_TOKEN ? { url: hosts.homeAssistant.url, token: process.env.HOME_ASSISTANT_TOKEN } : undefined, hosts, }; }