Skip to content

Commit 8ba8752

Browse files
committed
fix(status): detect shell-wrapped gateway services
1 parent 462a056 commit 8ba8752

2 files changed

Lines changed: 54 additions & 4 deletions

File tree

src/daemon/service-audit.test.ts

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -269,6 +269,37 @@ describe("auditGatewayServiceConfig", () => {
269269
).toBe(undefined);
270270
});
271271

272+
it("accepts shell-wrapped LaunchAgent gateway commands", async () => {
273+
const command =
274+
'set -a; [ -f "$HOME/.config/api-keys.env" ] && . "$HOME/.config/api-keys.env"; set +a; unset OPENCLAW_GATEWAY_TOKEN; exec /opt/homebrew/opt/node/bin/node /Users/test/.npm-global/lib/node_modules/openclaw/dist/index.js gateway --port 18890';
275+
const audit = await auditGatewayServiceConfig({
276+
env: { HOME: "/tmp" },
277+
platform: "darwin",
278+
expectedPort: 18890,
279+
command: {
280+
programArguments: ["/bin/zsh", "-lc", command],
281+
environment: {},
282+
},
283+
});
284+
285+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayCommandMissing)).toBe(false);
286+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayPortMismatch)).toBe(false);
287+
expect(readGatewayServiceCommandPort(["/bin/zsh", "-lc", command])).toBe(18890);
288+
});
289+
290+
it("does not treat gateway-looking option names in shell wrappers as the gateway subcommand", async () => {
291+
const audit = await auditGatewayServiceConfig({
292+
env: { HOME: "/tmp" },
293+
platform: "darwin",
294+
command: {
295+
programArguments: ["/bin/zsh", "-lc", "exec /usr/local/bin/helper --gateway-url ws://x"],
296+
environment: {},
297+
},
298+
});
299+
300+
expect(hasIssue(audit, SERVICE_AUDIT_CODES.gatewayCommandMissing)).toBe(true);
301+
});
302+
272303
it("flags gateway service port drift from the expected config port", async () => {
273304
const audit = await auditGatewayServiceConfig({
274305
env: { HOME: "/tmp" },

src/daemon/service-audit.ts

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,26 @@ export function needsNodeRuntimeMigration(issues: ServiceConfigIssue[]): boolean
7070
);
7171
}
7272

73+
function tokenizeShellCommandFragment(value: string): string[] {
74+
return value.match(/[^\s;"'`]+/g) ?? [];
75+
}
76+
77+
function expandServiceCommandTokens(programArguments?: string[]): string[] {
78+
if (!programArguments || programArguments.length === 0) {
79+
return [];
80+
}
81+
const tokens: string[] = [];
82+
for (const arg of programArguments) {
83+
tokens.push(arg);
84+
if (/[\s;"'`]/.test(arg)) {
85+
tokens.push(...tokenizeShellCommandFragment(arg));
86+
}
87+
}
88+
return tokens;
89+
}
90+
7391
function hasGatewaySubcommand(programArguments?: string[]): boolean {
74-
return Boolean(programArguments?.some((arg) => arg === "gateway"));
92+
return expandServiceCommandTokens(programArguments).some((arg) => arg === "gateway");
7593
}
7694

7795
function parseSystemdUnit(content: string): {
@@ -227,10 +245,11 @@ export function readGatewayServiceCommandPort(programArguments?: string[]): numb
227245
if (!programArguments || programArguments.length === 0) {
228246
return undefined;
229247
}
230-
for (let index = 0; index < programArguments.length; index += 1) {
231-
const arg = programArguments[index];
248+
const commandTokens = expandServiceCommandTokens(programArguments);
249+
for (let index = 0; index < commandTokens.length; index += 1) {
250+
const arg = commandTokens[index];
232251
if (arg === "--port") {
233-
return parseGatewayPortArg(programArguments[index + 1]);
252+
return parseGatewayPortArg(commandTokens[index + 1]);
234253
}
235254
if (arg.startsWith("--port=")) {
236255
return parseGatewayPortArg(arg.slice("--port=".length));

0 commit comments

Comments
 (0)