config: apply Nacos source review fixes
Made-with: Cursor
This commit is contained in:
parent
7bae3314be
commit
bda40c7e57
@ -7,6 +7,8 @@ import { formatConfigIssueLines, normalizeConfigIssues } from "../config/issue-f
|
||||
import { CONFIG_PATH } from "../config/paths.js";
|
||||
import { isBlockedObjectKey } from "../config/prototype-keys.js";
|
||||
import { redactConfigObject } from "../config/redact-snapshot.js";
|
||||
import { setConfigSource } from "../config/sources/current.js";
|
||||
import { resolveConfigSource } from "../config/sources/resolve.js";
|
||||
import {
|
||||
coerceSecretRef,
|
||||
isValidEnvSecretRefId,
|
||||
@ -18,6 +20,7 @@ import {
|
||||
import { validateConfigObjectRaw } from "../config/validation.js";
|
||||
import { SecretProviderSchema } from "../config/zod-schema.core.js";
|
||||
import { danger, info, success } from "../globals.js";
|
||||
import { loadDotEnv } from "../infra/dotenv.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
import {
|
||||
@ -99,6 +102,13 @@ class ConfigSetDryRunValidationError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
function initializeConfigSourceForStandaloneConfigCli(): void {
|
||||
// Standalone config commands run in a fresh process.
|
||||
// Ensure config helpers read from the same source (file vs Nacos) as the gateway.
|
||||
loadDotEnv({ quiet: true });
|
||||
setConfigSource(resolveConfigSource(process.env));
|
||||
}
|
||||
|
||||
function isIndexSegment(raw: string): boolean {
|
||||
return /^[0-9]+$/.test(raw);
|
||||
}
|
||||
@ -969,6 +979,7 @@ export async function runConfigSet(opts: {
|
||||
cliOptions: ConfigSetOptions;
|
||||
runtime?: RuntimeEnv;
|
||||
}) {
|
||||
initializeConfigSourceForStandaloneConfigCli();
|
||||
const runtime = opts.runtime ?? defaultRuntime;
|
||||
try {
|
||||
const isBatchMode = hasBatchMode(opts.cliOptions);
|
||||
@ -1130,6 +1141,7 @@ export async function runConfigSet(opts: {
|
||||
}
|
||||
|
||||
export async function runConfigGet(opts: { path: string; json?: boolean; runtime?: RuntimeEnv }) {
|
||||
initializeConfigSourceForStandaloneConfigCli();
|
||||
const runtime = opts.runtime ?? defaultRuntime;
|
||||
try {
|
||||
const parsedPath = parseRequiredPath(opts.path);
|
||||
@ -1161,6 +1173,7 @@ export async function runConfigGet(opts: { path: string; json?: boolean; runtime
|
||||
}
|
||||
|
||||
export async function runConfigUnset(opts: { path: string; runtime?: RuntimeEnv }) {
|
||||
initializeConfigSourceForStandaloneConfigCli();
|
||||
const runtime = opts.runtime ?? defaultRuntime;
|
||||
try {
|
||||
const parsedPath = parseRequiredPath(opts.path);
|
||||
@ -1184,6 +1197,7 @@ export async function runConfigUnset(opts: { path: string; runtime?: RuntimeEnv
|
||||
}
|
||||
|
||||
export async function runConfigFile(opts: { runtime?: RuntimeEnv }) {
|
||||
initializeConfigSourceForStandaloneConfigCli();
|
||||
const runtime = opts.runtime ?? defaultRuntime;
|
||||
try {
|
||||
const snapshot = await readConfigFileSnapshot();
|
||||
@ -1195,6 +1209,7 @@ export async function runConfigFile(opts: { runtime?: RuntimeEnv }) {
|
||||
}
|
||||
|
||||
export async function runConfigValidate(opts: { json?: boolean; runtime?: RuntimeEnv } = {}) {
|
||||
initializeConfigSourceForStandaloneConfigCli();
|
||||
const runtime = opts.runtime ?? defaultRuntime;
|
||||
let outputPath = CONFIG_PATH ?? "openclaw.json";
|
||||
|
||||
|
||||
@ -10,12 +10,15 @@ import {
|
||||
resolveStateDir,
|
||||
resolveGatewayPort,
|
||||
} from "../../config/config.js";
|
||||
import { setConfigSource } from "../../config/sources/current.js";
|
||||
import { resolveConfigSource } from "../../config/sources/resolve.js";
|
||||
import { hasConfiguredSecretInput } from "../../config/types.secrets.js";
|
||||
import { resolveGatewayAuth } from "../../gateway/auth.js";
|
||||
import { startGatewayServer } from "../../gateway/server.js";
|
||||
import type { GatewayWsLogStyle } from "../../gateway/ws-logging.js";
|
||||
import { setGatewayWsLogStyle } from "../../gateway/ws-logging.js";
|
||||
import { setVerbose } from "../../globals.js";
|
||||
import { loadDotEnv } from "../../infra/dotenv.js";
|
||||
import { GatewayLockError } from "../../infra/gateway-lock.js";
|
||||
import { formatPortDiagnostics, inspectPortUsage } from "../../infra/ports.js";
|
||||
import { cleanStaleGatewayProcessesSync } from "../../infra/restart-stale-pids.js";
|
||||
@ -198,6 +201,11 @@ async function runGatewayCommand(opts: GatewayRunOpts) {
|
||||
await ensureDevGatewayConfig({ reset: Boolean(opts.reset) });
|
||||
}
|
||||
|
||||
// Initialize config source (file vs Nacos) before preflight so port/bind and
|
||||
// config-exists / gateway.mode checks use Nacos when OPENCLAW_CONFIG_SOURCE=nacos.
|
||||
loadDotEnv({ quiet: true });
|
||||
setConfigSource(resolveConfigSource(process.env));
|
||||
|
||||
const cfg = loadConfig();
|
||||
const portOverride = parsePort(opts.port);
|
||||
if (opts.port !== undefined && portOverride === null) {
|
||||
|
||||
@ -12,12 +12,15 @@ import { formatCliCommand } from "../cli/command-format.js";
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { CONFIG_PATH, readConfigFileSnapshot, writeConfigFile } from "../config/config.js";
|
||||
import { logConfigUpdated } from "../config/logging.js";
|
||||
import { setConfigSource } from "../config/sources/current.js";
|
||||
import { resolveConfigSource } from "../config/sources/resolve.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { resolveGatewayService } from "../daemon/service.js";
|
||||
import { hasAmbiguousGatewayAuthModeConfig } from "../gateway/auth-mode-policy.js";
|
||||
import { resolveGatewayAuth } from "../gateway/auth.js";
|
||||
import { buildGatewayConnectionDetails } from "../gateway/call.js";
|
||||
import { runStartupMatrixMigration } from "../gateway/server-startup-matrix-migration.js";
|
||||
import { loadDotEnv } from "../infra/dotenv.js";
|
||||
import { resolveOpenClawPackageRoot } from "../infra/openclaw-root.js";
|
||||
import type { RuntimeEnv } from "../runtime.js";
|
||||
import { defaultRuntime } from "../runtime.js";
|
||||
@ -80,6 +83,11 @@ export async function doctorCommand(
|
||||
printWizardHeader(runtime);
|
||||
intro("OpenClaw doctor");
|
||||
|
||||
// Standalone CLI process: initialize config source (file vs Nacos)
|
||||
// before any config reads/writes.
|
||||
loadDotEnv({ quiet: true });
|
||||
setConfigSource(resolveConfigSource(process.env));
|
||||
|
||||
const root = await resolveOpenClawPackageRoot({
|
||||
moduleUrl: import.meta.url,
|
||||
argv1: process.argv[1],
|
||||
|
||||
@ -66,4 +66,39 @@ describe("createNacosConfigClient", () => {
|
||||
expect(onChange).toHaveBeenCalled();
|
||||
teardown();
|
||||
});
|
||||
|
||||
it("subscribe backs off when listener returns HTTP error", async () => {
|
||||
vi.useFakeTimers();
|
||||
const setTimeoutSpy = vi.spyOn(globalThis, "setTimeout");
|
||||
|
||||
const fetchMock = vi.fn().mockResolvedValue({
|
||||
ok: false,
|
||||
status: 401,
|
||||
statusText: "Unauthorized",
|
||||
text: () => Promise.resolve(""),
|
||||
});
|
||||
|
||||
const client = createNacosConfigClient({
|
||||
serverAddr: "http://127.0.0.1:8848",
|
||||
dataId: "openclaw.json",
|
||||
group: "DEFAULT_GROUP",
|
||||
fetch: fetchMock,
|
||||
});
|
||||
|
||||
const onChange = vi.fn();
|
||||
const teardown = client.subscribe(onChange);
|
||||
|
||||
// Flush the initial microtasks so poll() reaches the HTTP error branch.
|
||||
await Promise.resolve();
|
||||
|
||||
expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), 5000);
|
||||
teardown();
|
||||
|
||||
// Let the scheduled backoff settle and avoid leaving pending timers/promises.
|
||||
await vi.advanceTimersByTimeAsync(5000);
|
||||
|
||||
expect(onChange).not.toHaveBeenCalled();
|
||||
setTimeoutSpy.mockRestore();
|
||||
vi.useRealTimers();
|
||||
});
|
||||
});
|
||||
|
||||
@ -79,7 +79,11 @@ export function createNacosConfigClient(opts: NacosConfigClientOptions): NacosCo
|
||||
body: body.toString(),
|
||||
});
|
||||
if (stopped) return;
|
||||
if (res.ok) {
|
||||
if (!res.ok) {
|
||||
// Nacos can respond quickly with HTTP errors (401/403/5xx) when ACL/service is unhealthy.
|
||||
// Add a backoff to avoid a tight POST loop hammering the server.
|
||||
await new Promise((r) => setTimeout(r, 5000));
|
||||
} else {
|
||||
const text = await res.text();
|
||||
if (text.trim()) {
|
||||
try {
|
||||
|
||||
@ -13,4 +13,21 @@ describe("buildSnapshotFromRaw", () => {
|
||||
expect(snap.valid).toBe(true);
|
||||
expect(snap.config?.gateway?.mode).toBe("local");
|
||||
});
|
||||
|
||||
it("applies config.env to process.env for Nacos snapshots", async () => {
|
||||
const envKey = "OPENAI_API_KEY";
|
||||
const previous = process.env[envKey];
|
||||
delete process.env[envKey];
|
||||
try {
|
||||
const raw = `{"env":{"${envKey}":"sk-test"}}`;
|
||||
await buildSnapshotFromRaw(raw, "nacos:openclaw.json", { env: process.env });
|
||||
expect(process.env[envKey]).toBe("sk-test");
|
||||
} finally {
|
||||
if (previous !== undefined) {
|
||||
process.env[envKey] = previous;
|
||||
} else {
|
||||
delete process.env[envKey];
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
@ -80,7 +80,13 @@ export async function buildSnapshotFromRaw(
|
||||
typeof parsedRes.parsed === "object" &&
|
||||
"env" in (parsedRes.parsed as object)
|
||||
) {
|
||||
applyConfigEnvVars(parsedRes.parsed as OpenClawConfig, envCopy);
|
||||
const cfgWithEnv = parsedRes.parsed as OpenClawConfig;
|
||||
// Nacos-backed snapshots should also hydrate the real process env so features
|
||||
// that consult process.env later in the process see config.env values.
|
||||
if (opts.env === process.env) {
|
||||
applyConfigEnvVars(cfgWithEnv, opts.env);
|
||||
}
|
||||
applyConfigEnvVars(cfgWithEnv, envCopy);
|
||||
}
|
||||
const resolvedConfigRaw = resolveConfigEnvVars(parsedRes.parsed, envCopy, {
|
||||
onMissing: (w) => envWarnings.push(w),
|
||||
|
||||
@ -17,6 +17,7 @@ import {
|
||||
loadConfig,
|
||||
migrateLegacyConfig,
|
||||
readConfigFileSnapshot,
|
||||
getRuntimeConfigSourceSnapshot,
|
||||
setRuntimeConfigSnapshot,
|
||||
writeConfigFile,
|
||||
} from "../config/config.js";
|
||||
@ -557,7 +558,11 @@ export async function startGatewayServer(
|
||||
// Nacos source: keep loadConfig() in sync with the config we use (no file path).
|
||||
const configSource = getConfigSource();
|
||||
if (configSource?.kind === "nacos") {
|
||||
setRuntimeConfigSnapshot(cfgAtStart);
|
||||
// Preserve the original Nacos "source" snapshot captured during secret activation.
|
||||
// If we overwrite it with cfgAtStart, config.env and ${ENV} substitutions can lose
|
||||
// their original surface values in subsequent config.get/set/patch operations.
|
||||
const sourceSnapshot = getRuntimeConfigSourceSnapshot();
|
||||
setRuntimeConfigSnapshot(cfgAtStart, sourceSnapshot ?? cfgAtStart);
|
||||
}
|
||||
|
||||
initSubagentRegistry();
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user