mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 20:40: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 path from "node:path";
|
||||||
import type { AuthProfileStore } from "../agents/auth-profiles/types.js";
|
import type { AuthProfileStore } from "../agents/auth-profiles/types.js";
|
||||||
import { formatCliCommand } from "../cli/command-format.js";
|
import { formatCliCommand } from "../cli/command-format.js";
|
||||||
import {
|
import { collectDurableServiceEnvVarSources } from "../config/state-dir-dotenv.js";
|
||||||
collectDurableServiceEnvVars,
|
|
||||||
readStateDirDotEnvVars,
|
|
||||||
} from "../config/state-dir-dotenv.js";
|
|
||||||
import type { OpenClawConfig } from "../config/types.js";
|
import type { OpenClawConfig } from "../config/types.js";
|
||||||
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
import { resolveSecretInputRef } from "../config/types.secrets.js";
|
||||||
import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js";
|
import { resolveGatewayLaunchAgentLabel } from "../daemon/constants.js";
|
||||||
@@ -16,11 +13,16 @@ import {
|
|||||||
resolveGatewayProgramArguments,
|
resolveGatewayProgramArguments,
|
||||||
resolveOpenClawWrapperPath,
|
resolveOpenClawWrapperPath,
|
||||||
} from "../daemon/program-args.js";
|
} 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 { buildServiceEnvironment } from "../daemon/service-env.js";
|
||||||
import {
|
import {
|
||||||
formatManagedServiceEnvKeys,
|
formatManagedServiceEnvKeys,
|
||||||
readManagedServiceEnvKeysFromEnvironment,
|
readManagedServiceEnvKeysFromEnvironment,
|
||||||
writeManagedServiceEnvKeysToEnvironment,
|
|
||||||
} from "../daemon/service-managed-env.js";
|
} from "../daemon/service-managed-env.js";
|
||||||
import { isNonMinimalServicePathEntry } from "../daemon/service-path-policy.js";
|
import { isNonMinimalServicePathEntry } from "../daemon/service-path-policy.js";
|
||||||
import type { GatewayServiceEnvironmentValueSource } from "../daemon/service-types.js";
|
import type { GatewayServiceEnvironmentValueSource } from "../daemon/service-types.js";
|
||||||
@@ -395,35 +397,6 @@ function resolveGatewayInstallWorkingDirectory(params: {
|
|||||||
return resolveGatewayStateDir(params.env);
|
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: {
|
async function buildGatewayInstallEnvironment(params: {
|
||||||
env: Record<string, string | undefined>;
|
env: Record<string, string | undefined>;
|
||||||
config?: OpenClawConfig;
|
config?: OpenClawConfig;
|
||||||
@@ -440,11 +413,11 @@ async function buildGatewayInstallEnvironment(params: {
|
|||||||
environment: Record<string, string | undefined>;
|
environment: Record<string, string | undefined>;
|
||||||
environmentValueSources: Record<string, GatewayServiceEnvironmentValueSource | undefined>;
|
environmentValueSources: Record<string, GatewayServiceEnvironmentValueSource | undefined>;
|
||||||
}> {
|
}> {
|
||||||
const stateDirDotEnvEnvironment = readStateDirDotEnvVars(params.env);
|
const { stateDirDotEnvEnvironment, configEnvironment, durableEnvironment } =
|
||||||
const durableEnvironment = collectDurableServiceEnvVars({
|
collectDurableServiceEnvVarSources({
|
||||||
env: params.env,
|
env: params.env,
|
||||||
config: params.config,
|
config: params.config,
|
||||||
});
|
});
|
||||||
const configSecretRefEnvironment = collectConfigSecretRefServiceEnvVars({
|
const configSecretRefEnvironment = collectConfigSecretRefServiceEnvVars({
|
||||||
env: params.env,
|
env: params.env,
|
||||||
config: params.config,
|
config: params.config,
|
||||||
@@ -466,51 +439,33 @@ async function buildGatewayInstallEnvironment(params: {
|
|||||||
params.existingEnvironment,
|
params.existingEnvironment,
|
||||||
readManagedServiceEnvKeysFromEnvironment(params.existingEnvironment),
|
readManagedServiceEnvKeysFromEnvironment(params.existingEnvironment),
|
||||||
);
|
);
|
||||||
const environment: Record<string, string | undefined> = {
|
const plan = createMutableServiceEnvPlan();
|
||||||
...preservedExistingEnvironment,
|
addServiceEnvPlanEntries(plan, preservedExistingEnvironment, {
|
||||||
...durableEnvironment,
|
source: "existing-preserved",
|
||||||
...configSecretRefEnvironment,
|
valueSource: ({ normalizedKey }) =>
|
||||||
...execSecretRefPassEnvEnvironment,
|
readExistingEnvironmentValueSource({
|
||||||
...authProfileEnvironment,
|
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
|
||||||
};
|
normalizedKey,
|
||||||
const environmentValueSources: Record<string, GatewayServiceEnvironmentValueSource | undefined> =
|
}) ?? "inline",
|
||||||
{};
|
});
|
||||||
for (const rawKey of Object.keys(preservedExistingEnvironment)) {
|
addServiceEnvPlanEntries(plan, stateDirDotEnvEnvironment, { source: "state-dotenv" });
|
||||||
const normalizedKey = normalizeEnvVarKey(rawKey, { portable: true })?.toUpperCase();
|
addServiceEnvPlanEntries(plan, configEnvironment, { source: "config-env" });
|
||||||
environmentValueSources[rawKey] = normalizedKey
|
addServiceEnvPlanEntries(plan, configSecretRefEnvironment, { source: "config-secretref-env" });
|
||||||
? (readExistingEnvironmentValueSource({
|
addServiceEnvPlanEntries(plan, execSecretRefPassEnvEnvironment, { source: "exec-passenv" });
|
||||||
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
|
addServiceEnvPlanEntries(plan, authProfileEnvironment, { source: "auth-profile-env" });
|
||||||
normalizedKey,
|
|
||||||
}) ?? "inline")
|
|
||||||
: "inline";
|
|
||||||
}
|
|
||||||
for (const key of Object.keys({
|
|
||||||
...durableEnvironment,
|
|
||||||
...configSecretRefEnvironment,
|
|
||||||
...execSecretRefPassEnvEnvironment,
|
|
||||||
...authProfileEnvironment,
|
|
||||||
})) {
|
|
||||||
environmentValueSources[key] = "inline";
|
|
||||||
}
|
|
||||||
const managedServiceEnvKeys = formatManagedServiceEnvKeys(durableEnvironment, {
|
const managedServiceEnvKeys = formatManagedServiceEnvKeys(durableEnvironment, {
|
||||||
omitKeys: Object.keys(params.serviceEnvironment),
|
omitKeys: Object.keys(params.serviceEnvironment),
|
||||||
});
|
});
|
||||||
writeManagedServiceEnvKeysToEnvironment(environment, managedServiceEnvKeys);
|
applyManagedServiceEnvRenderPolicy({
|
||||||
retainLaunchAgentManagedServiceEnvValues({
|
plan,
|
||||||
environment,
|
|
||||||
durableEnvironment,
|
|
||||||
managedServiceEnvKeys,
|
managedServiceEnvKeys,
|
||||||
stateDirDotEnvEnvironment,
|
|
||||||
serviceEnvironment: params.serviceEnvironment,
|
serviceEnvironment: params.serviceEnvironment,
|
||||||
platform: params.platform,
|
platform: params.platform,
|
||||||
});
|
});
|
||||||
if (environment.OPENCLAW_SERVICE_MANAGED_ENV_KEYS) {
|
addServiceEnvPlanEntries(plan, params.serviceEnvironment, {
|
||||||
environmentValueSources.OPENCLAW_SERVICE_MANAGED_ENV_KEYS = "inline";
|
source: "service-generated",
|
||||||
}
|
includeRawKeys: true,
|
||||||
Object.assign(environment, params.serviceEnvironment);
|
});
|
||||||
for (const key of Object.keys(params.serviceEnvironment)) {
|
|
||||||
environmentValueSources[key] = "inline";
|
|
||||||
}
|
|
||||||
const mergedPath = mergeServicePath(
|
const mergedPath = mergeServicePath(
|
||||||
params.serviceEnvironment.PATH,
|
params.serviceEnvironment.PATH,
|
||||||
params.existingEnvironment?.PATH,
|
params.existingEnvironment?.PATH,
|
||||||
@@ -518,15 +473,14 @@ async function buildGatewayInstallEnvironment(params: {
|
|||||||
params.platform,
|
params.platform,
|
||||||
);
|
);
|
||||||
if (mergedPath) {
|
if (mergedPath) {
|
||||||
environment.PATH = mergedPath;
|
plan.environment.PATH = mergedPath;
|
||||||
environmentValueSources.PATH = "inline";
|
plan.environmentValueSources.PATH = "inline";
|
||||||
}
|
}
|
||||||
for (const key of Object.keys(environmentValueSources)) {
|
compactServiceEnvPlanValueSources(plan);
|
||||||
if (!Object.hasOwn(environment, key)) {
|
return {
|
||||||
delete environmentValueSources[key];
|
environment: plan.environment,
|
||||||
}
|
environmentValueSources: plan.environmentValueSources,
|
||||||
}
|
};
|
||||||
return { environment, environmentValueSources };
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function buildGatewayInstallPlan(params: {
|
export async function buildGatewayInstallPlan(params: {
|
||||||
|
|||||||
@@ -54,6 +54,28 @@ export function readStateDirDotEnvVars(
|
|||||||
return readStateDirDotEnvVarsFromStateDir(stateDir);
|
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
|
* Durable service env sources survive beyond the invoking shell and are safe to
|
||||||
* persist into owner-only gateway service environment sources.
|
* persist into owner-only gateway service environment sources.
|
||||||
@@ -66,8 +88,5 @@ export function collectDurableServiceEnvVars(params: {
|
|||||||
env: Record<string, string | undefined>;
|
env: Record<string, string | undefined>;
|
||||||
config?: OpenClawConfig;
|
config?: OpenClawConfig;
|
||||||
}): Record<string, string> {
|
}): Record<string, string> {
|
||||||
return {
|
return collectDurableServiceEnvVarSources(params).durableEnvironment;
|
||||||
...readStateDirDotEnvVars(params.env),
|
|
||||||
...collectConfigServiceEnvVars(params.config),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
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