| import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce"; |
| import * as startupRepair from "../../commands/doctor/shared/automatic-startup-config-repair.js"; |
| import { |
| cloneEnvWithPlatformSemantics, |
| resetPublishedConfigRuntimeEnv, |
| } from "../../config/config-env-vars.js"; |
| |
| import { ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS_ENV } from "../../config/future-version-guard.js"; |
| import { GATEWAY_CONFIG_SELECTION_ENV_KEYS } from "../../config/gateway-env-selection.js"; |
| import { CONFIG_AUDIT_STORE_LABEL } from "../../config/io.audit.js"; |
| import type { ConfigFileSnapshot } from "../../config/types.js"; |
| import { ExitError, type RuntimeEnv } from "../../runtime.js"; |
| import { withArtifactPreservingStateReads } from "../../state/openclaw-state-db-readonly.js"; |
| import { formatCliCommand } from "../command-format.js"; |
| import type { GatewayRunPreBootstrapOptions } from "./future-config-guard.js"; |
| import { enforceGatewayRunFutureConfigGuard } from "./future-config-guard.js"; |
| import type { GatewayRunOpts } from "./run-options.js"; |
| import { getGatewayRunRuntimeHooks } from "./runtime-hooks.js"; |
|
|
| type GatewayRunGuardParams = { |
| opts: GatewayRunPreBootstrapOptions & Pick<GatewayRunOpts, "allowUnconfigured" | "dev">; |
| runtime: RuntimeEnv; |
| }; |
|
|
| type GatewayRunEnvironmentSelection = { |
| after: Record<string, string | undefined>; |
| before: Record<string, string | undefined>; |
| }; |
|
|
| type PreparedGatewayRunReset = { |
| selectionEnvironment: Record<string, string | undefined>; |
| selectionSignature: string; |
| snapshot: ConfigFileSnapshot; |
| }; |
|
|
| let selectedGatewayRunEnvironment: GatewayRunEnvironmentSelection | undefined; |
| let appliedGatewayRunConfigEnvironment: GatewayRunEnvironmentSelection | undefined; |
| let lastGuardedGatewayRunSnapshot: ConfigFileSnapshot | undefined; |
| let preparedGatewayRunBootstrap: |
| | (Pick<GatewayRunOpts, "allowUnconfigured" | "dev"> & { snapshot: ConfigFileSnapshot }) |
| | undefined; |
| let preparedGatewayRunStateWasPristine = false; |
| let preparedGatewayRunCoreStateWasPristine = false; |
| let preparedGatewayRunReset: PreparedGatewayRunReset | undefined; |
| let gatewayRunTargetSelectedByConfig = false; |
|
|
| export function getGatewayStartGuardErrors(params: { |
| allowUnconfigured?: boolean; |
| configExists: boolean; |
| mode: string | undefined; |
| }): string[] { |
| if ( |
| (params.allowUnconfigured ?? preparedGatewayRunBootstrap?.allowUnconfigured) || |
| params.mode === "local" || |
| (!params.configExists && preparedGatewayRunBootstrap?.dev) |
| ) { |
| return []; |
| } |
| if (!params.configExists) { |
| return [ |
| `Missing config. Run \`${formatCliCommand("openclaw setup")}\` or set gateway.mode=local (or pass --allow-unconfigured).`, |
| ]; |
| } |
| return [ |
| params.mode === undefined |
| ? [ |
| "Gateway start blocked: existing config is missing gateway.mode.", |
| "Treat this as suspicious or clobbered config.", |
| `Re-run \`${formatCliCommand("openclaw onboard --mode local")}\` or \`${formatCliCommand("openclaw setup")}\`, set gateway.mode=local manually, or pass --allow-unconfigured.`, |
| ].join(" ") |
| : `Gateway start blocked: set gateway.mode=local (current: ${params.mode}) or pass --allow-unconfigured.`, |
| `Config write audit: ${CONFIG_AUDIT_STORE_LABEL}`, |
| ]; |
| } |
|
|
| async function pinGatewayRunRuntimePaths(): Promise<void> { |
| const [{ pinRuntimePaths }, { pinConfigDir }] = await Promise.all([ |
| import("../../config/paths.js"), |
| import("../../utils.js"), |
| ]); |
| pinRuntimePaths(process.env); |
| pinConfigDir(process.env); |
| } |
|
|
| const GATEWAY_RESET_SELECTION_ENV_KEYS = new Set([ |
| ...GATEWAY_CONFIG_SELECTION_ENV_KEYS, |
| "OPENCLAW_PROFILE", |
| "OPENCLAW_WORKSPACE_DIR", |
| ]); |
|
|
| function resolveGatewayConfigSelectionSignature(env: NodeJS.ProcessEnv): string { |
| return JSON.stringify([...GATEWAY_CONFIG_SELECTION_ENV_KEYS].map((key) => [key, env[key]])); |
| } |
|
|
| function snapshotGatewayConfigSelectionEnvironment( |
| env: NodeJS.ProcessEnv, |
| ): Record<string, string | undefined> { |
| return Object.fromEntries([...GATEWAY_CONFIG_SELECTION_ENV_KEYS].map((key) => [key, env[key]])); |
| } |
|
|
| function restoreGatewayConfigSelectionEnvironment( |
| snapshot: Record<string, string | undefined>, |
| ): void { |
| for (const key of GATEWAY_CONFIG_SELECTION_ENV_KEYS) { |
| const value = snapshot[key]; |
| if (value === undefined) { |
| delete process.env[key]; |
| } else { |
| process.env[key] = value; |
| } |
| } |
| } |
|
|
| function resolveGatewayRunDotEnvPaths(params: { |
| env: NodeJS.ProcessEnv; |
| join: (...paths: string[]) => string; |
| resolve: (path: string) => string; |
| resolveConfigDir: (env: NodeJS.ProcessEnv) => string; |
| resolveStateDir: (env: NodeJS.ProcessEnv) => string; |
| }): { additionalEnvPaths?: string[]; stateEnvPath: string } { |
| const stateEnvPath = params.join(params.resolveStateDir(params.env), ".env"); |
| const configEnvPath = params.join(params.resolveConfigDir(params.env), ".env"); |
| return params.resolve(stateEnvPath) === params.resolve(configEnvPath) |
| ? { stateEnvPath } |
| : { additionalEnvPaths: [configEnvPath], stateEnvPath }; |
| } |
|
|
| function resolveInvocationDestructiveOverride(): string | undefined { |
| if (process.env.OPENCLAW_SERVICE_MARKER?.trim()) { |
| delete process.env[ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS_ENV]; |
| return undefined; |
| } |
| return process.env[ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS_ENV]; |
| } |
|
|
| function applyInvocationDestructiveOverride(value: string | undefined): void { |
| if (process.env.OPENCLAW_SERVICE_MARKER?.trim() || value === undefined) { |
| delete process.env[ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS_ENV]; |
| } else { |
| process.env[ALLOW_OLDER_BINARY_DESTRUCTIVE_ACTIONS_ENV] = value; |
| } |
| } |
|
|
| function restoreGatewayEnvChanges(params: { |
| before: Record<string, string | undefined>; |
| after: Record<string, string | undefined>; |
| preservedKeys?: ReadonlySet<string>; |
| }): void { |
| const keys = new Set([...Object.keys(params.before), ...Object.keys(params.after)]); |
| for (const key of keys) { |
| const preservedKey = process.platform === "win32" ? key.toUpperCase() : key; |
| if (params.preservedKeys?.has(preservedKey)) { |
| continue; |
| } |
| if (params.before[key] === params.after[key] || process.env[key] !== params.after[key]) { |
| continue; |
| } |
| const previous = params.before[key]; |
| if (previous === undefined) { |
| delete process.env[key]; |
| } else { |
| process.env[key] = previous; |
| } |
| } |
| } |
|
|
| function restoreSupersededGatewaySelectionEnv(params: { |
| beforeCurrentPass: Record<string, string | undefined>; |
| environmentSelection?: GatewayRunEnvironmentSelection; |
| }): void { |
| restoreGatewayEnvChanges({ |
| before: params.beforeCurrentPass, |
| after: { ...process.env }, |
| preservedKeys: GATEWAY_CONFIG_SELECTION_ENV_KEYS, |
| }); |
| if (params.environmentSelection) { |
| |
| |
| restoreGatewayEnvChanges({ |
| before: params.environmentSelection.before, |
| after: params.environmentSelection.after, |
| preservedKeys: GATEWAY_CONFIG_SELECTION_ENV_KEYS, |
| }); |
| } |
| } |
|
|
| function restoreAppliedGatewayRunConfigEnvironment(preserveSelection = true): void { |
| const applied = appliedGatewayRunConfigEnvironment; |
| appliedGatewayRunConfigEnvironment = undefined; |
| if (!applied) { |
| return; |
| } |
| restoreGatewayEnvChanges({ |
| before: applied.before, |
| after: applied.after, |
| ...(preserveSelection ? { preservedKeys: GATEWAY_CONFIG_SELECTION_ENV_KEYS } : {}), |
| }); |
| } |
|
|
| async function readGuardedGatewayRunConfig( |
| params: GatewayRunGuardParams, |
| ): Promise<ConfigFileSnapshot | null> { |
| const { readConfigFileSnapshot } = await import("../../config/config.js"); |
| const { createConfigIO } = await import("../../config/io.factory.js"); |
| return await withArtifactPreservingStateReads(async () => { |
| const current = await readConfigFileSnapshot({ |
| isolateEnv: true, |
| observe: false, |
| pluginValidation: "core-only", |
| }); |
| const guard = (snapshot: ConfigFileSnapshot) => |
| enforceGatewayRunFutureConfigGuard({ ...params, snapshot }); |
| if (!guard(current)) { |
| return null; |
| } |
| const recovery = await createConfigIO({ |
| configPath: current.path, |
| env: cloneEnvWithPlatformSemantics(process.env), |
| observe: false, |
| pluginValidation: "core-only", |
| }).prepareConfigRecovery(current); |
| return recovery ? (guard(recovery.snapshot) ? recovery.snapshot : null) : current; |
| }); |
| } |
|
|
| async function isSameGatewayRunConfigSnapshot( |
| expected: ConfigFileSnapshot, |
| current: ConfigFileSnapshot, |
| options: { allowPathChange?: boolean } = {}, |
| ): Promise<boolean> { |
| const { hashRuntimeConfigValue } = await import("../../config/runtime-snapshot.js"); |
| return ( |
| (options.allowPathChange || current.path === expected.path) && |
| current.exists === expected.exists && |
| (current.hash ?? current.raw) === (expected.hash ?? expected.raw) && |
| hashRuntimeConfigValue(current.sourceConfig) === hashRuntimeConfigValue(expected.sourceConfig) |
| ); |
| } |
|
|
| function resolveGatewayConfigSelectionDeclarationSignature( |
| entries: Record<string, string>, |
| ): string { |
| const normalized = new Map( |
| Object.entries(entries).map(([key, value]) => [key.toUpperCase(), value]), |
| ); |
| return JSON.stringify( |
| [...GATEWAY_CONFIG_SELECTION_ENV_KEYS].map((key) => [key, normalized.get(key)]), |
| ); |
| } |
|
|
| async function guardGatewayRunSelectedConfig( |
| params: GatewayRunGuardParams & { |
| environmentSelection?: GatewayRunEnvironmentSelection; |
| }, |
| ): Promise<boolean> { |
| lastGuardedGatewayRunSnapshot = undefined; |
| const [ |
| path, |
| { applyConfigEnvVars, isConfigRuntimeEnvVarAllowed }, |
| { loadGlobalRuntimeDotEnvFiles }, |
| { normalizeEnv }, |
| { normalizeStateDirEnv, resolveStateDir }, |
| { resolveConfigDir }, |
| { collectEnvSecretRefIds }, |
| { clearMissingManagedServiceEnvKeys, readManagedSystemdServiceEnvKeysFromEnvironment }, |
| ] = await Promise.all([ |
| import("node:path"), |
| import("../../config/config-env-vars.js"), |
| import("../../infra/dotenv-global.js"), |
| import("../../infra/env.js"), |
| import("../../config/paths.js"), |
| import("../../utils.js"), |
| import("../../config/resolution-facts.js"), |
| import("../../daemon/service-managed-env.js"), |
| ]); |
| const invocationDestructiveOverride = resolveInvocationDestructiveOverride(); |
| if (params.environmentSelection) { |
| restoreAppliedGatewayRunConfigEnvironment(); |
| restoreGatewayEnvChanges({ |
| before: params.environmentSelection.before, |
| after: params.environmentSelection.after, |
| preservedKeys: GATEWAY_CONFIG_SELECTION_ENV_KEYS, |
| }); |
| } |
| const applyTrustedGatewayEnv = () => { |
| normalizeStateDirEnv(process.env); |
| const loaded = loadGlobalRuntimeDotEnvFiles({ |
| ...(gatewayRunTargetSelectedByConfig ? { entryFilter: isConfigRuntimeEnvVarAllowed } : {}), |
| overrideKeys: readManagedSystemdServiceEnvKeysFromEnvironment(process.env), |
| quiet: true, |
| ...resolveGatewayRunDotEnvPaths({ |
| env: process.env, |
| join: path.join, |
| resolve: path.resolve, |
| resolveConfigDir, |
| resolveStateDir, |
| }), |
| }); |
| normalizeStateDirEnv(process.env); |
| normalizeEnv(); |
| applyInvocationDestructiveOverride(invocationDestructiveOverride); |
| return loaded; |
| }; |
| const applySelectedConfigEnv = (snapshot: ConfigFileSnapshot) => { |
| restoreAppliedGatewayRunConfigEnvironment(params.opts.reset !== true); |
| if (snapshot.valid && params.opts.reset !== true) { |
| const envBeforeApply = { ...process.env }; |
| applyConfigEnvVars(snapshot.sourceConfig, process.env); |
| normalizeStateDirEnv(process.env); |
| normalizeEnv(); |
| appliedGatewayRunConfigEnvironment = { |
| before: envBeforeApply, |
| after: { ...process.env }, |
| }; |
| } |
| applyInvocationDestructiveOverride(invocationDestructiveOverride); |
| }; |
| for (;;) { |
| const envBeforeTrustedApply = { ...process.env }; |
| const trustedSelectionSignature = resolveGatewayConfigSelectionSignature(process.env); |
| const trustedEnvLoad = applyTrustedGatewayEnv(); |
| if (resolveGatewayConfigSelectionSignature(process.env) !== trustedSelectionSignature) { |
| const stateEnvSelectedTarget = trustedEnvLoad.stateEnvAppliedKeys.some((key) => |
| GATEWAY_CONFIG_SELECTION_ENV_KEYS.has(key.toUpperCase()), |
| ); |
| if (stateEnvSelectedTarget) { |
| const fallbackSelectorKeys = new Set( |
| trustedEnvLoad.gatewayEnvAppliedKeys |
| .map((key) => key.toUpperCase()) |
| .filter((key) => GATEWAY_CONFIG_SELECTION_ENV_KEYS.has(key)), |
| ); |
| restoreGatewayEnvChanges({ |
| before: envBeforeTrustedApply, |
| after: { ...process.env }, |
| preservedKeys: new Set( |
| [...GATEWAY_CONFIG_SELECTION_ENV_KEYS].filter((key) => !fallbackSelectorKeys.has(key)), |
| ), |
| }); |
| } |
| |
| |
| restoreSupersededGatewaySelectionEnv({ |
| beforeCurrentPass: envBeforeTrustedApply, |
| environmentSelection: params.environmentSelection, |
| }); |
| continue; |
| } |
| const snapshot = await readGuardedGatewayRunConfig(params); |
| if (!snapshot) { |
| return false; |
| } |
| if (!snapshot.valid && params.opts.reset) { |
| |
| |
| |
| lastGuardedGatewayRunSnapshot = snapshot; |
| return true; |
| } |
| const trustedSnapshot = startupRepair.resolveStartupConfigSnapshot(snapshot); |
| if (!trustedSnapshot) { |
| return false; |
| } |
| |
| |
| clearMissingManagedServiceEnvKeys({ |
| environment: process.env, |
| managedKeys: readManagedSystemdServiceEnvKeysFromEnvironment(process.env), |
| presentKeys: trustedEnvLoad.dotenvPresentKeys, |
| |
| |
| |
| |
| preserveKeys: new Set([ |
| ...collectEnvSecretRefIds(trustedSnapshot.sourceConfig), |
| ...collectEnvSecretRefIds(snapshot.sourceConfig), |
| ]), |
| }); |
| const selectionSignature = resolveGatewayConfigSelectionSignature(process.env); |
| applySelectedConfigEnv(trustedSnapshot); |
| |
| |
| if (resolveGatewayConfigSelectionSignature(process.env) !== selectionSignature) { |
| |
| |
| gatewayRunTargetSelectedByConfig = true; |
| restoreSupersededGatewaySelectionEnv({ |
| beforeCurrentPass: envBeforeTrustedApply, |
| environmentSelection: params.environmentSelection, |
| }); |
| continue; |
| } |
| |
| lastGuardedGatewayRunSnapshot = snapshot; |
| return true; |
| } |
| } |
|
|
| async function guardGatewayRunReset(params: GatewayRunGuardParams): Promise<boolean> { |
| gatewayRunTargetSelectedByConfig = false; |
| const envBeforeGuard = { ...process.env }; |
| try { |
| return await guardGatewayRunSelectedConfig(params); |
| } finally { |
| |
| |
| restoreAppliedGatewayRunConfigEnvironment(false); |
| |
| |
| restoreGatewayEnvChanges({ |
| before: envBeforeGuard, |
| after: { ...process.env }, |
| preservedKeys: GATEWAY_RESET_SELECTION_ENV_KEYS, |
| }); |
| } |
| } |
|
|
| export async function recheckGatewayRunReset(params: GatewayRunGuardParams): Promise<boolean> { |
| const expected = preparedGatewayRunReset; |
| preparedGatewayRunReset = undefined; |
| const rejectDrift = async () => { |
| if (expected) { |
| restoreGatewayConfigSelectionEnvironment(expected.selectionEnvironment); |
| await pinGatewayRunRuntimePaths(); |
| } |
| params.runtime.error( |
| "Refusing to reset the dev gateway state because the selected config or state target changed during startup. Retry the reset so the new target can be validated.", |
| ); |
| params.runtime.exit(1); |
| return false; |
| }; |
| if ( |
| !expected || |
| resolveGatewayConfigSelectionSignature(process.env) !== expected.selectionSignature |
| ) { |
| return await rejectDrift(); |
| } |
| if (!(await guardGatewayRunReset(params))) { |
| return false; |
| } |
| const current = lastGuardedGatewayRunSnapshot; |
| if ( |
| resolveGatewayConfigSelectionSignature(process.env) !== expected.selectionSignature || |
| !current || |
| !(await isSameGatewayRunConfigSnapshot(expected.snapshot, current)) |
| ) { |
| return await rejectDrift(); |
| } |
| return true; |
| } |
|
|
| export async function applyFinalGatewayRunConfigEnv(params: { |
| lowerPrecedenceEnv?: Readonly<Record<string, string>>; |
| runtime: RuntimeEnv; |
| snapshot: ConfigFileSnapshot; |
| }): Promise<boolean> { |
| const preparedSnapshot = preparedGatewayRunBootstrap?.snapshot; |
| preparedGatewayRunBootstrap = undefined; |
| if (!params.snapshot.valid) { |
| restoreAppliedGatewayRunConfigEnvironment(false); |
| if (preparedSnapshot) { |
| params.runtime.error( |
| "Refusing to start the gateway because the final config read became invalid. Retry startup after fixing the config.", |
| ); |
| params.runtime.exit(1); |
| return false; |
| } |
| await pinGatewayRunRuntimePaths(); |
| return true; |
| } |
| const invocationDestructiveOverride = resolveInvocationDestructiveOverride(); |
| const envBeforeApply = { ...process.env }; |
| const selectionSignature = resolveGatewayConfigSelectionSignature(process.env); |
| const [ |
| { |
| applyConfigEnvVars, |
| collectConfigRuntimeEnvOwnership, |
| collectConfigRuntimeEnvVars, |
| initializePublishedConfigRuntimeEnv, |
| }, |
| { normalizeEnv }, |
| { normalizeStateDirEnv }, |
| { clearShellEnvAppliedKeys }, |
| ] = await Promise.all([ |
| import("../../config/config-env-vars.js"), |
| import("../../infra/env.js"), |
| import("../../config/paths.js"), |
| import("../../infra/shell-env.js"), |
| ]); |
| const finalConfigEnv = collectConfigRuntimeEnvVars(params.snapshot.sourceConfig); |
| if ( |
| preparedSnapshot && |
| resolveGatewayConfigSelectionDeclarationSignature( |
| collectConfigRuntimeEnvVars(preparedSnapshot.sourceConfig), |
| ) !== resolveGatewayConfigSelectionDeclarationSignature(finalConfigEnv) |
| ) { |
| params.runtime.error( |
| "Refusing to start the gateway because the final config read changed config or state selection. Retry startup so the selected target can be validated.", |
| ); |
| params.runtime.exit(1); |
| return false; |
| } |
| restoreAppliedGatewayRunConfigEnvironment(); |
| const envBeforeConfigApply = { ...process.env }; |
| const replacedLowerPrecedenceKeys: string[] = []; |
| applyConfigEnvVars(params.snapshot.sourceConfig, process.env, { |
| lowerPrecedenceEnv: params.lowerPrecedenceEnv, |
| onLowerPrecedenceKeysReplaced: (keys) => { |
| replacedLowerPrecedenceKeys.push(...keys); |
| clearShellEnvAppliedKeys(keys); |
| }, |
| }); |
| normalizeStateDirEnv(process.env); |
| normalizeEnv(); |
| applyInvocationDestructiveOverride(invocationDestructiveOverride); |
| appliedGatewayRunConfigEnvironment = { |
| before: envBeforeApply, |
| after: { ...process.env }, |
| }; |
| if (resolveGatewayConfigSelectionSignature(process.env) === selectionSignature) { |
| initializePublishedConfigRuntimeEnv(params.snapshot.sourceConfig, { |
| ownedEnv: collectConfigRuntimeEnvOwnership( |
| params.snapshot.sourceConfig, |
| envBeforeConfigApply, |
| process.env, |
| { replacedLowerPrecedenceKeys }, |
| ), |
| }); |
| return true; |
| } |
| appliedGatewayRunConfigEnvironment = undefined; |
| restoreGatewayEnvChanges({ before: envBeforeApply, after: { ...process.env } }); |
| params.runtime.error( |
| "Refusing to start the gateway because the final config read changed config or state selection. Retry startup so the selected target can be validated.", |
| ); |
| params.runtime.exit(1); |
| return false; |
| } |
|
|
| export function clearGatewayRunConfigEnvironment(): void { |
| restoreAppliedGatewayRunConfigEnvironment(); |
| resetPublishedConfigRuntimeEnv(); |
| } |
|
|
| export async function reloadTrustedGatewayRunEnvironment(params: { |
| runtime: RuntimeEnv; |
| }): Promise<boolean> { |
| const [ |
| path, |
| { isConfigRuntimeEnvVarAllowed }, |
| { loadGlobalRuntimeDotEnvFiles }, |
| { normalizeEnv }, |
| { normalizeStateDirEnv, resolveStateDir }, |
| { resolveConfigDir }, |
| { readManagedSystemdServiceEnvKeysFromEnvironment }, |
| ] = await Promise.all([ |
| import("node:path"), |
| import("../../config/env-vars.js"), |
| import("../../infra/dotenv-global.js"), |
| import("../../infra/env.js"), |
| import("../../config/paths.js"), |
| import("../../utils.js"), |
| import("../../daemon/service-managed-env.js"), |
| ]); |
| const envBeforeReload = { ...process.env }; |
| const selectionSignature = resolveGatewayConfigSelectionSignature(process.env); |
| const invocationDestructiveOverride = resolveInvocationDestructiveOverride(); |
| normalizeStateDirEnv(process.env); |
| loadGlobalRuntimeDotEnvFiles({ |
| ...(gatewayRunTargetSelectedByConfig ? { entryFilter: isConfigRuntimeEnvVarAllowed } : {}), |
| overrideKeys: readManagedSystemdServiceEnvKeysFromEnvironment(process.env), |
| quiet: true, |
| ...resolveGatewayRunDotEnvPaths({ |
| env: process.env, |
| join: path.join, |
| resolve: path.resolve, |
| resolveConfigDir, |
| resolveStateDir, |
| }), |
| }); |
| normalizeStateDirEnv(process.env); |
| normalizeEnv(); |
| applyInvocationDestructiveOverride(invocationDestructiveOverride); |
| if (resolveGatewayConfigSelectionSignature(process.env) !== selectionSignature) { |
| |
| |
| restoreGatewayEnvChanges({ before: envBeforeReload, after: { ...process.env } }); |
| applyInvocationDestructiveOverride(invocationDestructiveOverride); |
| await pinGatewayRunRuntimePaths(); |
| params.runtime.error( |
| "Refusing to start the gateway because trusted dotenv reload after startup mutations changed config or state selection. Retry startup so the selected target can be validated.", |
| ); |
| params.runtime.exit(1); |
| return false; |
| } |
| await pinGatewayRunRuntimePaths(); |
| return true; |
| } |
|
|
| export async function selectGatewayRunEnvironment(params: GatewayRunGuardParams): Promise<boolean> { |
| gatewayRunTargetSelectedByConfig = false; |
| preparedGatewayRunBootstrap = undefined; |
| preparedGatewayRunReset = undefined; |
| restoreAppliedGatewayRunConfigEnvironment(params.opts.reset !== true); |
| const envBeforeGuard = { ...process.env }; |
| selectedGatewayRunEnvironment = undefined; |
| let guarded: boolean; |
| try { |
| guarded = await guardGatewayRunSelectedConfig(params); |
| } finally { |
| if (params.opts.reset) { |
| restoreAppliedGatewayRunConfigEnvironment(false); |
| restoreGatewayEnvChanges({ |
| before: envBeforeGuard, |
| after: { ...process.env }, |
| preservedKeys: GATEWAY_RESET_SELECTION_ENV_KEYS, |
| }); |
| } |
| } |
| selectedGatewayRunEnvironment = { |
| before: envBeforeGuard, |
| after: { ...process.env }, |
| }; |
| await pinGatewayRunRuntimePaths(); |
| return guarded; |
| } |
|
|
| export async function prepareGatewayRunBootstrap(params: GatewayRunGuardParams): Promise<boolean> { |
| preparedGatewayRunReset = undefined; |
| preparedGatewayRunStateWasPristine = false; |
| preparedGatewayRunCoreStateWasPristine = false; |
| const pristineSelectionSignature = resolveGatewayConfigSelectionSignature(process.env); |
| const { planPristineStartupConfigMigrations, planPristineStartupStateMigrations } = |
| await import("../../commands/doctor/shared/pristine-startup-state.js"); |
| const pristineStatePlan = planPristineStartupStateMigrations(process.env); |
| |
| |
| await getGatewayRunRuntimeHooks().releaseManagedProxy?.(); |
| const environmentSelection = selectedGatewayRunEnvironment; |
| selectedGatewayRunEnvironment = undefined; |
| if (!environmentSelection) { |
| gatewayRunTargetSelectedByConfig = false; |
| } |
| const guarded = params.opts.reset |
| ? await guardGatewayRunReset(params) |
| : await guardGatewayRunSelectedConfig({ |
| ...params, |
| environmentSelection, |
| }); |
| |
| |
| const guardedConfigPlan = planPristineStartupConfigMigrations( |
| guarded ? lastGuardedGatewayRunSnapshot?.parsed : undefined, |
| process.env, |
| ); |
| preparedGatewayRunStateWasPristine = |
| guarded && |
| !params.opts.reset && |
| pristineStatePlan.skipAllStateMigrations && |
| guardedConfigPlan.skipAllStateMigrations && |
| resolveGatewayConfigSelectionSignature(process.env) === pristineSelectionSignature; |
| preparedGatewayRunCoreStateWasPristine = |
| guarded && |
| !params.opts.reset && |
| pristineStatePlan.skipCoreStateMigrations && |
| guardedConfigPlan.skipCoreStateMigrations && |
| resolveGatewayConfigSelectionSignature(process.env) === pristineSelectionSignature; |
| await pinGatewayRunRuntimePaths(); |
| |
| |
| const shouldBootstrap = guarded && !params.opts.reset; |
| preparedGatewayRunBootstrap = |
| shouldBootstrap && lastGuardedGatewayRunSnapshot |
| ? { |
| snapshot: lastGuardedGatewayRunSnapshot, |
| allowUnconfigured: params.opts.allowUnconfigured === true, |
| dev: |
| Boolean(params.opts.dev) || |
| normalizeOptionalLowercaseString(process.env.OPENCLAW_PROFILE) === "dev", |
| } |
| : undefined; |
| if (guarded && params.opts.reset && lastGuardedGatewayRunSnapshot) { |
| preparedGatewayRunReset = { |
| selectionEnvironment: snapshotGatewayConfigSelectionEnvironment(process.env), |
| selectionSignature: resolveGatewayConfigSelectionSignature(process.env), |
| snapshot: lastGuardedGatewayRunSnapshot, |
| }; |
| } |
| return shouldBootstrap; |
| } |
|
|
| |
| export function wasPreparedGatewayRunStatePristine(): boolean { |
| return preparedGatewayRunStateWasPristine; |
| } |
|
|
| |
| export function wasPreparedGatewayRunCoreStatePristine(): boolean { |
| return preparedGatewayRunCoreStateWasPristine; |
| } |
|
|
| export async function recheckGatewayRunBootstrap( |
| params: GatewayRunGuardParams & { snapshot?: ConfigFileSnapshot }, |
| ): Promise<boolean> { |
| |
| |
| const deferredExitRuntime: RuntimeEnv = { |
| ...params.runtime, |
| exit: (code) => { |
| throw new ExitError(code); |
| }, |
| }; |
| const expected = preparedGatewayRunBootstrap?.snapshot; |
| if (!expected) { |
| params.runtime.error( |
| "Refusing to run automatic gateway startup migrations without a prepared config snapshot. Retry startup.", |
| ); |
| throw new ExitError(1); |
| } |
| const current = params.snapshot |
| ? enforceGatewayRunFutureConfigGuard({ |
| opts: params.opts, |
| runtime: deferredExitRuntime, |
| snapshot: params.snapshot, |
| }) |
| ? params.snapshot |
| : null |
| : await readGuardedGatewayRunConfig({ ...params, runtime: deferredExitRuntime }); |
| if (!current) { |
| return false; |
| } |
| |
| |
| if ( |
| (await isSameGatewayRunConfigSnapshot(expected, current, { |
| allowPathChange: params.snapshot !== undefined, |
| })) || |
| startupRepair.isStartupConfigRepairResult(expected, current) |
| ) { |
| return true; |
| } |
| params.runtime.error( |
| "Refusing to run automatic gateway startup migrations because the selected config changed during startup. Retry startup so the new config can be validated.", |
| ); |
| throw new ExitError(1); |
| } |
|
|