From 81359e229ef0ae35f1dc31316d4ca3594bad166e Mon Sep 17 00:00:00 2001 From: "ARCG Admin (Claude)" Date: Sun, 25 Oct 2026 22:13:04 +0100 Subject: [PATCH] fix(plugin-config): ENV seedet nur fehlende Settings (fill-if-missing) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Problem: In prod überschrieben ensureMicrohamSettings und ensureOpenWebRxBandmapSettings ihre Plugin-Settings bei JEDEM Start aus ENV → UI-Änderungen gingen bei Deploy/Neustart verloren. rms.wavelog.cat hatte gar kein ensure* → seine (nicht-geheime) ENV-Config wirkte zwar via getSetting-Fallback, war aber nie in plugin-state und damit in der Admin-UI unsichtbar ("funktioniert, wird aber nicht angezeigt"). Fix (Modell: State gewinnt, ENV seedet): - ensureMicrohamSettings + ensureOpenWebRxBandmapSettings auf fill-if-missing umgestellt: ENV seedet ein Feld nur, wenn es in plugin-state fehlt; danach bleibt der gespeicherte/UI-Wert erhalten und überlebt Deploys. - Neues ensureWavelogSettings (fill-if-missing) seedet baseUrl/radioName/ timeoutMs aus ENV → in der UI sichtbar, UI-Edits persistent. - vswr-/oauth-ensure* waren bereits fill-if-missing (unverändert). - Secret-Plugins (SMTP/oauth-Secrets/debug-Token) bewusst NICHT geseedet (dürfen nicht in plugin-state.json/UI landen). Hinweis: Dazu gehört eine Deploy-Repo-Änderung (entfernt die per-Deploy- Löschung der Bandmap-Keys in deploy.yml), sonst würde Bandmap weiter re-geseedet. Co-Authored-By: Claude Opus 4.8 --- server/index.js | 90 ++++++++++++++++++++++++++++++++++++------------- 1 file changed, 66 insertions(+), 24 deletions(-) diff --git a/server/index.js b/server/index.js index 11df935..3785f4f 100644 --- a/server/index.js +++ b/server/index.js @@ -1928,6 +1928,7 @@ async function migratePluginSettingsDefaults() { changed = ensureVswrReportReaderSettings(dataDir) || changed; changed = ensureOpenWebRxBandmapSettings() || changed; changed = ensureMicrohamSettings() || changed; + changed = ensureWavelogSettings() || changed; changed = ensureOauthSettings() || changed; if (changed) { @@ -1935,6 +1936,36 @@ async function migratePluginSettingsDefaults() { } } +function ensureWavelogSettings() { + const pluginId = "rms.wavelog.cat"; + const current = runtime.pluginState.settings[pluginId] && typeof runtime.pluginState.settings[pluginId] === "object" + ? { ...runtime.pluginState.settings[pluginId] } + : {}; + const next = { ...current }; + + const syncFromEnv = boolFromEnv("WAVELOG_SYNC_SETTINGS_FROM_ENV", config.execMode === "prod"); + if (!syncFromEnv) { + return false; + } + + // fill-if-missing: ENV seedet nur fehlende Felder, damit die (nicht-geheime) + // Wavelog-Config in der Admin-UI sichtbar ist und UI-Änderungen Deploys überleben. + const setIfMissing = (key, value) => { + if (current[key] === undefined || current[key] === null) { + next[key] = value; + } + }; + setIfMissing("baseUrl", String(process.env.WAVELOG_BASE_URL || "").trim()); + setIfMissing("radioName", String(process.env.WAVELOG_RADIO_NAME || "RMS Remote").trim() || "RMS Remote"); + setIfMissing("timeoutMs", clampInteger(Number(process.env.WAVELOG_TIMEOUT_MS || 8000), 1000, 120000, 8000)); + + if (JSON.stringify(current) !== JSON.stringify(next)) { + runtime.pluginState.settings[pluginId] = next; + return true; + } + return false; +} + function ensureOauthSettings() { const pluginId = "rms.auth.oauth"; const current = runtime.pluginState.settings[pluginId] && typeof runtime.pluginState.settings[pluginId] === "object" @@ -1971,11 +2002,17 @@ function ensureOpenWebRxBandmapSettings() { const syncFromEnv = boolFromEnv("OPENWEBRX_BANDMAP_SYNC_SETTINGS_FROM_ENV", config.execMode === "prod"); if (syncFromEnv) { - next.csvPath = String(process.env.OPENWEBRX_BANDMAP_CSV_PATH || "").trim(); - next.configFilePath = String(process.env.OPENWEBRX_CONFIG_PATH || "").trim(); - next.setCommandTemplate = String(process.env.OPENWEBRX_BAND_SET_CMD_TEMPLATE || "").trim(); - next.stateFilePath = String(process.env.OPENWEBRX_BAND_STATE_PATH || "").trim(); - next.timeoutMs = clampInteger(Number(process.env.OPENWEBRX_BAND_TIMEOUT_MS || 20000), 1000, 120000, 20000); + // fill-if-missing: ENV seedet nur fehlende Felder, UI-Wert bleibt erhalten. + const setIfMissing = (key, value) => { + if (current[key] === undefined || current[key] === null) { + next[key] = value; + } + }; + setIfMissing("csvPath", String(process.env.OPENWEBRX_BANDMAP_CSV_PATH || "").trim()); + setIfMissing("configFilePath", String(process.env.OPENWEBRX_CONFIG_PATH || "").trim()); + setIfMissing("setCommandTemplate", String(process.env.OPENWEBRX_BAND_SET_CMD_TEMPLATE || "").trim()); + setIfMissing("stateFilePath", String(process.env.OPENWEBRX_BAND_STATE_PATH || "").trim()); + setIfMissing("timeoutMs", clampInteger(Number(process.env.OPENWEBRX_BAND_TIMEOUT_MS || 20000), 1000, 120000, 20000)); } else if (!Number.isFinite(Number(next.timeoutMs)) || Number(next.timeoutMs) < 1000) { next.timeoutMs = 20000; } @@ -2088,25 +2125,30 @@ function ensureMicrohamSettings() { return false; } - next.device = String(process.env.MICROHAM_DEVICE || process.env.RMS_MICROHAM_DEV || "/dev/rms-microham-u3").trim() || "/dev/rms-microham-u3"; - next.pttCommandsEnabled = boolFromEnv("MICROHAM_PTT_COMMANDS_ENABLED", true); - next.pttDownCommand = String(process.env.MICROHAM_PTT_DOWN_CMD || "").trim(); - next.pttUpCommand = String(process.env.MICROHAM_PTT_UP_CMD || "").trim(); - next.pttTimeoutMs = clampInteger(Number(process.env.MICROHAM_PTT_TIMEOUT_MS || 5000), 1000, 60000, 5000); - next.pttApplyBandState = boolFromEnv("MICROHAM_PTT_APPLY_BAND_STATE", true); - next.pttRigctlModel = String(process.env.MICROHAM_PTT_RIGCTL_MODEL || "3023").trim() || "3023"; - next.pttRigctlBaud = String(process.env.MICROHAM_PTT_RIGCTL_BAUD || "19200").trim() || "19200"; - next.pttRigctlSetConf = String(process.env.MICROHAM_PTT_RIGCTL_SETCONF || "rts_state=OFF,dtr_state=OFF").trim() || "rts_state=OFF,dtr_state=OFF"; - next.audioEnabled = boolFromEnv("MICROHAM_AUDIO_ENABLED", true); - next.audioAlsaDevice = String(process.env.MICROHAM_AUDIO_ALSA_DEVICE || "plughw:CARD=CODEC,DEV=0").trim() || "plughw:CARD=CODEC,DEV=0"; - next.audioInputMime = String(process.env.MICROHAM_AUDIO_INPUT_MIME || "webm").trim().toLowerCase() === "ogg" ? "ogg" : "webm"; - next.audioStopOnDisconnect = boolFromEnv("MICROHAM_AUDIO_STOP_ON_DISCONNECT", true); - next.audioChunkMs = clampInteger(Number(process.env.MICROHAM_AUDIO_CHUNK_MS || 100), 40, 2000, 100); - next.audioSessionTimeoutMs = clampInteger(Number(process.env.MICROHAM_AUDIO_SESSION_TIMEOUT_MS || 120000), 1000, 3600000, 120000); - next.audioFfmpegPath = String(process.env.MICROHAM_AUDIO_FFMPEG_PATH || "").trim(); - if (next.audioFfmpegExtraArgs === undefined || next.audioFfmpegExtraArgs === null) { - next.audioFfmpegExtraArgs = String(process.env.MICROHAM_AUDIO_FFMPEG_EXTRA_ARGS || "").trim(); - } + // fill-if-missing: ENV seedet ein Feld nur, wenn es in plugin-state fehlt. + // Danach gewinnt immer der gespeicherte/UI-Wert (überlebt Deploys/Neustarts). + const setIfMissing = (key, value) => { + if (current[key] === undefined || current[key] === null) { + next[key] = value; + } + }; + setIfMissing("device", String(process.env.MICROHAM_DEVICE || process.env.RMS_MICROHAM_DEV || "/dev/rms-microham-u3").trim() || "/dev/rms-microham-u3"); + setIfMissing("pttCommandsEnabled", boolFromEnv("MICROHAM_PTT_COMMANDS_ENABLED", true)); + setIfMissing("pttDownCommand", String(process.env.MICROHAM_PTT_DOWN_CMD || "").trim()); + setIfMissing("pttUpCommand", String(process.env.MICROHAM_PTT_UP_CMD || "").trim()); + setIfMissing("pttTimeoutMs", clampInteger(Number(process.env.MICROHAM_PTT_TIMEOUT_MS || 5000), 1000, 60000, 5000)); + setIfMissing("pttApplyBandState", boolFromEnv("MICROHAM_PTT_APPLY_BAND_STATE", true)); + setIfMissing("pttRigctlModel", String(process.env.MICROHAM_PTT_RIGCTL_MODEL || "3023").trim() || "3023"); + setIfMissing("pttRigctlBaud", String(process.env.MICROHAM_PTT_RIGCTL_BAUD || "19200").trim() || "19200"); + setIfMissing("pttRigctlSetConf", String(process.env.MICROHAM_PTT_RIGCTL_SETCONF || "rts_state=OFF,dtr_state=OFF").trim() || "rts_state=OFF,dtr_state=OFF"); + setIfMissing("audioEnabled", boolFromEnv("MICROHAM_AUDIO_ENABLED", true)); + setIfMissing("audioAlsaDevice", String(process.env.MICROHAM_AUDIO_ALSA_DEVICE || "plughw:CARD=CODEC,DEV=0").trim() || "plughw:CARD=CODEC,DEV=0"); + setIfMissing("audioInputMime", String(process.env.MICROHAM_AUDIO_INPUT_MIME || "webm").trim().toLowerCase() === "ogg" ? "ogg" : "webm"); + setIfMissing("audioStopOnDisconnect", boolFromEnv("MICROHAM_AUDIO_STOP_ON_DISCONNECT", true)); + setIfMissing("audioChunkMs", clampInteger(Number(process.env.MICROHAM_AUDIO_CHUNK_MS || 100), 40, 2000, 100)); + setIfMissing("audioSessionTimeoutMs", clampInteger(Number(process.env.MICROHAM_AUDIO_SESSION_TIMEOUT_MS || 120000), 1000, 3600000, 120000)); + setIfMissing("audioFfmpegPath", String(process.env.MICROHAM_AUDIO_FFMPEG_PATH || "").trim()); + setIfMissing("audioFfmpegExtraArgs", String(process.env.MICROHAM_AUDIO_FFMPEG_EXTRA_ARGS || "").trim()); if (JSON.stringify(current) !== JSON.stringify(next)) { runtime.pluginState.settings[pluginId] = next;