feat: Runtime-Config via Diagnostic UI — kein .env-Sync mehr

Framework fuer zentrale Runtime-Konfiguration:
- /api/runtime-config (GET/POST) persistiert in /shared/config/runtime.json
- Werte haben Vorrang ueber die ENV-Variablen aus aria.env
- Feldliste: RVS_HOST/PORT/TLS/TOKEN, ARIA_AUTH_TOKEN, WHISPER_MODEL/LANGUAGE
- Atomic write (tmp + rename) fuer Konsistenz

Bridge:
- load_config() liest nach aria.env noch runtime.json und ueberschreibt
  die Werte. Aenderungen werden beim Neustart der Bridge uebernommen.

Diagnostic UI:
- Neue Sektion "Runtime-Konfiguration" in Einstellungen
- Formular fuer RVS-Credentials + Aria-Auth-Token
- "Speichern" persistiert, triggert auch QR-Code-Regenerierung
- Hinweis: Diagnostic-Container selbst bleibt auf ENV (erstmal)

issue.md konsolidiert — 6 groessere Tasks dieser Session als erledigt.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-19 16:18:37 +02:00
parent b203503fd8
commit fc3bee6d05
4 changed files with 166 additions and 1 deletions
+55
View File
@@ -58,6 +58,41 @@ let activeSessionKey = (() => {
return "main";
})();
// ── Runtime-Config: /shared/config/runtime.json ─────────────
// ENV-Werte sind Defaults; Werte aus runtime.json haben Vorrang.
// Bridge und ggf. andere Komponenten lesen dieselbe Datei.
const RUNTIME_CONFIG_FILE = "/shared/config/runtime.json";
const RUNTIME_CONFIG_FIELDS = [
"RVS_HOST", "RVS_PORT", "RVS_TLS", "RVS_TOKEN",
"ARIA_AUTH_TOKEN", "WHISPER_MODEL", "WHISPER_LANGUAGE",
];
function readRuntimeConfig() {
const envDefaults = {
RVS_HOST, RVS_PORT, RVS_TLS, RVS_TOKEN,
ARIA_AUTH_TOKEN: process.env.ARIA_AUTH_TOKEN || "",
WHISPER_MODEL: process.env.WHISPER_MODEL || "medium",
WHISPER_LANGUAGE: process.env.WHISPER_LANGUAGE || "de",
};
try {
const raw = fs.readFileSync(RUNTIME_CONFIG_FILE, "utf-8");
const parsed = JSON.parse(raw);
return { ...envDefaults, ...parsed };
} catch {
return envDefaults;
}
}
function writeRuntimeConfig(patch) {
let current = {};
try { current = JSON.parse(fs.readFileSync(RUNTIME_CONFIG_FILE, "utf-8")); } catch {}
for (const key of Object.keys(patch)) {
if (RUNTIME_CONFIG_FIELDS.includes(key)) current[key] = patch[key];
}
fs.mkdirSync("/shared/config", { recursive: true });
const tmp = RUNTIME_CONFIG_FILE + ".tmp";
fs.writeFileSync(tmp, JSON.stringify(current, null, 2));
fs.renameSync(tmp, RUNTIME_CONFIG_FILE);
}
// Atomic write: temp-file + rename, laute Logs bei Fehler.
function persistActiveSession(key) {
try {
@@ -1169,6 +1204,26 @@ const server = http.createServer((req, res) => {
} else if (req.url === "/api/session") {
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ sessionKey: activeSessionKey }));
} else if (req.url === "/api/runtime-config" && req.method === "GET") {
// Zentrale Runtime-Config (ENV + Override aus /shared/config/runtime.json)
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(readRuntimeConfig()));
} else if (req.url === "/api/runtime-config" && req.method === "POST") {
let body = "";
req.on("data", chunk => { body += chunk; if (body.length > 32768) req.destroy(); });
req.on("end", () => {
try {
const patch = JSON.parse(body);
writeRuntimeConfig(patch);
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: true, config: readRuntimeConfig() }));
log("info", "server", `Runtime-Config aktualisiert: ${Object.keys(patch).join(", ")}`);
} catch (err) {
res.writeHead(400, { "Content-Type": "application/json" });
res.end(JSON.stringify({ ok: false, error: err.message }));
}
});
return;
} else if (req.url === "/api/onboarding") {
// RVS-Credentials fuer QR-Code App-Onboarding
res.writeHead(200, { "Content-Type": "application/json" });