Skip to content

Commit 3fea219

Browse files
fix(daemon): preserve explicit systemd unit during refresh
Preserve explicit gateway service identity when package/update refreshes the managed service environment. This keeps caller-selected systemd units ahead of stale persisted service env and applies the same precedence to launchd labels and Windows task names during service-state inspection. Fixes #87490 Verification: - node scripts/run-vitest.mjs src/daemon/service-env.test.ts src/daemon/service.test.ts src/cli/update-cli.test.ts src/cli/update-cli/restart-helper.test.ts src/cli/daemon-cli/install.test.ts src/daemon/systemd.test.ts - git diff --check origin/main...pr/87556 - Crabbox AWS Linux systemd install/refresh proof: run_f3374bd610f7, lease cbx_754e69eb6c3a, provider aws, target linux - autoreview --mode branch --base origin/main: clean, no accepted/actionable findings Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
1 parent 3f3ed5e commit 3fea219

4 files changed

Lines changed: 74 additions & 2 deletions

File tree

src/daemon/service-env.test.ts

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -685,6 +685,33 @@ describe("buildServiceEnvironment", () => {
685685
}
686686
});
687687

688+
it("preserves explicit systemd unit overrides", () => {
689+
const env = buildServiceEnvironment({
690+
env: {
691+
HOME: "/home/user",
692+
OPENCLAW_PROFILE: "work",
693+
OPENCLAW_SYSTEMD_UNIT: "openclaw-gateway-maintenance",
694+
},
695+
port: 18789,
696+
platform: "linux",
697+
});
698+
699+
expect(env.OPENCLAW_SYSTEMD_UNIT).toBe("openclaw-gateway-maintenance.service");
700+
});
701+
702+
it("preserves explicit systemd unit overrides with service suffix", () => {
703+
const env = buildServiceEnvironment({
704+
env: {
705+
HOME: "/home/user",
706+
OPENCLAW_SYSTEMD_UNIT: "openclaw-gateway-maintenance.service",
707+
},
708+
port: 18789,
709+
platform: "linux",
710+
});
711+
712+
expect(env.OPENCLAW_SYSTEMD_UNIT).toBe("openclaw-gateway-maintenance.service");
713+
});
714+
688715
it("sets a profile-specific launchd marker for macOS gateway services", () => {
689716
const env = buildServiceEnvironment({
690717
env: { HOME: "/Users/user", OPENCLAW_PROFILE: "work" },

src/daemon/service-env.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,14 @@ export function buildMinimalServicePath(options: BuildServicePathOptions = {}):
391391
return getMinimalServicePathPartsFromEnv({ ...options, env }).join(path.posix.delimiter);
392392
}
393393

394+
function resolveGatewaySystemdUnitEnv(env: Record<string, string | undefined>): string {
395+
const override = normalizeOptionalString(env.OPENCLAW_SYSTEMD_UNIT);
396+
if (override) {
397+
return override.endsWith(".service") ? override : `${override}.service`;
398+
}
399+
return `${resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE)}.service`;
400+
}
401+
394402
export function buildServiceEnvironment(params: {
395403
env: Record<string, string | undefined>;
396404
port: number;
@@ -411,7 +419,7 @@ export function buildServiceEnvironment(params: {
411419
const wrapperPath = normalizeOptionalString(env.OPENCLAW_WRAPPER);
412420
const resolvedLaunchdLabel =
413421
launchdLabel || (platform === "darwin" ? resolveGatewayLaunchAgentLabel(profile) : undefined);
414-
const systemdUnit = `${resolveGatewaySystemdServiceName(profile)}.service`;
422+
const systemdUnit = resolveGatewaySystemdUnitEnv(env);
415423
return {
416424
...buildCommonServiceEnvironment(env, sharedEnv),
417425
OPENCLAW_PROFILE: profile,

src/daemon/service.test.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,32 @@ describe("readGatewayServiceState", () => {
112112
expect(state.running).toBe(true);
113113
expect(state.env.OPENCLAW_GATEWAY_PORT).toBe("18789");
114114
});
115+
116+
it("keeps the caller-selected service identity when merging persisted env", async () => {
117+
const readRuntime = vi.fn(async () => ({ status: "running" }));
118+
const service = createService({
119+
isLoaded: vi.fn(async () => true),
120+
readCommand: vi.fn(async () => ({
121+
programArguments: ["openclaw", "gateway", "run"],
122+
environment: {
123+
OPENCLAW_GATEWAY_PORT: "18789",
124+
OPENCLAW_SYSTEMD_UNIT: "openclaw-gateway.service",
125+
},
126+
})),
127+
readRuntime,
128+
});
129+
130+
const state = await readGatewayServiceState(service, {
131+
env: { OPENCLAW_SYSTEMD_UNIT: "openclaw-gateway-maintenance.service" },
132+
});
133+
134+
expect(state.env.OPENCLAW_SYSTEMD_UNIT).toBe("openclaw-gateway-maintenance.service");
135+
expect(readRuntime).toHaveBeenCalledWith(
136+
expect.objectContaining({
137+
OPENCLAW_SYSTEMD_UNIT: "openclaw-gateway-maintenance.service",
138+
}),
139+
);
140+
});
115141
});
116142

117143
describe("startGatewayService", () => {

src/daemon/service.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,10 +91,21 @@ function mergeGatewayServiceEnv(
9191
if (!command?.environment) {
9292
return baseEnv;
9393
}
94-
return {
94+
const merged = {
9595
...baseEnv,
9696
...command.environment,
9797
};
98+
for (const key of [
99+
"OPENCLAW_LAUNCHD_LABEL",
100+
"OPENCLAW_SYSTEMD_UNIT",
101+
"OPENCLAW_WINDOWS_TASK_NAME",
102+
]) {
103+
const value = baseEnv[key]?.trim();
104+
if (value) {
105+
merged[key] = value;
106+
}
107+
}
108+
return merged;
98109
}
99110

100111
const TEMP_PROGRAM_ROOTS = [os.tmpdir(), "/tmp", "/private/tmp", "/var/tmp"].map((entry) =>

0 commit comments

Comments
 (0)