From 938a69879569849ab45e06ae38ccf0e0afa28f95 Mon Sep 17 00:00:00 2001 From: Nystik <236107-Nystik@users.noreply.gitlab.com> Date: Sat, 6 Jun 2026 01:16:01 +0200 Subject: [PATCH] add server settings store --- apps/ignis-server/server/config.js | 10 --- apps/ignis-server/server/index.js | 5 +- apps/ignis-server/server/settings.js | 91 ++++++++++++++++++++++++++++ packages/server-core/src/ws.js | 15 +++-- 4 files changed, 104 insertions(+), 17 deletions(-) create mode 100644 apps/ignis-server/server/settings.js diff --git a/apps/ignis-server/server/config.js b/apps/ignis-server/server/config.js index 0538dc8..637cc68 100644 --- a/apps/ignis-server/server/config.js +++ b/apps/ignis-server/server/config.js @@ -75,16 +75,6 @@ module.exports = { vaults = discoverVaults(); return vaults; }, - writeCoalesceMs: - process.env.WRITE_COALESCE_MS !== undefined - ? parseInt(process.env.WRITE_COALESCE_MS) - : 5000, - - wsOrigins: process.env.WS_ORIGINS - ? process.env.WS_ORIGINS.split(",") - .map((s) => s.trim()) - .filter(Boolean) - : null, demoMode: process.env.DEMO_MODE === "true", demoMaxSessions: parseInt(process.env.DEMO_MAX_SESSIONS) || 20, diff --git a/apps/ignis-server/server/index.js b/apps/ignis-server/server/index.js index bc40c64..dc94591 100644 --- a/apps/ignis-server/server/index.js +++ b/apps/ignis-server/server/index.js @@ -3,6 +3,7 @@ const fs = require("fs"); const path = require("path"); const compression = require("compression"); const config = require("./config"); +const settings = require("./settings"); const { getVersion } = require("./version"); const { setupWebSocket, @@ -19,7 +20,7 @@ const { getBundledPluginDirs, } = require("./plugin-system/manager"); const pluginRoutes = require("./routes/plugins"); -writeCoalescer.configure({ writeCoalesceMs: config.writeCoalesceMs }); +writeCoalescer.configure({ writeCoalesceMs: settings.get("writeCoalesceMs") }); const { flushAll } = writeCoalescer; const { setupDemo, wireDemoWebSocket } = require("./demo"); @@ -197,7 +198,7 @@ const server = app.listen(config.port, async () => { const wss = setupWebSocket(server, { getVaultPath: config.getVaultPath, - originAllowlist: config.wsOrigins, + originAllowlist: settings.get("wsOrigins"), }); wireDemoWebSocket(server); diff --git a/apps/ignis-server/server/settings.js b/apps/ignis-server/server/settings.js new file mode 100644 index 0000000..682cd18 --- /dev/null +++ b/apps/ignis-server/server/settings.js @@ -0,0 +1,91 @@ +const fs = require("fs"); +const path = require("path"); +const config = require("./config"); + +// Runtime server settings set through UI. + +const SETTINGS_FILE = path.join(config.dataRoot, "server-settings.json"); + +const DEFAULTS = { + contentCacheBytes: 50 * 1024 * 1024, + inputCacheBytes: 200 * 1024 * 1024, + inputCacheTtlMs: 5 * 60 * 1000, + writeCoalesceMs: 5000, + maxBodyBytes: 50 * 1024 * 1024, + // Empty arrays mean "no restriction": any proxy host, any WS origin. + proxyAllowlist: [], + wsOrigins: [], +}; + +const KEYS = Object.keys(DEFAULTS); + +function parseList(raw) { + return raw + .split(",") + .map((s) => s.trim()) + .filter(Boolean); +} + +function fromEnv() { + const env = {}; + + if (process.env.WRITE_COALESCE_MS !== undefined) { + const n = parseInt(process.env.WRITE_COALESCE_MS, 10); + + if (Number.isFinite(n)) { + env.writeCoalesceMs = n; + } + } + + if (process.env.WS_ORIGINS) { + env.wsOrigins = parseList(process.env.WS_ORIGINS); + } + + return env; +} + +const envOverrides = fromEnv(); + +function loadFile() { + try { + const parsed = JSON.parse(fs.readFileSync(SETTINGS_FILE, "utf-8")); + // Keep only known keys so a stale or hand-edited file can't inject junk. + const clean = {}; + + for (const key of KEYS) { + if (parsed[key] !== undefined) { + clean[key] = parsed[key]; + } + } + + return clean; + } catch { + return {}; + } +} + +let fileOverrides = loadFile(); + +function getAll() { + return { ...DEFAULTS, ...envOverrides, ...fileOverrides }; +} + +function get(key) { + return getAll()[key]; +} + +// Merge validated changes into the persisted file and return the new effective settings. +function update(partial) { + for (const [key, value] of Object.entries(partial)) { + if (KEYS.includes(key)) { + fileOverrides[key] = value; + } + } + + fs.mkdirSync(path.dirname(SETTINGS_FILE), { recursive: true }); + fs.writeFileSync(SETTINGS_FILE, JSON.stringify(fileOverrides, null, 2)); + + return getAll(); +} + +module.exports = { DEFAULTS, KEYS, getAll, get, update }; diff --git a/packages/server-core/src/ws.js b/packages/server-core/src/ws.js index 9b42e46..5717de6 100644 --- a/packages/server-core/src/ws.js +++ b/packages/server-core/src/ws.js @@ -2,6 +2,11 @@ const { WebSocketServer } = require("ws"); const url = require("url"); const watcher = require("./watcher"); +// Null / undefined / empty array means no Origin check. +function toOriginSet(list) { + return Array.isArray(list) && list.length > 0 ? new Set(list) : null; +} + function setupWebSocket(server, opts = {}) { const { getVaultPath, originAllowlist } = opts; @@ -9,14 +14,14 @@ function setupWebSocket(server, opts = {}) { throw new Error("setupWebSocket: opts.getVaultPath is required"); } - // Null / undefined / empty array = no Origin check. - const originSet = - Array.isArray(originAllowlist) && originAllowlist.length > 0 - ? new Set(originAllowlist) - : null; + let originSet = toOriginSet(originAllowlist); const wss = new WebSocketServer({ server, path: "/ws" }); + wss.setOriginAllowlist = function (list) { + originSet = toOriginSet(list); + }; + // Global message handlers: type -> handler(msg, ws). wss.messageHandlers = new Map();