openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
263 lines (262 loc) • 10.5 kB
JavaScript
import { c as normalizeOptionalString } from "./string-coerce-mnp54Vah.js";
import { d as resolveGatewayPort } from "./paths-mvMm5bYV.js";
import { t as formatCliCommand } from "./command-format-CKGmlpAQ.js";
import { t as resolveNodeStartupTlsEnvironment } from "./node-startup-env-Chdn0e25.js";
import { a as normalizeEnvVarKey, n as isDangerousHostEnvOverrideVarName, r as isDangerousHostEnvVarName } from "./host-env-security-RnaFYKhk.js";
import { a as OPENCLAW_WRAPPER_ENV_KEY, c as resolveOpenClawWrapperPath } from "./daemon-install-plan.shared-Dr7I1qWf.js";
import { n as buildGatewayInstallPlan } from "./auth-install-policy-O7nc7i9I.js";
import { r as isNonFatalSystemdInstallProbeError } from "./systemd-BXTM6gEb.js";
import { n as defaultRuntime } from "./runtime-B4lgFmsS.js";
import { r as isGatewayDaemonRuntime, t as DEFAULT_GATEWAY_DAEMON_RUNTIME } from "./daemon-runtime-C76za6vm.js";
import { d as readConfigFileSnapshotForWrite } from "./io-Gi7-pyU-.js";
import { i as replaceConfigFile } from "./config-C9RxTsn1.js";
import { t as resolveGatewayInstallToken } from "./gateway-install-token-jVqLCOms.js";
import { r as resolveFutureConfigActionBlock } from "./future-version-guard-D6QWZtAq.js";
import { a as readEmbeddedGatewayToken } from "./service-audit-Bj05_7pm.js";
import { i as resolveGatewayService } from "./service-MPqQWJ3s.js";
import { n as formatInvalidConfigPort, r as formatInvalidPortOption } from "./error-format-COoNML-C.js";
import { g as installDaemonServiceAndEmit, n as createDaemonInstallActionContext, p as buildDaemonServiceSnapshot, r as failIfNixDaemonInstallMode } from "./shared-BEkveSUA.js";
import { t as parsePort } from "./parse-port-CbiRuE9n.js";
//#region src/cli/daemon-cli/install.ts
/** Merge safe existing service environment into the current install invocation environment. */
function mergeInstallInvocationEnv(params) {
if (!params.existingServiceEnv || Object.keys(params.existingServiceEnv).length === 0) return params.env;
const preservedServiceEnv = {};
for (const [rawKey, rawValue] of Object.entries(params.existingServiceEnv)) {
const key = normalizeEnvVarKey(rawKey, { portable: true });
if (!key) continue;
const upper = key.toUpperCase();
if (upper === "OPENCLAW_WRAPPER") {
const value = rawValue.trim();
if (value) preservedServiceEnv[OPENCLAW_WRAPPER_ENV_KEY] = value;
continue;
}
if (upper === "HOME" || upper === "PATH" || upper === "TMPDIR" || upper.startsWith("OPENCLAW_")) continue;
if (isDangerousHostEnvVarName(key) || isDangerousHostEnvOverrideVarName(key)) continue;
const value = rawValue.trim();
if (!value) continue;
preservedServiceEnv[key] = value;
}
return {
...preservedServiceEnv,
...params.env
};
}
/** Install or refresh the managed Gateway service. */
async function runDaemonInstall(opts) {
const { json, stdout, warnings, emit, fail } = createDaemonInstallActionContext(opts.json);
if (failIfNixDaemonInstallMode(fail)) return;
let { snapshot: configSnapshot, writeOptions: configWriteOptions } = await readConfigFileSnapshotForWrite();
const futureBlock = resolveFutureConfigActionBlock({
action: "install or rewrite the gateway service",
snapshot: configSnapshot
});
if (futureBlock) {
fail(`Gateway install blocked: ${futureBlock.message}`, futureBlock.hints);
return;
}
let cfg = configSnapshot.valid ? configSnapshot.sourceConfig : configSnapshot.config;
const portOverride = parsePort(opts.port);
if (opts.port !== void 0 && portOverride === null) {
fail(formatInvalidPortOption("--port"));
return;
}
const port = portOverride ?? resolveGatewayPort(cfg);
if (!Number.isFinite(port) || port <= 0 || port > 65535) {
fail(formatInvalidConfigPort("gateway.port"));
return;
}
const runtimeRaw = opts.runtime ? opts.runtime : DEFAULT_GATEWAY_DAEMON_RUNTIME;
if (!isGatewayDaemonRuntime(runtimeRaw)) {
fail("Invalid --runtime (use \"node\" or \"bun\")");
return;
}
let wrapperPath;
if (opts.wrapper !== void 0) try {
wrapperPath = await resolveOpenClawWrapperPath(opts.wrapper);
if (!wrapperPath) {
fail("Invalid --wrapper");
return;
}
} catch (err) {
fail(`Invalid --wrapper: ${String(err)}`);
return;
}
if (configSnapshot.valid && cfg.gateway?.mode === void 0) {
const baseConfig = configSnapshot.sourceConfig ?? configSnapshot.config;
await replaceConfigFile({
nextConfig: {
...baseConfig,
gateway: {
...baseConfig.gateway,
mode: "local"
}
},
snapshot: configSnapshot,
writeOptions: {
baseSnapshot: configSnapshot,
...configWriteOptions,
skipRuntimeSnapshotRefresh: true
},
afterWrite: { mode: "auto" }
});
const refreshed = await readConfigFileSnapshotForWrite();
configSnapshot = refreshed.snapshot;
configWriteOptions = refreshed.writeOptions;
cfg = configSnapshot.valid ? configSnapshot.sourceConfig : configSnapshot.config;
const message = "No gateway.mode found. Set gateway.mode=local for managed gateway install.";
if (json) warnings.push(message);
else defaultRuntime.log(message);
}
const service = resolveGatewayService();
let loaded;
try {
loaded = await service.isLoaded({ env: process.env });
} catch (err) {
if (isNonFatalSystemdInstallProbeError(err)) loaded = false;
else {
fail(`Gateway service check failed: ${String(err)}`);
return;
}
}
const existingServiceCommand = await service.readCommand(process.env).catch(() => null);
const existingServiceEnv = existingServiceCommand?.environment;
const installEnv = mergeInstallInvocationEnv({
env: process.env,
existingServiceEnv
});
if (!wrapperPath) try {
wrapperPath = await resolveOpenClawWrapperPath(installEnv[OPENCLAW_WRAPPER_ENV_KEY]);
} catch (err) {
fail(`Invalid ${OPENCLAW_WRAPPER_ENV_KEY}: ${String(err)}`);
return;
}
if (loaded) {
if (!opts.force) {
const autoRefreshMessage = await getGatewayServiceAutoRefreshMessage({
currentCommand: existingServiceCommand,
env: process.env,
installEnv,
port,
runtime: runtimeRaw,
wrapperPath,
existingEnvironment: existingServiceEnv,
existingEnvironmentValueSources: existingServiceCommand?.environmentValueSources,
config: cfg
});
if (autoRefreshMessage) if (json) warnings.push(autoRefreshMessage);
else defaultRuntime.log(autoRefreshMessage);
else {
emit({
ok: true,
result: "already-installed",
message: `Gateway service already ${service.loadedText}.`,
service: buildDaemonServiceSnapshot(service, loaded)
});
if (!json) {
defaultRuntime.log(`Gateway service already ${service.loadedText}.`);
defaultRuntime.log(`Reinstall with: ${formatCliCommand("openclaw gateway install --force")}`);
}
return;
}
}
}
const tokenResolution = await resolveGatewayInstallToken({
config: cfg,
configSnapshot,
configWriteOptions,
env: installEnv,
explicitToken: opts.token,
autoGenerateWhenMissing: true,
persistGeneratedToken: true
});
if (tokenResolution.unavailableReason) {
fail(`Gateway install blocked: ${tokenResolution.unavailableReason}`);
return;
}
for (const warning of tokenResolution.warnings) if (json) warnings.push(warning);
else defaultRuntime.log(warning);
const { programArguments, workingDirectory, environment, environmentValueSources } = await buildGatewayInstallPlan({
env: installEnv,
port,
runtime: runtimeRaw,
wrapperPath,
existingEnvironment: existingServiceEnv,
existingEnvironmentValueSources: existingServiceCommand?.environmentValueSources,
warn: (message) => {
if (json) warnings.push(message);
else defaultRuntime.log(message);
},
config: cfg
});
await installDaemonServiceAndEmit({
serviceNoun: "Gateway",
service,
warnings,
emit,
fail,
install: async () => {
await service.install({
env: installEnv,
stdout,
programArguments,
workingDirectory,
environment,
environmentValueSources
});
}
});
}
async function getGatewayServiceAutoRefreshMessage(params) {
try {
const currentCommand = params.currentCommand;
if (!currentCommand) return;
const currentEmbeddedToken = readEmbeddedGatewayToken(currentCommand);
if (currentEmbeddedToken) {
if (currentEmbeddedToken !== normalizeOptionalString((await buildGatewayInstallPlan({
env: params.installEnv,
port: params.port,
runtime: params.runtime,
wrapperPath: params.wrapperPath,
existingEnvironment: params.existingEnvironment,
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
warn: () => void 0,
config: params.config
})).environment.OPENCLAW_GATEWAY_TOKEN)) return "Gateway service OPENCLAW_GATEWAY_TOKEN differs from the current install plan; refreshing the install.";
}
if (Boolean(params.wrapperPath || normalizeOptionalString(params.installEnv["OPENCLAW_WRAPPER"]))) {
const plannedInstall = await buildGatewayInstallPlan({
env: params.installEnv,
port: params.port,
runtime: params.runtime,
wrapperPath: params.wrapperPath,
existingEnvironment: params.existingEnvironment,
existingEnvironmentValueSources: params.existingEnvironmentValueSources,
warn: () => void 0,
config: params.config
});
if (plannedInstall.programArguments.join("\0") !== currentCommand.programArguments.join("\0")) return "Gateway service command differs from the current wrapper install plan; refreshing the install.";
if (normalizeOptionalString(plannedInstall.environment["OPENCLAW_WRAPPER"]) !== normalizeOptionalString(currentCommand.environment?.["OPENCLAW_WRAPPER"])) return `Gateway service ${OPENCLAW_WRAPPER_ENV_KEY} differs from the current wrapper install plan; refreshing the install.`;
}
const currentExecPath = currentCommand.programArguments[0]?.trim();
if (!currentExecPath) return;
const currentEnvironment = currentCommand.environment ?? {};
const currentNodeExtraCaCerts = currentEnvironment.NODE_EXTRA_CA_CERTS?.trim();
const expectedNodeExtraCaCerts = resolveNodeStartupTlsEnvironment({
env: {
...params.env,
...currentEnvironment,
NODE_EXTRA_CA_CERTS: void 0
},
execPath: currentExecPath,
includeDarwinDefaults: false
}).NODE_EXTRA_CA_CERTS;
if (!expectedNodeExtraCaCerts) return;
if (currentNodeExtraCaCerts !== expectedNodeExtraCaCerts) return "Gateway service is missing the nvm TLS CA bundle; refreshing the install.";
return;
} catch {
return;
}
}
//#endregion
export { runDaemonInstall as n, mergeInstallInvocationEnv as t };