mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-12 21:53:00 -06:00
refactor(daemon): centralize environment source lookup (#113283)
This commit is contained in:
@@ -1705,7 +1705,7 @@ describe("buildGatewayInstallPlan — dotenv merge", () => {
|
||||
expect(plan.environment.CUSTOM_TOOL_HOME).toBe("/Users/test/.custom-tool");
|
||||
});
|
||||
|
||||
it("keeps source metadata for EnvironmentFile-backed preserved vars", async () => {
|
||||
it("keeps differently-cased source metadata for EnvironmentFile-backed preserved vars", async () => {
|
||||
mockNodeGatewayPlanFixture({
|
||||
serviceEnvironment: {
|
||||
HOME: "/from-service",
|
||||
@@ -1723,9 +1723,9 @@ describe("buildGatewayInstallPlan — dotenv merge", () => {
|
||||
OPENCLAW_GATEWAY_TOKEN: "old-token",
|
||||
},
|
||||
existingEnvironmentValueSources: {
|
||||
OPENROUTER_API_KEY: "file",
|
||||
CUSTOM_TOOL_HOME: "inline",
|
||||
OPENCLAW_GATEWAY_TOKEN: "file",
|
||||
openrouter_api_key: "file",
|
||||
custom_tool_home: "inline",
|
||||
openclaw_gateway_token: "file",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||
import {
|
||||
formatManagedServiceEnvKeys,
|
||||
hasEnvironmentFileSource,
|
||||
readEnvironmentValueSource,
|
||||
readManagedServiceEnvKeysFromEnvironment,
|
||||
} from "../daemon/service-managed-env.js";
|
||||
import { isNonMinimalServicePathEntry } from "../daemon/service-path-policy.js";
|
||||
@@ -500,22 +501,6 @@ function collectPreservedExistingServiceEnvVars(
|
||||
return preserved;
|
||||
}
|
||||
|
||||
function readExistingEnvironmentValueSource(params: {
|
||||
existingEnvironmentValueSources?: Record<
|
||||
string,
|
||||
GatewayServiceEnvironmentValueSource | undefined
|
||||
>;
|
||||
normalizedKey: string;
|
||||
}): GatewayServiceEnvironmentValueSource | undefined {
|
||||
for (const [rawKey, source] of Object.entries(params.existingEnvironmentValueSources ?? {})) {
|
||||
const key = normalizeEnvVarKey(rawKey, { portable: true })?.toUpperCase();
|
||||
if (key === params.normalizedKey) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function collectExistingEnvironmentFileManagedServiceEnvVars(params: {
|
||||
existingEnvironment: Record<string, string | undefined> | undefined;
|
||||
existingEnvironmentValueSources?: Record<
|
||||
@@ -540,10 +525,10 @@ function collectExistingEnvironmentFileManagedServiceEnvVars(params: {
|
||||
if (isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key)) {
|
||||
continue;
|
||||
}
|
||||
const source = readExistingEnvironmentValueSource({
|
||||
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
|
||||
const source = readEnvironmentValueSource(
|
||||
params.existingEnvironmentValueSources,
|
||||
normalizedKey,
|
||||
});
|
||||
);
|
||||
if (!hasEnvironmentFileSource(source)) {
|
||||
continue;
|
||||
}
|
||||
@@ -651,10 +636,7 @@ async function buildGatewayInstallEnvironment(params: {
|
||||
const plan = createMutableServiceEnvPlan();
|
||||
addServiceEnvPlanEntries(plan, preservedExistingEnvironment, {
|
||||
valueSource: ({ normalizedKey }) =>
|
||||
readExistingEnvironmentValueSource({
|
||||
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
|
||||
normalizedKey,
|
||||
}) ?? "inline",
|
||||
readEnvironmentValueSource(params.existingEnvironmentValueSources, normalizedKey) ?? "inline",
|
||||
});
|
||||
addServiceEnvPlanEntries(plan, stateDirDotEnvEnvironment, {});
|
||||
addServiceEnvPlanEntries(plan, configEnvironment, {});
|
||||
|
||||
@@ -715,6 +715,23 @@ describe("auditGatewayServiceConfig", () => {
|
||||
|
||||
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayProxyEnvEmbedded)).toBe(true);
|
||||
});
|
||||
|
||||
it("matches managed and proxy source metadata keys case-insensitively", async () => {
|
||||
const audit = await createGatewayAudit({
|
||||
expectedManagedServiceEnvKeys: ["TAVILY_API_KEY"],
|
||||
extraEnvironment: {
|
||||
TAVILY_API_KEY: "tvly-test",
|
||||
HTTPS_PROXY: "https://proxy.local:7890",
|
||||
},
|
||||
environmentValueSources: {
|
||||
tavily_api_key: "file",
|
||||
https_proxy: "file",
|
||||
},
|
||||
});
|
||||
|
||||
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayManagedEnvEmbedded)).toBe(false);
|
||||
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayProxyEnvEmbedded)).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("checkTokenDrift", () => {
|
||||
|
||||
@@ -27,6 +27,7 @@ import {
|
||||
collectInlineManagedServiceEnvKeys,
|
||||
hasInlineEnvironmentSource,
|
||||
isEnvironmentFileOnlySource,
|
||||
readEnvironmentValueSource,
|
||||
} from "./service-managed-env.js";
|
||||
import { isNonMinimalServicePathEntry, normalizeServicePathEntry } from "./service-path-policy.js";
|
||||
import type { GatewayServiceEnvironmentValueSource } from "./service-types.js";
|
||||
@@ -393,18 +394,6 @@ function normalizeServiceEnvKey(key: string): string | null {
|
||||
return normalizeEnvVarKey(key, { portable: true })?.toUpperCase() ?? null;
|
||||
}
|
||||
|
||||
function readEnvironmentValueSource(
|
||||
command: GatewayServiceCommand,
|
||||
normalizedKey: string,
|
||||
): GatewayServiceEnvironmentValueSource | undefined {
|
||||
for (const [rawKey, source] of Object.entries(command?.environmentValueSources ?? {})) {
|
||||
if (normalizeServiceEnvKey(rawKey) === normalizedKey) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const SERVICE_PROXY_ENV_KEY_SET = new Set(
|
||||
SERVICE_PROXY_ENV_KEYS.flatMap((key) => {
|
||||
const normalized = normalizeServiceEnvKey(key);
|
||||
@@ -425,7 +414,11 @@ function collectInlineProxyEnvKeys(command: GatewayServiceCommand): string[] {
|
||||
if (!normalized || !SERVICE_PROXY_ENV_KEY_SET.has(normalized)) {
|
||||
continue;
|
||||
}
|
||||
if (!hasInlineEnvironmentSource(readEnvironmentValueSource(command, normalized))) {
|
||||
if (
|
||||
!hasInlineEnvironmentSource(
|
||||
readEnvironmentValueSource(command.environmentValueSources, normalized),
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
inlineKeys.push(normalized);
|
||||
|
||||
@@ -114,11 +114,17 @@ export function writeManagedServiceEnvKeysToEnvironment(
|
||||
environment[MANAGED_SERVICE_ENV_KEYS_VAR] = value;
|
||||
}
|
||||
|
||||
function readEnvironmentValueSource(
|
||||
command: ServiceEnvCommand,
|
||||
normalizedKey: string,
|
||||
export function readEnvironmentValueSource(
|
||||
environmentValueSources:
|
||||
| Record<string, GatewayServiceEnvironmentValueSource | undefined>
|
||||
| undefined,
|
||||
key: string,
|
||||
): GatewayServiceEnvironmentValueSource | undefined {
|
||||
for (const [rawKey, source] of Object.entries(command?.environmentValueSources ?? {})) {
|
||||
const normalizedKey = normalizeServiceEnvKey(key);
|
||||
if (!normalizedKey) {
|
||||
return undefined;
|
||||
}
|
||||
for (const [rawKey, source] of Object.entries(environmentValueSources ?? {})) {
|
||||
if (normalizeServiceEnvKey(rawKey) === normalizedKey) {
|
||||
return source;
|
||||
}
|
||||
@@ -155,7 +161,11 @@ export function collectInlineManagedServiceEnvKeys(
|
||||
if (normalized === MANAGED_SERVICE_ENV_KEYS_VAR) {
|
||||
continue;
|
||||
}
|
||||
if (!hasInlineEnvironmentSource(readEnvironmentValueSource(command, normalized))) {
|
||||
if (
|
||||
!hasInlineEnvironmentSource(
|
||||
readEnvironmentValueSource(command.environmentValueSources, normalized),
|
||||
)
|
||||
) {
|
||||
continue;
|
||||
}
|
||||
// Only inline/file-overlap sources can be repaired from the service command
|
||||
|
||||
@@ -1322,7 +1322,7 @@ describe("stageSystemdService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("writes node file-backed managed values to the node env file instead of the unit", async () => {
|
||||
it("matches differently-cased source metadata when writing node file-backed values", async () => {
|
||||
await withStageFixture(async ({ env, stateDir, unitPath, envFilePath, nodeEnvFilePath }) => {
|
||||
await fs.rm(stateDir, { recursive: true, force: true });
|
||||
const gatewayPassword = 'symbol " \\ $ `'; // pragma: allowlist secret
|
||||
@@ -1342,9 +1342,9 @@ describe("stageSystemdService", () => {
|
||||
OPENCLAW_SERVICE_KIND: "node",
|
||||
},
|
||||
environmentValueSources: {
|
||||
OPENCLAW_GATEWAY_TOKEN: "file",
|
||||
OPENCLAW_GATEWAY_PASSWORD: "file", // pragma: allowlist secret
|
||||
OPENCLAW_SERVICE_MANAGED_ENV_KEYS: "inline",
|
||||
openclaw_gateway_token: "file",
|
||||
openclaw_gateway_password: "file", // pragma: allowlist secret
|
||||
openclaw_service_managed_env_keys: "inline",
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
+3
-24
@@ -31,6 +31,7 @@ import {
|
||||
hasEnvironmentFileSource,
|
||||
hasInlineEnvironmentSource,
|
||||
isEnvironmentFileOnlySource,
|
||||
readEnvironmentValueSource,
|
||||
readManagedServiceEnvKeysFromEnvironment,
|
||||
} from "./service-managed-env.js";
|
||||
import { createGatewayLifecycleMutationReporter } from "./service-mutation.js";
|
||||
@@ -254,22 +255,6 @@ function normalizeSystemdEnvironmentKey(key: string): string | null {
|
||||
return normalizeEnvVarKey(key, { portable: true })?.toUpperCase() ?? null;
|
||||
}
|
||||
|
||||
function readSystemdEnvironmentValueSource(params: {
|
||||
environmentValueSources?: Record<string, GatewayServiceEnvironmentValueSource | undefined>;
|
||||
key: string;
|
||||
}): GatewayServiceEnvironmentValueSource | undefined {
|
||||
const normalizedKey = normalizeSystemdEnvironmentKey(params.key);
|
||||
if (!normalizedKey) {
|
||||
return undefined;
|
||||
}
|
||||
for (const [rawKey, source] of Object.entries(params.environmentValueSources ?? {})) {
|
||||
if (normalizeSystemdEnvironmentKey(rawKey) === normalizedKey) {
|
||||
return source;
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
||||
function collectSystemdInlineManagedKeys(params: {
|
||||
environment?: GatewayServiceEnv;
|
||||
environmentValueSources?: Record<string, GatewayServiceEnvironmentValueSource | undefined>;
|
||||
@@ -288,10 +273,7 @@ function collectSystemdInlineManagedKeys(params: {
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
const source = readSystemdEnvironmentValueSource({
|
||||
environmentValueSources: params.environmentValueSources,
|
||||
key: rawKey,
|
||||
});
|
||||
const source = readEnvironmentValueSource(params.environmentValueSources, rawKey);
|
||||
if (hasInlineEnvironmentSource(source) && !hasEnvironmentFileSource(source)) {
|
||||
keys.add(key);
|
||||
}
|
||||
@@ -1040,10 +1022,7 @@ async function writeSystemdUnit({
|
||||
if (typeof value !== "string") {
|
||||
return false;
|
||||
}
|
||||
const source = readSystemdEnvironmentValueSource({
|
||||
environmentValueSources,
|
||||
key,
|
||||
});
|
||||
const source = readEnvironmentValueSource(environmentValueSources, key);
|
||||
if (hasEnvironmentFileSource(source) && isUnresolvedShellReference(value)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user