fix: persist gateway service wrappers

This commit is contained in:
Peter Steinberger
2026-04-27 01:32:30 +01:00
parent 414fd41a1f
commit 9f9bd41f40
14 changed files with 297 additions and 11 deletions

View File

@@ -10,6 +10,7 @@ import { resolveFutureConfigActionBlock } from "../../config/future-version-guar
import { readConfigFileSnapshotForWrite } from "../../config/io.js";
import { resolveGatewayPort } from "../../config/paths.js";
import type { OpenClawConfig } from "../../config/types.js";
import { OPENCLAW_WRAPPER_ENV_KEY, resolveOpenClawWrapperPath } from "../../daemon/program-args.js";
import { readEmbeddedGatewayToken } from "../../daemon/service-audit.js";
import { resolveGatewayService } from "../../daemon/service.js";
import type { GatewayServiceCommandConfig } from "../../daemon/service.js";
@@ -44,6 +45,13 @@ function mergeInstallInvocationEnv(params: {
continue;
}
const upper = key.toUpperCase();
if (upper === OPENCLAW_WRAPPER_ENV_KEY) {
const value = rawValue.trim();
if (value) {
preservedServiceEnv[OPENCLAW_WRAPPER_ENV_KEY] = value;
}
continue;
}
if (
upper === "HOME" ||
upper === "PATH" ||
@@ -99,6 +107,19 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
fail('Invalid --runtime (use "node" or "bun")');
return;
}
let wrapperPath: string | undefined;
if (opts.wrapper !== undefined) {
try {
wrapperPath = await resolveOpenClawWrapperPath(opts.wrapper);
if (!wrapperPath) {
fail("Invalid --wrapper");
return;
}
} catch (err) {
fail(`Invalid --wrapper: ${String(err)}`);
return;
}
}
const service = resolveGatewayService();
let loaded = false;
@@ -122,6 +143,14 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
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({
@@ -130,6 +159,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
installEnv,
port,
runtime: runtimeRaw,
wrapperPath,
existingEnvironment: existingServiceEnv,
config: cfg,
});
@@ -182,6 +212,7 @@ export async function runDaemonInstall(opts: DaemonInstallOptions) {
env: installEnv,
port,
runtime: runtimeRaw,
wrapperPath,
existingEnvironment: existingServiceEnv,
warn: (message) => {
if (json) {
@@ -217,6 +248,7 @@ async function getGatewayServiceAutoRefreshMessage(params: {
installEnv: NodeJS.ProcessEnv;
port: number;
runtime: GatewayDaemonRuntime;
wrapperPath?: string;
existingEnvironment?: Record<string, string | undefined>;
config: OpenClawConfig;
}): Promise<string | undefined> {
@@ -231,6 +263,7 @@ async function getGatewayServiceAutoRefreshMessage(params: {
env: params.installEnv,
port: params.port,
runtime: params.runtime,
wrapperPath: params.wrapperPath,
existingEnvironment: params.existingEnvironment,
warn: () => undefined,
config: params.config,
@@ -242,6 +275,26 @@ async function getGatewayServiceAutoRefreshMessage(params: {
return "Gateway service OPENCLAW_GATEWAY_TOKEN differs from the current install plan; refreshing the install.";
}
}
const wrapperRequested = Boolean(
params.wrapperPath || normalizeOptionalString(params.installEnv[OPENCLAW_WRAPPER_ENV_KEY]),
);
if (wrapperRequested) {
const plannedInstall = await buildGatewayInstallPlan({
env: params.installEnv,
port: params.port,
runtime: params.runtime,
wrapperPath: params.wrapperPath,
existingEnvironment: params.existingEnvironment,
warn: () => undefined,
config: params.config,
});
if (
plannedInstall.programArguments.join("\u0000") !==
currentCommand.programArguments.join("\u0000")
) {
return "Gateway service command differs from the current wrapper install plan; refreshing the install.";
}
}
const currentExecPath = currentCommand.programArguments[0]?.trim();
if (!currentExecPath) {
return undefined;

View File

@@ -77,6 +77,7 @@ export function addGatewayServiceCommands(parent: Command, opts?: { statusDescri
.option("--port <port>", "Gateway port")
.option("--runtime <runtime>", "Daemon runtime (node|bun). Default: node")
.option("--token <token>", "Gateway token (token auth)")
.option("--wrapper <path>", "Executable wrapper for generated service ProgramArguments")
.option("--force", "Reinstall/overwrite if already installed", false)
.option("--json", "Output JSON", false)
.action(async (cmdOpts, command) => {

View File

@@ -19,6 +19,7 @@ export type DaemonInstallOptions = {
port?: string | number;
runtime?: string;
token?: string;
wrapper?: string;
force?: boolean;
json?: boolean;
};