/* The hub's configuration, read from the environment. `parseConfig` is pure so the tests can feed it any environment; `loadConfig` is the one that reads `process.env`. */ export const HUB_VERSION = '1.0.0' export interface HubServer { id: string url: string token: string } export interface HubConfig { servers: HubServer[] user: string password: string port: number cacheSeconds: number timeoutMs: number /** Shown as `dnsServerDomain` (and `clusterDomain`) in the session. */ domain: string /** The console's static build. */ staticDir: string } type Env = Record /* `ndns` -> `NDNS_TOKEN`, `my-dns.1` -> `MY_DNS_1_TOKEN`. */ export function tokenVariable(id: string): string { return `${id.toUpperCase().replace(/[^A-Z0-9]/g, '_')}_TOKEN` } function number(env: Env, name: string, fallback: number, min: number): number { const raw = env[name] if (raw === undefined || raw.trim() === '') return fallback const value = Number(raw) if (!Number.isFinite(value) || value < min) throw new Error(`${name} must be a number of at least ${min}.`) return value } export function parseConfig(env: Env): HubConfig { const user = env.HUB_USER ?? '' const password = env.HUB_PASSWORD ?? '' if (user === '' || password === '') throw new Error('HUB_USER and HUB_PASSWORD are required.') const servers: HubServer[] = [] for (const part of (env.HUB_SERVERS ?? '').split(',')) { const entry = part.trim() if (entry === '') continue const at = entry.indexOf('=') const id = at < 0 ? '' : entry.slice(0, at).trim() const url = at < 0 ? '' : entry.slice(at + 1).trim().replace(/\/+$/, '') if (!/^[A-Za-z0-9._-]+$/.test(id) || id === 'cluster' || !/^https?:\/\/[^/]/.test(url)) { throw new Error(`HUB_SERVERS entry "${entry}" is not of the form id=https://host (id: letters, digits, . _ -).`) } if (servers.some((s) => s.id === id)) throw new Error(`HUB_SERVERS lists "${id}" twice.`) const variable = tokenVariable(id) const token = env[variable] ?? '' if (token === '') throw new Error(`${variable} is required for the server "${id}".`) servers.push({ id, url, token }) } if (servers.length === 0) { throw new Error('HUB_SERVERS is required, e.g. dns=https://dns.example.net,ndns=https://ndns.example.net.') } return { servers, user, password, port: number(env, 'HUB_PORT', 8080, 0), cacheSeconds: number(env, 'HUB_CACHE_SECONDS', 15, 0), timeoutMs: number(env, 'HUB_TIMEOUT_MS', 8000, 1), domain: env.HUB_DOMAIN?.trim() || 'hub', staticDir: env.HUB_STATIC?.trim() || './dist', } } export function loadConfig(): HubConfig { return parseConfig(process.env) }