fix(cli): honor update restart overrides

This commit is contained in:
Sebastian 2026-02-17 08:46:59 -05:00
parent dff8692613
commit 366da7569a
4 changed files with 93 additions and 5 deletions

View File

@ -64,6 +64,7 @@ Docs: https://docs.openclaw.ai
- CLI/Doctor: ensure `openclaw doctor --fix --non-interactive --yes` exits promptly after completion so one-shot automation no longer hangs. (#18502)
- CLI/Doctor: auto-repair `dmPolicy="open"` configs missing wildcard allowlists and write channel-correct repair paths (including `channels.googlechat.dm.allowFrom`) so `openclaw doctor --fix` no longer leaves Google Chat configs invalid after attempted repair. (#18544)
- CLI/Doctor: detect gateway service token drift when the gateway token is only provided via environment variables, keeping service repairs aligned after token rotation.
- CLI/Update: run a standalone restart helper after updates, honoring service-name overrides and reporting restart initiation separately from confirmed restarts. (#18050)
- CLI/Daemon: warn when a gateway restart sees a stale service token so users can reinstall with `openclaw gateway install --force`, and skip drift warnings for non-gateway service restarts. (#18018)
- CLI/Status: fix `openclaw status --all` token summaries for bot-token-only channels so Mattermost/Zalo no longer show a bot+app warning. (#18527) Thanks @echo931.
- CLI/Configure: make the `/model picker` allowlist prompt searchable with tokenized matching in `openclaw configure` so users can filter huge model lists by typing terms like `gpt-5.2 openai/`. (#19010) Thanks @bjesuiter.

View File

@ -41,6 +41,22 @@ describe("restart-helper", () => {
}
});
it("uses OPENCLAW_SYSTEMD_UNIT override for systemd scripts", async () => {
Object.defineProperty(process, "platform", { value: "linux" });
const scriptPath = await prepareRestartScript({
OPENCLAW_PROFILE: "default",
OPENCLAW_SYSTEMD_UNIT: "custom-gateway",
});
expect(scriptPath).toBeTruthy();
const content = await fs.readFile(scriptPath!, "utf-8");
expect(content).toContain("systemctl --user restart 'custom-gateway.service'");
if (scriptPath) {
await fs.unlink(scriptPath);
}
});
it("creates a launchd restart script on macOS", async () => {
Object.defineProperty(process, "platform", { value: "darwin" });
process.getuid = () => 501;
@ -62,6 +78,24 @@ describe("restart-helper", () => {
}
});
it("uses OPENCLAW_LAUNCHD_LABEL override on macOS", async () => {
Object.defineProperty(process, "platform", { value: "darwin" });
process.getuid = () => 501;
const scriptPath = await prepareRestartScript({
OPENCLAW_PROFILE: "default",
OPENCLAW_LAUNCHD_LABEL: "com.custom.openclaw",
});
expect(scriptPath).toBeTruthy();
const content = await fs.readFile(scriptPath!, "utf-8");
expect(content).toContain("launchctl kickstart -k 'gui/501/com.custom.openclaw'");
if (scriptPath) {
await fs.unlink(scriptPath);
}
});
it("creates a schtasks restart script on Windows", async () => {
Object.defineProperty(process, "platform", { value: "win32" });
@ -84,6 +118,24 @@ describe("restart-helper", () => {
}
});
it("uses OPENCLAW_WINDOWS_TASK_NAME override on Windows", async () => {
Object.defineProperty(process, "platform", { value: "win32" });
const scriptPath = await prepareRestartScript({
OPENCLAW_PROFILE: "default",
OPENCLAW_WINDOWS_TASK_NAME: "OpenClaw Gateway (custom)",
});
expect(scriptPath).toBeTruthy();
const content = await fs.readFile(scriptPath!, "utf-8");
expect(content).toContain('schtasks /End /TN "OpenClaw Gateway (custom)"');
expect(content).toContain('schtasks /Run /TN "OpenClaw Gateway (custom)"');
if (scriptPath) {
await fs.unlink(scriptPath);
}
});
it("uses custom profile in service names", async () => {
Object.defineProperty(process, "platform", { value: "linux" });
const scriptPath = await prepareRestartScript({

View File

@ -23,6 +23,30 @@ function isBatchSafe(value: string): boolean {
return /^[A-Za-z0-9 _\-().]+$/.test(value);
}
function resolveSystemdUnit(env: NodeJS.ProcessEnv): string {
const override = env.OPENCLAW_SYSTEMD_UNIT?.trim();
if (override) {
return override.endsWith(".service") ? override : `${override}.service`;
}
return `${resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE)}.service`;
}
function resolveLaunchdLabel(env: NodeJS.ProcessEnv): string {
const override = env.OPENCLAW_LAUNCHD_LABEL?.trim();
if (override) {
return override;
}
return resolveGatewayLaunchAgentLabel(env.OPENCLAW_PROFILE);
}
function resolveWindowsTaskName(env: NodeJS.ProcessEnv): string {
const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim();
if (override) {
return override;
}
return resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE);
}
/**
* Prepares a standalone script to restart the gateway service.
* This script is written to a temporary directory and does not depend on
@ -41,8 +65,8 @@ export async function prepareRestartScript(
try {
if (platform === "linux") {
const serviceName = resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE);
const escaped = shellEscape(`${serviceName}.service`);
const unitName = resolveSystemdUnit(env);
const escaped = shellEscape(unitName);
filename = `openclaw-restart-${timestamp}.sh`;
scriptContent = `#!/bin/sh
# Standalone restart script survives parent process termination.
@ -53,7 +77,7 @@ systemctl --user restart '${escaped}'
rm -f "$0"
`;
} else if (platform === "darwin") {
const label = resolveGatewayLaunchAgentLabel(env.OPENCLAW_PROFILE);
const label = resolveLaunchdLabel(env);
const escaped = shellEscape(label);
// Fallback to 501 if getuid is not available (though it should be on macOS)
const uid = process.getuid ? process.getuid() : 501;
@ -67,7 +91,7 @@ launchctl kickstart -k 'gui/${uid}/${escaped}'
rm -f "$0"
`;
} else if (platform === "win32") {
const taskName = resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE);
const taskName = resolveWindowsTaskName(env);
if (!isBatchSafe(taskName)) {
return null;
}

View File

@ -400,9 +400,10 @@ async function maybeRestartService(params: {
try {
let restarted = false;
let restartInitiated = false;
if (params.restartScriptPath) {
await runRestartScript(params.restartScriptPath);
restarted = true;
restartInitiated = true;
} else {
restarted = await runDaemonRestart();
}
@ -423,6 +424,16 @@ async function maybeRestartService(params: {
delete process.env.OPENCLAW_UPDATE_IN_PROGRESS;
}
}
if (!params.opts.json && restartInitiated) {
defaultRuntime.log(theme.success("Daemon restart initiated."));
defaultRuntime.log(
theme.muted(
`Verify with \`${replaceCliName(formatCliCommand("openclaw gateway status"), CLI_NAME)}\` once the gateway is back.`,
),
);
defaultRuntime.log("");
}
} catch (err) {
if (!params.opts.json) {
defaultRuntime.log(theme.warn(`Daemon restart failed: ${String(err)}`));