mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:20:43 +00:00
refactor: source service env install planning
This commit is contained in:
@@ -3,10 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import type { AuthProfileStore } from "../agents/auth-profiles/types.js";
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
import {
|
||||
collectDurableServiceEnvVars,
|
||||
readStateDirDotEnvVars,
|
||||
} from "../config/state-dir-dotenv.js";
|
||||
import { collectDurableServiceEnvVarSources } from "../config/state-dir-dotenv.js";
|
||||
import type { OpenClawConfig } from "../config/types.js";
|
||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||
import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js";
|
||||
@@ -16,11 +13,16 @@ import {
|
||||
resolveGatewayProgramArguments,
|
||||
resolveOpenClawWrapperPath,
|
||||
} from "../daemon/program-args.js";
|
||||
import {
|
||||
addServiceEnvPlanEntries,
|
||||
compactServiceEnvPlanValueSources,
|
||||
createMutableServiceEnvPlan,
|
||||
} from "../daemon/service-env-plan.js";
|
||||
import { applyManagedServiceEnvRenderPolicy } from "../daemon/service-env-render-policy.js";
|
||||
import { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||
import {
|
||||
formatManagedServiceEnvKeys,
|
||||
readManagedServiceEnvKeysFromEnvironment,
|
||||
writeManagedServiceEnvKeysToEnvironment,
|
||||
} from "../daemon/service-managed-env.js";
|
||||
import { isNonMinimalServicePathEntry } from "../daemon/service-path-policy.js";
|
||||
import type { GatewayServiceEnvironmentValueSource } from "../daemon/service-types.js";
|
||||
@@ -395,35 +397,6 @@ function resolveGatewayInstallWorkingDirectory(params: {
|
||||
return resolveGatewayStateDir(params.env);
|
||||
}
|
||||
|
||||
function retainLaunchAgentManagedServiceEnvValues(params: {
|
||||
environment: Record<string, string | undefined>;
|
||||
durableEnvironment: Record<string, string | undefined>;
|
||||
managedServiceEnvKeys: string | undefined;
|
||||
stateDirDotEnvEnvironment: Record<string, string | undefined>;
|
||||
serviceEnvironment: Record<string, string | undefined>;
|
||||
platform: NodeJS.Platform;
|
||||
}): void {
|
||||
if (params.platform !== "darwin" || !params.serviceEnvironment.OPENCLAW_LAUNCHD_LABEL?.trim()) {
|
||||
return;
|
||||
}
|
||||
const managedKeys = readManagedServiceEnvKeysFromEnvironment({
|
||||
OPENCLAW_SERVICE_MANAGED_ENV_KEYS: params.managedServiceEnvKeys,
|
||||
});
|
||||
if (managedKeys.size === 0) {
|
||||
return;
|
||||
}
|
||||
for (const [rawKey, value] of Object.entries(params.stateDirDotEnvEnvironment)) {
|
||||
const key = normalizeEnvVarKey(rawKey, { portable: true })?.toUpperCase();
|
||||
if (!key || !managedKeys.has(key) || typeof value !== "string" || !value.trim()) {
|
||||
continue;
|
||||
}
|
||||
if (params.durableEnvironment[rawKey] !== value) {
|
||||
continue;
|
||||
}
|
||||
params.environment[rawKey] = value;
|
||||
}
|
||||
}
|
||||
|
||||
async function buildGatewayInstallEnvironment(params: {
|
||||
env: Record<string, string | undefined>;
|
||||
config?: OpenClawConfig;
|
||||
@@ -440,11 +413,11 @@ async function buildGatewayInstallEnvironment(params: {
|
||||
environment: Record<string, string | undefined>;
|
||||
environmentValueSources: Record<string, GatewayServiceEnvironmentValueSource | undefined>;
|
||||
}> {
|
||||
const stateDirDotEnvEnvironment = readStateDirDotEnvVars(params.env);
|
||||
const durableEnvironment = collectDurableServiceEnvVars({
|
||||
env: params.env,
|
||||
config: params.config,
|
||||
});
|
||||
const { stateDirDotEnvEnvironment, configEnvironment, durableEnvironment } =
|
||||
collectDurableServiceEnvVarSources({
|
||||
env: params.env,
|
||||
config: params.config,
|
||||
});
|
||||
const configSecretRefEnvironment = collectConfigSecretRefServiceEnvVars({
|
||||
env: params.env,
|
||||
config: params.config,
|
||||
@@ -466,51 +439,33 @@ async function buildGatewayInstallEnvironment(params: {
|
||||
params.existingEnvironment,
|
||||
readManagedServiceEnvKeysFromEnvironment(params.existingEnvironment),
|
||||
);
|
||||
const environment: Record<string, string | undefined> = {
|
||||
...preservedExistingEnvironment,
|
||||
...durableEnvironment,
|
||||
...configSecretRefEnvironment,
|
||||
...execSecretRefPassEnvEnvironment,
|
||||
...authProfileEnvironment,
|
||||
};
|
||||
const environmentValueSources: Record<string, GatewayServiceEnvironmentValueSource | undefined> =
|
||||
{};
|
||||
for (const rawKey of Object.keys(preservedExistingEnvironment)) {
|
||||
const normalizedKey = normalizeEnvVarKey(rawKey, { portable: true })?.toUpperCase();
|
||||
environmentValueSources[rawKey] = normalizedKey
|
||||
? (readExistingEnvironmentValueSource({
|
||||
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
|
||||
normalizedKey,
|
||||
}) ?? "inline")
|
||||
: "inline";
|
||||
}
|
||||
for (const key of Object.keys({
|
||||
...durableEnvironment,
|
||||
...configSecretRefEnvironment,
|
||||
...execSecretRefPassEnvEnvironment,
|
||||
...authProfileEnvironment,
|
||||
})) {
|
||||
environmentValueSources[key] = "inline";
|
||||
}
|
||||
const plan = createMutableServiceEnvPlan();
|
||||
addServiceEnvPlanEntries(plan, preservedExistingEnvironment, {
|
||||
source: "existing-preserved",
|
||||
valueSource: ({ normalizedKey }) =>
|
||||
readExistingEnvironmentValueSource({
|
||||
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
|
||||
normalizedKey,
|
||||
}) ?? "inline",
|
||||
});
|
||||
addServiceEnvPlanEntries(plan, stateDirDotEnvEnvironment, { source: "state-dotenv" });
|
||||
addServiceEnvPlanEntries(plan, configEnvironment, { source: "config-env" });
|
||||
addServiceEnvPlanEntries(plan, configSecretRefEnvironment, { source: "config-secretref-env" });
|
||||
addServiceEnvPlanEntries(plan, execSecretRefPassEnvEnvironment, { source: "exec-passenv" });
|
||||
addServiceEnvPlanEntries(plan, authProfileEnvironment, { source: "auth-profile-env" });
|
||||
const managedServiceEnvKeys = formatManagedServiceEnvKeys(durableEnvironment, {
|
||||
omitKeys: Object.keys(params.serviceEnvironment),
|
||||
});
|
||||
writeManagedServiceEnvKeysToEnvironment(environment, managedServiceEnvKeys);
|
||||
retainLaunchAgentManagedServiceEnvValues({
|
||||
environment,
|
||||
durableEnvironment,
|
||||
applyManagedServiceEnvRenderPolicy({
|
||||
plan,
|
||||
managedServiceEnvKeys,
|
||||
stateDirDotEnvEnvironment,
|
||||
serviceEnvironment: params.serviceEnvironment,
|
||||
platform: params.platform,
|
||||
});
|
||||
if (environment.OPENCLAW_SERVICE_MANAGED_ENV_KEYS) {
|
||||
environmentValueSources.OPENCLAW_SERVICE_MANAGED_ENV_KEYS = "inline";
|
||||
}
|
||||
Object.assign(environment, params.serviceEnvironment);
|
||||
for (const key of Object.keys(params.serviceEnvironment)) {
|
||||
environmentValueSources[key] = "inline";
|
||||
}
|
||||
addServiceEnvPlanEntries(plan, params.serviceEnvironment, {
|
||||
source: "service-generated",
|
||||
includeRawKeys: true,
|
||||
});
|
||||
const mergedPath = mergeServicePath(
|
||||
params.serviceEnvironment.PATH,
|
||||
params.existingEnvironment?.PATH,
|
||||
@@ -518,15 +473,14 @@ async function buildGatewayInstallEnvironment(params: {
|
||||
params.platform,
|
||||
);
|
||||
if (mergedPath) {
|
||||
environment.PATH = mergedPath;
|
||||
environmentValueSources.PATH = "inline";
|
||||
plan.environment.PATH = mergedPath;
|
||||
plan.environmentValueSources.PATH = "inline";
|
||||
}
|
||||
for (const key of Object.keys(environmentValueSources)) {
|
||||
if (!Object.hasOwn(environment, key)) {
|
||||
delete environmentValueSources[key];
|
||||
}
|
||||
}
|
||||
return { environment, environmentValueSources };
|
||||
compactServiceEnvPlanValueSources(plan);
|
||||
return {
|
||||
environment: plan.environment,
|
||||
environmentValueSources: plan.environmentValueSources,
|
||||
};
|
||||
}
|
||||
|
||||
export async function buildGatewayInstallPlan(params: {
|
||||
|
||||
@@ -54,6 +54,28 @@ export function readStateDirDotEnvVars(
|
||||
return readStateDirDotEnvVarsFromStateDir(stateDir);
|
||||
}
|
||||
|
||||
export type DurableServiceEnvVarSources = {
|
||||
stateDirDotEnvEnvironment: Record<string, string>;
|
||||
configEnvironment: Record<string, string>;
|
||||
durableEnvironment: Record<string, string>;
|
||||
};
|
||||
|
||||
export function collectDurableServiceEnvVarSources(params: {
|
||||
env: Record<string, string | undefined>;
|
||||
config?: OpenClawConfig;
|
||||
}): DurableServiceEnvVarSources {
|
||||
const stateDirDotEnvEnvironment = readStateDirDotEnvVars(params.env);
|
||||
const configEnvironment = collectConfigServiceEnvVars(params.config);
|
||||
return {
|
||||
stateDirDotEnvEnvironment,
|
||||
configEnvironment,
|
||||
durableEnvironment: {
|
||||
...stateDirDotEnvEnvironment,
|
||||
...configEnvironment,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Durable service env sources survive beyond the invoking shell and are safe to
|
||||
* persist into owner-only gateway service environment sources.
|
||||
@@ -66,8 +88,5 @@ export function collectDurableServiceEnvVars(params: {
|
||||
env: Record<string, string | undefined>;
|
||||
config?: OpenClawConfig;
|
||||
}): Record<string, string> {
|
||||
return {
|
||||
...readStateDirDotEnvVars(params.env),
|
||||
...collectConfigServiceEnvVars(params.config),
|
||||
};
|
||||
return collectDurableServiceEnvVarSources(params).durableEnvironment;
|
||||
}
|
||||
|
||||
86
src/daemon/service-env-plan.ts
Normal file
86
src/daemon/service-env-plan.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
import { normalizeEnvVarKey } from "../infra/host-env-security.js";
|
||||
import type { GatewayServiceEnvironmentValueSource } from "./service-types.js";
|
||||
|
||||
export type ServiceEnvSource =
|
||||
| "state-dotenv"
|
||||
| "config-env"
|
||||
| "config-secretref-env"
|
||||
| "exec-passenv"
|
||||
| "auth-profile-env"
|
||||
| "existing-preserved"
|
||||
| "service-generated";
|
||||
|
||||
export type ServiceEnvPlanEntry = {
|
||||
rawKey: string;
|
||||
normalizedKey: string;
|
||||
value: string;
|
||||
source: ServiceEnvSource;
|
||||
};
|
||||
|
||||
export type MutableServiceEnvPlan = {
|
||||
environment: Record<string, string | undefined>;
|
||||
environmentValueSources: Record<string, GatewayServiceEnvironmentValueSource | undefined>;
|
||||
entriesByNormalizedKey: Map<string, ServiceEnvPlanEntry>;
|
||||
};
|
||||
|
||||
export function createMutableServiceEnvPlan(): MutableServiceEnvPlan {
|
||||
return {
|
||||
environment: {},
|
||||
environmentValueSources: {},
|
||||
entriesByNormalizedKey: new Map(),
|
||||
};
|
||||
}
|
||||
|
||||
export function normalizeServiceEnvPlanKey(rawKey: string): string | undefined {
|
||||
return normalizeEnvVarKey(rawKey, { portable: true })?.toUpperCase();
|
||||
}
|
||||
|
||||
export function addServiceEnvPlanEntries(
|
||||
plan: MutableServiceEnvPlan,
|
||||
entries: Record<string, string | undefined>,
|
||||
options: {
|
||||
source: ServiceEnvSource;
|
||||
includeRawKeys?: boolean;
|
||||
valueSource?:
|
||||
| GatewayServiceEnvironmentValueSource
|
||||
| ((params: {
|
||||
rawKey: string;
|
||||
normalizedKey: string;
|
||||
}) => GatewayServiceEnvironmentValueSource | undefined);
|
||||
},
|
||||
): void {
|
||||
for (const [rawKey, rawValue] of Object.entries(entries)) {
|
||||
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
||||
if (options.includeRawKeys) {
|
||||
plan.environment[rawKey] = rawValue;
|
||||
plan.environmentValueSources[rawKey] = "inline";
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const value = rawValue;
|
||||
const normalizedKey = normalizeServiceEnvPlanKey(rawKey);
|
||||
if (!normalizedKey) {
|
||||
continue;
|
||||
}
|
||||
plan.environment[rawKey] = value;
|
||||
const valueSource =
|
||||
typeof options.valueSource === "function"
|
||||
? options.valueSource({ rawKey, normalizedKey })
|
||||
: options.valueSource;
|
||||
plan.environmentValueSources[rawKey] = valueSource ?? "inline";
|
||||
plan.entriesByNormalizedKey.set(normalizedKey, {
|
||||
rawKey,
|
||||
normalizedKey,
|
||||
value,
|
||||
source: options.source,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export function compactServiceEnvPlanValueSources(plan: MutableServiceEnvPlan): void {
|
||||
for (const key of Object.keys(plan.environmentValueSources)) {
|
||||
if (!Object.hasOwn(plan.environment, key)) {
|
||||
delete plan.environmentValueSources[key];
|
||||
}
|
||||
}
|
||||
}
|
||||
43
src/daemon/service-env-render-policy.ts
Normal file
43
src/daemon/service-env-render-policy.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import type { MutableServiceEnvPlan } from "./service-env-plan.js";
|
||||
import {
|
||||
readManagedServiceEnvKeysFromEnvironment,
|
||||
writeManagedServiceEnvKeysToEnvironment,
|
||||
} from "./service-managed-env.js";
|
||||
|
||||
function isLaunchAgentServiceEnvironment(params: {
|
||||
platform: NodeJS.Platform;
|
||||
serviceEnvironment: Record<string, string | undefined>;
|
||||
}): boolean {
|
||||
return (
|
||||
params.platform === "darwin" &&
|
||||
Boolean(params.serviceEnvironment.OPENCLAW_LAUNCHD_LABEL?.trim())
|
||||
);
|
||||
}
|
||||
|
||||
export function applyManagedServiceEnvRenderPolicy(params: {
|
||||
plan: MutableServiceEnvPlan;
|
||||
managedServiceEnvKeys: string | undefined;
|
||||
serviceEnvironment: Record<string, string | undefined>;
|
||||
platform: NodeJS.Platform;
|
||||
}): void {
|
||||
writeManagedServiceEnvKeysToEnvironment(params.plan.environment, params.managedServiceEnvKeys);
|
||||
if (params.plan.environment.OPENCLAW_SERVICE_MANAGED_ENV_KEYS) {
|
||||
params.plan.environmentValueSources.OPENCLAW_SERVICE_MANAGED_ENV_KEYS = "inline";
|
||||
}
|
||||
if (!isLaunchAgentServiceEnvironment(params)) {
|
||||
return;
|
||||
}
|
||||
const managedKeys = readManagedServiceEnvKeysFromEnvironment({
|
||||
OPENCLAW_SERVICE_MANAGED_ENV_KEYS: params.managedServiceEnvKeys,
|
||||
});
|
||||
if (managedKeys.size === 0) {
|
||||
return;
|
||||
}
|
||||
for (const entry of params.plan.entriesByNormalizedKey.values()) {
|
||||
if (entry.source !== "state-dotenv" || !managedKeys.has(entry.normalizedKey)) {
|
||||
continue;
|
||||
}
|
||||
params.plan.environment[entry.rawKey] = entry.value;
|
||||
params.plan.environmentValueSources[entry.rawKey] = "inline";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user