initial relay scaffold
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
// 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 },
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user