diff --git a/docs/gateway/configuration.md b/docs/gateway/configuration.md index 23599d3f611..c3de772ed21 100644 --- a/docs/gateway/configuration.md +++ b/docs/gateway/configuration.md @@ -633,13 +633,13 @@ You can load configuration from [Nacos](https://nacos.io/) instead of a local fi Set these environment variables: -| Variable | Required | Purpose | -| ------------------------ | -------- | ----------------------------------------------------------------------- | +| Variable | Required | Purpose | +| ------------------------ | -------- | ------------------------------------------------------------------------------------- | | `OPENCLAW_CONFIG_SOURCE` | Yes | Set to `nacos` to use Nacos; omit or set to `file` for the default file-based config. | -| `NACOS_SERVER_ADDR` | Yes | Nacos server address (e.g. `http://nacos:8848`). | -| `NACOS_DATA_ID` | Yes | Data ID of the config (e.g. `openclaw.json`). | -| `NACOS_GROUP` | No | Nacos group (default: `DEFAULT_GROUP`). | -| `NACOS_NAMESPACE` | No | Nacos namespace (tenant); omit for default namespace. | +| `NACOS_SERVER_ADDR` | Yes | Nacos server address (e.g. `http://nacos:8848`). | +| `NACOS_DATA_ID` | Yes | Data ID of the config (e.g. `openclaw.json`). | +| `NACOS_GROUP` | No | Nacos group (default: `DEFAULT_GROUP`). | +| `NACOS_NAMESPACE` | No | Nacos namespace (tenant); omit for default namespace. | When `OPENCLAW_CONFIG_SOURCE=nacos`, the gateway fetches the config from Nacos at startup. No config file is written on disk; the in-memory config is updated by Nacos long-polling. Hot reload is driven by Nacos (changes in Nacos are applied without restart). Config read RPC (`config.get`) uses the in-memory config. Config write RPC (`config.apply`, `config.patch`) and CLI `config set` update only the in-memory snapshot (no write to disk or to Nacos). To change config persistently when using Nacos, update the config in Nacos and rely on hot reload. diff --git a/docs/help/environment.md b/docs/help/environment.md index 51aae1948df..5c53e669eb8 100644 --- a/docs/help/environment.md +++ b/docs/help/environment.md @@ -113,13 +113,13 @@ Both resolve from process env at activation time. SecretRef details are document When loading config from Nacos instead of a local file (e.g. in Kubernetes pods with no config file mount): -| Variable | Required | Purpose | -| ------------------------ | -------- | ----------------------------------------------------------------------- | -| `OPENCLAW_CONFIG_SOURCE` | Yes | Set to `nacos` to use Nacos; omit or `file` for file-based config. | -| `NACOS_SERVER_ADDR` | Yes | Nacos server address (e.g. `http://nacos:8848`). | -| `NACOS_DATA_ID` | Yes | Data ID of the config (e.g. `openclaw.json`). | -| `NACOS_GROUP` | No | Nacos group (default: `DEFAULT_GROUP`). | -| `NACOS_NAMESPACE` | No | Nacos namespace (tenant); omit for default namespace. | +| Variable | Required | Purpose | +| ------------------------ | -------- | ------------------------------------------------------------------ | +| `OPENCLAW_CONFIG_SOURCE` | Yes | Set to `nacos` to use Nacos; omit or `file` for file-based config. | +| `NACOS_SERVER_ADDR` | Yes | Nacos server address (e.g. `http://nacos:8848`). | +| `NACOS_DATA_ID` | Yes | Data ID of the config (e.g. `openclaw.json`). | +| `NACOS_GROUP` | No | Nacos group (default: `DEFAULT_GROUP`). | +| `NACOS_NAMESPACE` | No | Nacos namespace (tenant); omit for default namespace. | Config is kept only in memory; hot reload is driven by Nacos long-polling. See [Configuration: Config source: Nacos](/gateway/configuration#config-source-nacos). diff --git a/src/config/io.ts b/src/config/io.ts index 63f1f5b2792..84f74f65ae0 100644 --- a/src/config/io.ts +++ b/src/config/io.ts @@ -47,8 +47,8 @@ import { normalizeExecSafeBinProfilesInConfig } from "./normalize-exec-safe-bin. import { normalizeConfigPaths } from "./normalize-paths.js"; import { resolveConfigPath, resolveDefaultConfigCandidates, resolveStateDir } from "./paths.js"; import { isBlockedObjectKey } from "./prototype-keys.js"; -import { getConfigSource } from "./sources/current.js"; import { applyConfigOverrides } from "./runtime-overrides.js"; +import { getConfigSource } from "./sources/current.js"; import type { OpenClawConfig, ConfigFileSnapshot, LegacyConfigIssue } from "./types.js"; import { validateConfigObjectRawWithPlugins, @@ -1506,8 +1506,8 @@ function buildSnapshotFromRuntimeSnapshots(path: string): ConfigFileSnapshot | n path, exists: true, raw, - parsed: config as unknown, - resolved: resolved as unknown, + parsed: config, + resolved, valid: true, config, hash: hashConfigRaw(raw), diff --git a/src/config/sources/nacos-client.ts b/src/config/sources/nacos-client.ts index d4ea049c983..5c2dd82266b 100644 --- a/src/config/sources/nacos-client.ts +++ b/src/config/sources/nacos-client.ts @@ -67,8 +67,7 @@ export function createNacosConfigClient(opts: NacosConfigClientOptions): NacosCo if (stopped) return; // Nacos v1 listener expects Listening-Configs=%02%02%02%01 const tenant = opts.tenant ?? ""; - const listeningConfigs = - `${opts.dataId}${STX}${opts.group}${STX}${lastContentMD5}${STX}${tenant}${SOH}`; + const listeningConfigs = `${opts.dataId}${STX}${opts.group}${STX}${lastContentMD5}${STX}${tenant}${SOH}`; const body = new URLSearchParams({ "Listening-Configs": listeningConfigs }); try { const res = await doFetch(listenerUrl, { diff --git a/src/config/sources/nacos.test.ts b/src/config/sources/nacos.test.ts index aa15d7d8659..ae36ae5a308 100644 --- a/src/config/sources/nacos.test.ts +++ b/src/config/sources/nacos.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import { createNacosConfigSource } from "./nacos.js"; import type { NacosConfigClient } from "./nacos-client.js"; +import { createNacosConfigSource } from "./nacos.js"; describe("createNacosConfigSource", () => { it("returns source with kind nacos, watchPath null, subscribe function, and readSnapshot builds snapshot", async () => { diff --git a/src/config/sources/snapshot-from-raw.ts b/src/config/sources/snapshot-from-raw.ts index 73cc1cb316a..d8aad813bbe 100644 --- a/src/config/sources/snapshot-from-raw.ts +++ b/src/config/sources/snapshot-from-raw.ts @@ -17,18 +17,21 @@ import { applyTalkApiKey, applyTalkConfigNormalization, } from "../defaults.js"; -import { applyConfigEnvVars } from "../env-vars.js"; import type { EnvSubstitutionWarning } from "../env-substitution.js"; import { resolveConfigEnvVars } from "../env-substitution.js"; +import { applyConfigEnvVars } from "../env-vars.js"; import { parseConfigJson5 } from "../io.js"; import { findLegacyConfigIssues } from "../legacy.js"; -import { normalizeConfigPaths } from "../normalize-paths.js"; import { normalizeExecSafeBinProfilesInConfig } from "../normalize-exec-safe-bin.js"; +import { normalizeConfigPaths } from "../normalize-paths.js"; import type { ConfigFileSnapshot, LegacyConfigIssue, OpenClawConfig } from "../types.js"; import { validateConfigObjectWithPlugins } from "../validation.js"; function hashConfigRaw(raw: string | null): string { - return crypto.createHash("sha256").update(raw ?? "").digest("hex"); + return crypto + .createHash("sha256") + .update(raw ?? "") + .digest("hex"); } function coerceConfig(value: unknown): OpenClawConfig { diff --git a/src/config/sources/types.test.ts b/src/config/sources/types.test.ts index 10902f92242..3a918951cbe 100644 --- a/src/config/sources/types.test.ts +++ b/src/config/sources/types.test.ts @@ -12,7 +12,7 @@ describe("ConfigSource types", () => { it("ConfigSource has readSnapshot and optional subscribe", () => { const source: ConfigSource = { kind: "file", - readSnapshot: async () => ({} as never), + readSnapshot: async () => ({}) as never, }; expect(source.kind).toBe("file"); expect(typeof source.readSnapshot).toBe("function"); diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index ba12fd934b5..b7a7731ac4f 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -20,11 +20,11 @@ import { setRuntimeConfigSnapshot, writeConfigFile, } from "../config/config.js"; -import { getConfigSource, setConfigSource } from "../config/sources/current.js"; -import { resolveConfigSource } from "../config/sources/resolve.js"; import { formatConfigIssueLines } from "../config/issue-format.js"; import { applyPluginAutoEnable } from "../config/plugin-auto-enable.js"; import { resolveMainSessionKey } from "../config/sessions.js"; +import { getConfigSource, setConfigSource } from "../config/sources/current.js"; +import { resolveConfigSource } from "../config/sources/resolve.js"; import { clearAgentRunContext, onAgentEvent } from "../infra/agent-events.js"; import { ensureControlUiAssetsBuilt, @@ -32,8 +32,8 @@ import { resolveControlUiRootOverrideSync, resolveControlUiRootSync, } from "../infra/control-ui-assets.js"; -import { loadDotEnv } from "../infra/dotenv.js"; import { isDiagnosticsEnabled } from "../infra/diagnostic-events.js"; +import { loadDotEnv } from "../infra/dotenv.js"; import { logAcceptedEnvOption } from "../infra/env.js"; import { createExecApprovalForwarder } from "../infra/exec-approval-forwarder.js"; import { onHeartbeatEvent } from "../infra/heartbeat-events.js"; @@ -78,8 +78,8 @@ import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js"; import { runSetupWizard } from "../wizard/setup.js"; import { createAuthRateLimiter, type AuthRateLimiter } from "./auth-rate-limit.js"; import { startChannelHealthMonitor } from "./channel-health-monitor.js"; -import { startGatewayConfigReloader } from "./config-reload.js"; import type { GatewayReloadPlan } from "./config-reload-plan.js"; +import { startGatewayConfigReloader } from "./config-reload.js"; import type { ControlUiRootState } from "./control-ui.js"; import { GATEWAY_EVENT_UPDATE_AVAILABLE,