diff --git a/backend/windmill-common/src/instance_config.rs b/backend/windmill-common/src/instance_config.rs index 21392c3a11..7270577e88 100644 --- a/backend/windmill-common/src/instance_config.rs +++ b/backend/windmill-common/src/instance_config.rs @@ -335,10 +335,18 @@ impl GlobalSettings { // including `extra` into a single map. skip_serializing_if ensures // None fields are omitted. let value = serde_json::to_value(self).expect("GlobalSettings serialization cannot fail"); - match value { + let mut map: BTreeMap = match value { serde_json::Value::Object(map) => map.into_iter().collect(), _ => unreachable!(), + }; + // Strip runtime-only `databases` sub-field from custom_instance_pg_databases. + // It contains setup status/logs managed by the setup endpoint, not configuration. + if let Some(pg) = map.get_mut("custom_instance_pg_databases") { + if let Some(obj) = pg.as_object_mut() { + obj.remove("databases"); + } } + map } } @@ -941,11 +949,23 @@ pub fn diff_global_settings( ); continue; } - let value = if key == "retention_period_secs" { + let mut value = if key == "retention_period_secs" { clamp_retention_period(desired_value.clone()) } else { desired_value.clone() }; + // Preserve the runtime-only `databases` sub-field inside + // `custom_instance_pg_databases` so that config sync never wipes + // setup status/logs that are managed by the setup endpoint. + if key == "custom_instance_pg_databases" { + if let Some(existing) = current.get(key) { + if let Some(databases) = existing.get("databases") { + if let Some(obj) = value.as_object_mut() { + obj.entry("databases").or_insert_with(|| databases.clone()); + } + } + } + } match current.get(key) { Some(existing) if *existing == value => { unchanged_count += 1; diff --git a/frontend/src/lib/components/InstanceSettings.svelte b/frontend/src/lib/components/InstanceSettings.svelte index da0539d8a9..423f25a46c 100644 --- a/frontend/src/lib/components/InstanceSettings.svelte +++ b/frontend/src/lib/components/InstanceSettings.svelte @@ -556,7 +556,7 @@ .filter((s) => s.fieldType === 'password' || s.fieldType === 'license_key') .map((s) => s.key), 'ducklake_user_pg_pwd', - 'rsa_keys' + 'jwt_secret' ]) // Settings that should never appear in YAML export/import @@ -568,7 +568,8 @@ smtp_settings: ['smtp_password'], secret_backend: ['token'], object_store_cache_config: ['secret_key', 'serviceAccountKey'], - custom_instance_pg_databases: ['user_pwd'] + custom_instance_pg_databases: ['user_pwd'], + rsa_keys: ['private_key'] } /** Returns SENSITIVE_UNCHANGED if the value is non-empty and matches the initial */ @@ -636,6 +637,11 @@ if (opts.normalize && normalizeValue(merged[key], key) === undefined) continue obj[key] = merged[key] } + // Strip runtime-only `databases` sub-field from custom_instance_pg_databases + if (obj['custom_instance_pg_databases']?.databases) { + obj['custom_instance_pg_databases'] = { ...obj['custom_instance_pg_databases'] } + delete obj['custom_instance_pg_databases'].databases + } return YAML.stringify(opts.mask ? maskSensitive(obj) : obj) } @@ -706,6 +712,12 @@ } } + // Preserve runtime-only `databases` sub-field in custom_instance_pg_databases + const existingDatabases = initialValues?.['custom_instance_pg_databases']?.databases + if (existingDatabases && parsed['custom_instance_pg_databases']) { + parsed['custom_instance_pg_databases'].databases = existingDatabases + } + $values = parsed applyFormDefaults($values)