From 295b5ea9abf0db13d7d7dcb53d6748ebeb98e4be Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Tue, 26 May 2026 14:50:06 +0200 Subject: [PATCH] fix(mac): fail closed on restart gateway check --- scripts/lib/restart-mac-gateway.sh | 21 +++++++++ scripts/restart-mac.sh | 3 +- test/scripts/restart-mac.test.ts | 76 ++++++++++++++++++++++++++++++ 3 files changed, 99 insertions(+), 1 deletion(-) create mode 100644 scripts/lib/restart-mac-gateway.sh create mode 100644 test/scripts/restart-mac.test.ts diff --git a/scripts/lib/restart-mac-gateway.sh b/scripts/lib/restart-mac-gateway.sh new file mode 100644 index 00000000000..8cbd567af46 --- /dev/null +++ b/scripts/lib/restart-mac-gateway.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +verify_gateway_port_listening() { + local port="$1" + local lsof_output="" + + if ! lsof_output="$(lsof -nP -iTCP:"${port}" -sTCP:LISTEN 2>&1)"; then + if [[ -n "${lsof_output}" ]]; then + printf '%s\n' "${lsof_output}" >&2 + fi + printf 'No process is listening on gateway port %s.\n' "${port}" >&2 + return 1 + fi + + if [[ -z "${lsof_output}" ]]; then + printf 'No process is listening on gateway port %s.\n' "${port}" >&2 + return 1 + fi + + awk 'NR <= 5 { print }' <<<"${lsof_output}" +} diff --git a/scripts/restart-mac.sh b/scripts/restart-mac.sh index 444c34c50ab..e7de40daf3f 100755 --- a/scripts/restart-mac.sh +++ b/scripts/restart-mac.sh @@ -4,6 +4,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" +source "${ROOT_DIR}/scripts/lib/restart-mac-gateway.sh" APP_BUNDLE="${OPENCLAW_APP_BUNDLE:-}" APP_PROCESS_PATTERN="OpenClaw.app/Contents/MacOS/OpenClaw" DEBUG_PROCESS_PATTERN="${ROOT_DIR}/apps/macos/.build/debug/OpenClaw" @@ -236,7 +237,7 @@ if [ "$NO_SIGN" -eq 1 ] && [ "$ATTACH_ONLY" -ne 1 ]; then } ' )" - run_step "verify gateway port ${GATEWAY_PORT} (unsigned)" bash -lc "lsof -iTCP:${GATEWAY_PORT} -sTCP:LISTEN | head -n 5 || true" + run_step "verify gateway port ${GATEWAY_PORT} (unsigned)" verify_gateway_port_listening "${GATEWAY_PORT}" fi ATTACH_ONLY_ARGS=() diff --git a/test/scripts/restart-mac.test.ts b/test/scripts/restart-mac.test.ts new file mode 100644 index 00000000000..451fccb8e0a --- /dev/null +++ b/test/scripts/restart-mac.test.ts @@ -0,0 +1,76 @@ +import { spawnSync } from "node:child_process"; +import { chmodSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { afterEach, describe, expect, it } from "vitest"; + +const helperPath = "scripts/lib/restart-mac-gateway.sh"; +const restartScriptPath = "scripts/restart-mac.sh"; +const tempRoots: string[] = []; + +function shellQuote(value: string): string { + return `'${value.replaceAll("'", "'\\''")}'`; +} + +function runGatewayPortCheck(fakeLsof: string) { + const root = mkdtempSync(join(tmpdir(), "openclaw-restart-mac-test-")); + tempRoots.push(root); + + const binDir = join(root, "bin"); + mkdirSync(binDir); + const lsofPath = join(binDir, "lsof"); + writeFileSync(lsofPath, fakeLsof); + chmodSync(lsofPath, 0o755); + + return spawnSync( + "bash", + ["-c", `source ${shellQuote(helperPath)}; verify_gateway_port_listening 18789`], + { + encoding: "utf8", + env: { + ...process.env, + PATH: `${binDir}:${process.env.PATH ?? ""}`, + }, + }, + ); +} + +afterEach(() => { + for (const root of tempRoots.splice(0)) { + rmSync(root, { force: true, recursive: true }); + } +}); + +describe("scripts/restart-mac.sh", () => { + it("fails the gateway verification when lsof finds no listener", () => { + const result = runGatewayPortCheck("#!/usr/bin/env bash\nexit 1\n"); + + expect(result.status).toBe(1); + expect(result.stderr).toContain("No process is listening on gateway port 18789."); + expect(result.stdout).toBe(""); + }); + + it("prints listener diagnostics when the gateway port is open", () => { + const result = runGatewayPortCheck( + [ + "#!/usr/bin/env bash", + "printf '%s\\n' 'COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME'", + "printf '%s\\n' 'node 12345 user 21u IPv4 0x123 0t0 TCP 127.0.0.1:18789 (LISTEN)'", + ].join("\n"), + ); + + expect(result.status).toBe(0); + expect(result.stdout).toContain("127.0.0.1:18789 (LISTEN)"); + expect(result.stderr).toBe(""); + }); + + it("uses a fail-closed gateway port verification helper", () => { + const script = readFileSync(restartScriptPath, "utf8"); + + expect(script).toContain('source "${ROOT_DIR}/scripts/lib/restart-mac-gateway.sh"'); + expect(script).toContain( + 'run_step "verify gateway port ${GATEWAY_PORT} (unsigned)" verify_gateway_port_listening "${GATEWAY_PORT}"', + ); + expect(script).not.toContain("lsof -iTCP:${GATEWAY_PORT} -sTCP:LISTEN | head -n 5 || true"); + }); +});