99 lines
3.1 KiB
JavaScript
99 lines
3.1 KiB
JavaScript
// Live-reloading config layer. Mirrors Recap's config.js pattern: read
|
|
// /data/config/relay-config.json on every access (filesystem watcher
|
|
// pulls in StartOS-action changes without a daemon restart), parse,
|
|
// and expose typed accessors.
|
|
//
|
|
// All defaults match the schema in startos/file-models/config.json.ts.
|
|
|
|
import fs from "fs/promises";
|
|
import path from "path";
|
|
|
|
let dataDir = "/data";
|
|
let cached = { mtimeMs: 0, snapshot: defaultConfig() };
|
|
|
|
function defaultConfig() {
|
|
return {
|
|
relay_gemini_api_key: "",
|
|
relay_parakeet_base_url: "",
|
|
relay_gemma_base_url: "",
|
|
relay_keysat_base_url: "https://keysat.xyz",
|
|
relay_admin_username: "",
|
|
relay_admin_password_hash: "",
|
|
relay_admin_password_salt: "",
|
|
relay_admin_session_secret: "",
|
|
relay_tier_quotas_json: JSON.stringify({
|
|
core: { lifetime: 5, monthly: null, geminiCapMonthly: null },
|
|
pro: { lifetime: null, monthly: 50, geminiCapMonthly: 25 },
|
|
max: { lifetime: null, monthly: null, geminiCapMonthly: 50 },
|
|
}),
|
|
};
|
|
}
|
|
|
|
function configPath() {
|
|
return path.join(dataDir, "config", "relay-config.json");
|
|
}
|
|
|
|
export async function initConfig({ dataDir: dd }) {
|
|
if (dd) dataDir = dd;
|
|
await fs.mkdir(path.dirname(configPath()), { recursive: true }).catch(() => {});
|
|
// Prime the cache so the first request doesn't pay for a file-read.
|
|
await getConfigSnapshot();
|
|
}
|
|
|
|
// Reads the on-disk config and merges with defaults. Cheap — single
|
|
// stat + read per call, but the result is cached until the file mtime
|
|
// changes so repeat callers within one request don't re-read.
|
|
export async function getConfigSnapshot() {
|
|
const p = configPath();
|
|
let stat;
|
|
try {
|
|
stat = await fs.stat(p);
|
|
} catch {
|
|
return cached.snapshot;
|
|
}
|
|
if (stat.mtimeMs === cached.mtimeMs) return cached.snapshot;
|
|
try {
|
|
const raw = await fs.readFile(p, "utf8");
|
|
const parsed = JSON.parse(raw);
|
|
cached = {
|
|
mtimeMs: stat.mtimeMs,
|
|
snapshot: { ...defaultConfig(), ...parsed },
|
|
};
|
|
} catch (err) {
|
|
console.warn(`[config] failed to parse ${p}: ${err?.message}`);
|
|
}
|
|
return cached.snapshot;
|
|
}
|
|
|
|
// Parsed view of relay_tier_quotas_json, with safe fallbacks if the
|
|
// blob is missing or malformed.
|
|
export async function getTierQuotas() {
|
|
const cfg = await getConfigSnapshot();
|
|
try {
|
|
const parsed = JSON.parse(cfg.relay_tier_quotas_json);
|
|
return {
|
|
core: {
|
|
lifetime: parsed?.core?.lifetime ?? 5,
|
|
monthly: parsed?.core?.monthly ?? null,
|
|
geminiCapMonthly: parsed?.core?.geminiCapMonthly ?? null,
|
|
},
|
|
pro: {
|
|
lifetime: parsed?.pro?.lifetime ?? null,
|
|
monthly: parsed?.pro?.monthly ?? 50,
|
|
geminiCapMonthly: parsed?.pro?.geminiCapMonthly ?? 25,
|
|
},
|
|
max: {
|
|
lifetime: parsed?.max?.lifetime ?? null,
|
|
monthly: parsed?.max?.monthly ?? null,
|
|
geminiCapMonthly: parsed?.max?.geminiCapMonthly ?? 50,
|
|
},
|
|
};
|
|
} catch {
|
|
return {
|
|
core: { lifetime: 5, monthly: null, geminiCapMonthly: null },
|
|
pro: { lifetime: null, monthly: 50, geminiCapMonthly: 25 },
|
|
max: { lifetime: null, monthly: null, geminiCapMonthly: 50 },
|
|
};
|
|
}
|
|
}
|