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:
parent
b203503fd8
commit
fc3bee6d05
|
|
@ -105,7 +105,14 @@ EPIC_TRIGGERS = load_epic_triggers()
|
|||
|
||||
|
||||
def load_config() -> dict[str, str]:
|
||||
"""Laedt Konfiguration aus /config/aria.env."""
|
||||
"""Laedt Konfiguration.
|
||||
|
||||
Reihenfolge (hoechste Prioritaet zuletzt):
|
||||
1. /config/aria.env (bind-mount)
|
||||
2. /shared/config/runtime.json (zentral gepflegt ueber Diagnostic UI)
|
||||
|
||||
Werte aus runtime.json ueberschreiben die env-Datei.
|
||||
"""
|
||||
config: dict[str, str] = {}
|
||||
if CONFIG_PATH.exists():
|
||||
for line in CONFIG_PATH.read_text().splitlines():
|
||||
|
|
@ -118,6 +125,18 @@ def load_config() -> dict[str, str]:
|
|||
logger.info("Konfiguration geladen aus %s", CONFIG_PATH)
|
||||
else:
|
||||
logger.warning("Keine Konfiguration gefunden: %s", CONFIG_PATH)
|
||||
|
||||
# Runtime-Overrides aus zentralem Shared-Volume (Diagnostic UI)
|
||||
runtime_path = Path("/shared/config/runtime.json")
|
||||
if runtime_path.exists():
|
||||
try:
|
||||
runtime = json.loads(runtime_path.read_text())
|
||||
overrides = {k: str(v) for k, v in runtime.items() if v not in (None, "")}
|
||||
if overrides:
|
||||
config.update(overrides)
|
||||
logger.info("Runtime-Overrides geladen: %s", sorted(overrides.keys()))
|
||||
except Exception as e:
|
||||
logger.warning("runtime.json konnte nicht gelesen werden: %s", e)
|
||||
return config
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -523,6 +523,39 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Runtime-Konfiguration (migriert von .env) -->
|
||||
<div class="settings-section">
|
||||
<h2>Runtime-Konfiguration</h2>
|
||||
<div style="font-size:11px;color:#8888AA;margin-bottom:8px;">
|
||||
Werte werden in <code>/shared/config/runtime.json</code> persistiert und
|
||||
ueberschreiben die ENV-Variablen aus <code>aria.env</code>. Bridge liest
|
||||
sie beim naechsten Start — nach Aenderung <b>Bridge-Container neu starten</b>
|
||||
(Diagnostic-Container bleibt auf ENV).
|
||||
</div>
|
||||
<div class="card" style="max-width:600px;">
|
||||
<div style="display:grid;grid-template-columns:150px 1fr;gap:8px;align-items:center;font-size:13px;">
|
||||
<label style="color:#8888AA;">RVS Host:</label>
|
||||
<input type="text" id="rc-rvs-host" style="background:#1E1E2E;border:1px solid #2A2A3E;border-radius:4px;padding:6px;color:#fff;">
|
||||
<label style="color:#8888AA;">RVS Port:</label>
|
||||
<input type="text" id="rc-rvs-port" style="background:#1E1E2E;border:1px solid #2A2A3E;border-radius:4px;padding:6px;color:#fff;">
|
||||
<label style="color:#8888AA;">RVS TLS:</label>
|
||||
<select id="rc-rvs-tls" style="background:#1E1E2E;border:1px solid #2A2A3E;border-radius:4px;padding:6px;color:#fff;">
|
||||
<option value="true">true (wss://)</option>
|
||||
<option value="false">false (ws://)</option>
|
||||
</select>
|
||||
<label style="color:#8888AA;">RVS Token:</label>
|
||||
<input type="password" id="rc-rvs-token" style="background:#1E1E2E;border:1px solid #2A2A3E;border-radius:4px;padding:6px;color:#fff;font-family:monospace;">
|
||||
<label style="color:#8888AA;">Aria Auth Token:</label>
|
||||
<input type="password" id="rc-auth-token" style="background:#1E1E2E;border:1px solid #2A2A3E;border-radius:4px;padding:6px;color:#fff;font-family:monospace;">
|
||||
</div>
|
||||
<div style="display:flex;gap:8px;margin-top:12px;">
|
||||
<button class="btn" onclick="saveRuntimeConfig()" style="flex:1;">Speichern</button>
|
||||
<button class="btn secondary" onclick="loadRuntimeConfig()" style="flex:1;">Neu laden</button>
|
||||
</div>
|
||||
<div id="rc-status" style="font-size:11px;color:#555570;margin-top:6px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- App-Onboarding via QR-Code -->
|
||||
<div class="settings-section">
|
||||
<h2>App-Onboarding (QR-Code)</h2>
|
||||
|
|
@ -1465,6 +1498,57 @@
|
|||
send({ action: 'send_voice_config', defaultVoice, highlightVoice, ttsEnabled, speedRamona, speedThorsten, ttsEngine, xttsVoice, whisperModel });
|
||||
}
|
||||
|
||||
// ── Runtime-Konfiguration ─────────────────────
|
||||
async function loadRuntimeConfig() {
|
||||
const statusEl = document.getElementById('rc-status');
|
||||
statusEl.textContent = 'Lade...';
|
||||
try {
|
||||
const resp = await fetch('/api/runtime-config');
|
||||
const cfg = await resp.json();
|
||||
document.getElementById('rc-rvs-host').value = cfg.RVS_HOST || '';
|
||||
document.getElementById('rc-rvs-port').value = cfg.RVS_PORT || '443';
|
||||
document.getElementById('rc-rvs-tls').value = String(cfg.RVS_TLS) === 'false' ? 'false' : 'true';
|
||||
document.getElementById('rc-rvs-token').value = cfg.RVS_TOKEN || '';
|
||||
document.getElementById('rc-auth-token').value = cfg.ARIA_AUTH_TOKEN || '';
|
||||
statusEl.textContent = 'Geladen.';
|
||||
statusEl.style.color = '#34C759';
|
||||
loadOnboardingQR(); // QR bei Config-Wechsel neu generieren
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Fehler: ' + e.message;
|
||||
statusEl.style.color = '#FF6B6B';
|
||||
}
|
||||
}
|
||||
|
||||
async function saveRuntimeConfig() {
|
||||
const statusEl = document.getElementById('rc-status');
|
||||
statusEl.textContent = 'Speichere...';
|
||||
const patch = {
|
||||
RVS_HOST: document.getElementById('rc-rvs-host').value.trim(),
|
||||
RVS_PORT: document.getElementById('rc-rvs-port').value.trim(),
|
||||
RVS_TLS: document.getElementById('rc-rvs-tls').value,
|
||||
RVS_TOKEN: document.getElementById('rc-rvs-token').value.trim(),
|
||||
ARIA_AUTH_TOKEN: document.getElementById('rc-auth-token').value.trim(),
|
||||
};
|
||||
try {
|
||||
const resp = await fetch('/api/runtime-config', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(patch),
|
||||
});
|
||||
const data = await resp.json();
|
||||
if (data.ok) {
|
||||
statusEl.textContent = 'Gespeichert — Bridge-Container fuer Uebernahme neu starten.';
|
||||
statusEl.style.color = '#FFD60A';
|
||||
loadOnboardingQR(); // QR mit neuem Token
|
||||
} else {
|
||||
throw new Error(data.error || 'Unbekannt');
|
||||
}
|
||||
} catch (e) {
|
||||
statusEl.textContent = 'Fehler: ' + e.message;
|
||||
statusEl.style.color = '#FF6B6B';
|
||||
}
|
||||
}
|
||||
|
||||
// ── App-Onboarding QR-Code ────────────────────
|
||||
let qrLibReady = false;
|
||||
function ensureQRLib() {
|
||||
|
|
@ -1991,6 +2075,7 @@
|
|||
if (tab === 'settings') {
|
||||
loadHighlightTriggers();
|
||||
send({ action: 'get_voice_config' });
|
||||
loadRuntimeConfig();
|
||||
loadOnboardingQR();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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" });
|
||||
|
|
|
|||
6
issue.md
6
issue.md
|
|
@ -41,6 +41,12 @@
|
|||
- [x] Gespraechsmodus: Max-Dauer 30s pro Aufnahme, Cache-Cleanup alter Files, Messages-Array gekappt (500)
|
||||
- [x] Diagnostic: Archivierte Session-Versionen (.reset.*) werden angezeigt + exportierbar — OpenClaw resettet Sessions bei erster Nutzung nach Container-Restart, Inhalt ist aber in .reset.<timestamp> Dateien gesichert
|
||||
- [x] tools/export-jsonl-to-md.js: CLI-Konverter fuer beliebige Session-JSONL zu Markdown
|
||||
- [x] NO_REPLY-Filter in Bridge + Diagnostic — still verworfen (kein Chat, kein TTS)
|
||||
- [x] Audio-Ducking + Exklusiv-Focus (Kotlin AudioFocusModule): andere Apps leiser bei TTS, pausiert bei Aufnahme
|
||||
- [x] TTS-Cleanup serverseitig: Code-Bloecke raus, Einheiten ausgeschrieben (22GB → Gigabyte), Abkuerzungen buchstabiert (CPU), URLs zu "ein Link". `<voice></voice>` Tag wird bevorzugt wenn ARIA ihn liefert.
|
||||
- [x] QR-Code Onboarding: Diagnostic generiert QR, App scannt (bestehender QRScanner funktioniert out of the box)
|
||||
- [x] TTS-Audio-Cache im Filesystem: Piper-Audio wird mit messageId verknuepft, als WAV in DocumentDirectory/tts_cache gespeichert, Play-Button spielt aus Cache statt regenerieren
|
||||
- [x] Config via Diagnostic: RVS-Credentials + Aria-Auth-Token via /api/runtime-config, persistiert in /shared/config/runtime.json, Bridge liest beim Start (Overrides der ENV)
|
||||
|
||||
## Offen
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue