import type { IUptimeRunnerConfig } from './interfaces.ts'; export const DEFAULT_CONFIG_PATH = '/etc/uptimerunner/config.json'; export async function loadConfig(configPathArg = getConfigPath()): Promise { const fileConfig = await readConfigFile(configPathArg); const config = applyEnvironmentOverrides(fileConfig); validateConfig(config); return config; } export async function writeConfig( configArg: IUptimeRunnerConfig, configPathArg = getConfigPath(), ): Promise { validateConfig(configArg); const configDir = configPathArg.slice(0, configPathArg.lastIndexOf('/')) || '.'; await Deno.mkdir(configDir, { recursive: true }); await Deno.writeTextFile(`${configPathArg}.tmp`, `${JSON.stringify(configArg, null, 2)}\n`); await Deno.rename(`${configPathArg}.tmp`, configPathArg); } export function getConfigPath(): string { return Deno.env.get('UPTIMERUNNER_CONFIG') || DEFAULT_CONFIG_PATH; } export function applyEnvironmentOverrides(configArg: Partial) { const labels = Deno.env.get('UPTIMERUNNER_LABELS'); const pollIntervalMs = Deno.env.get('UPTIMERUNNER_POLL_INTERVAL_MS'); const maxConcurrentChecks = Deno.env.get('UPTIMERUNNER_MAX_CONCURRENT_CHECKS'); return { ...configArg, instanceUrl: Deno.env.get('UPTIMERUNNER_INSTANCE_URL') || configArg.instanceUrl, runnerId: Deno.env.get('UPTIMERUNNER_RUNNER_ID') || configArg.runnerId, token: Deno.env.get('UPTIMERUNNER_TOKEN') || configArg.token, labels: labels ? labels.split(',').map((labelArg) => labelArg.trim()).filter(Boolean) : configArg.labels, pollIntervalMs: pollIntervalMs ? Number(pollIntervalMs) : configArg.pollIntervalMs, maxConcurrentChecks: maxConcurrentChecks ? Number(maxConcurrentChecks) : configArg.maxConcurrentChecks, } satisfies Partial; } export function validateConfig( configArg: Partial, ): asserts configArg is IUptimeRunnerConfig { if (!configArg.instanceUrl) { throw new Error('Missing instanceUrl. Set it in config or UPTIMERUNNER_INSTANCE_URL.'); } if (!configArg.runnerId) { throw new Error('Missing runnerId. Set it in config or UPTIMERUNNER_RUNNER_ID.'); } if (!configArg.token) { throw new Error('Missing token. Set it in config or UPTIMERUNNER_TOKEN.'); } if (configArg.pollIntervalMs !== undefined && configArg.pollIntervalMs < 1000) { throw new Error('pollIntervalMs must be at least 1000ms.'); } if (configArg.maxConcurrentChecks !== undefined && configArg.maxConcurrentChecks < 1) { throw new Error('maxConcurrentChecks must be at least 1.'); } } async function readConfigFile(configPathArg: string): Promise> { try { const configText = await Deno.readTextFile(configPathArg); return JSON.parse(configText) as Partial; } catch (error) { if (error instanceof Deno.errors.NotFound) { return {}; } throw error; } }