mirror of https://github.com/openclaw/openclaw.git
fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski)
* daemon: tighten systemd duplicate gateway detection (#15849) * fix three issues from PR review * fix windows unit tests due to posix/windows path differences * ensure line continuations are handled in systemd units * fix misleading test name * attempt fix windows test due to fs path separator * fix system_dir separator, fix platform side-effect * change approach for mocking systemd filesystem test * normalize systemd paths to linux style * revert to vers that didnt impact win32 tests * back out all systemd inspect tests * change test approach to avoid other tests issues * fix: tighten systemd duplicate gateway detection (#45328) (thanks @gregretkowski) --------- Co-authored-by: Ayaan Zaidi <hi@obviy.us>
This commit is contained in:
parent
ebad7490b4
commit
14430ade57
|
|
@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai
|
|||
- Agents/sandbox: make blocked-tool guidance glob-aware again, redact/sanitize session-specific explain hints for safer copy-paste, and avoid leaking control-character session keys in those hints. (#54684) Thanks @ngutman.
|
||||
- Feishu: close WebSocket connections on monitor stop/abort so ghost connections no longer persist, preventing duplicate event processing and resource leaks across restart cycles. (#52844) Thanks @schumilin.
|
||||
- Feishu: use the original message `create_time` instead of `Date.now()` for inbound timestamps so offline-retried messages carry the correct authoring time, preventing mis-targeted agent actions on stale instructions. (#52809) Thanks @schumilin.
|
||||
- Daemon/Linux: stop flagging non-gateway systemd services as duplicate gateways just because their unit files mention OpenClaw, reducing false-positive doctor/log noise. (#45328) Thanks @gregretkowski.
|
||||
- Plugins/SDK: thread `moduleUrl` through plugin-sdk alias resolution so user-installed plugins outside the openclaw directory (e.g. `~/.openclaw/extensions/`) correctly resolve `openclaw/plugin-sdk/*` subpath imports, and gate `plugin-sdk:check-exports` in `release:check`. (#54283) Thanks @xieyongliang.
|
||||
- Telegram/pairing: ignore self-authored DM `message` updates so bot-pinned status cards and similar service updates do not trigger bogus pairing requests or re-enter inbound dispatch. (#54530) thanks @huntharo
|
||||
- iMessage: stop leaking inline `[[reply_to:...]]` tags into delivered text by sending `reply_to` as RPC metadata and stripping stray directive tags from outbound messages. (#39512) Thanks @mvanhorn.
|
||||
|
|
|
|||
|
|
@ -1,5 +1,8 @@
|
|||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
import { findExtraGatewayServices } from "./inspect.js";
|
||||
import { detectMarkerLineWithGateway, findExtraGatewayServices } from "./inspect.js";
|
||||
|
||||
const { execSchtasksMock } = vi.hoisted(() => ({
|
||||
execSchtasksMock: vi.fn(),
|
||||
|
|
@ -9,6 +12,131 @@ vi.mock("./schtasks-exec.js", () => ({
|
|||
execSchtasks: (...args: unknown[]) => execSchtasksMock(...args),
|
||||
}));
|
||||
|
||||
// Real content from the openclaw-gateway.service unit file (the canonical gateway unit).
|
||||
const GATEWAY_SERVICE_CONTENTS = `\
|
||||
[Unit]
|
||||
Description=OpenClaw Gateway (v2026.3.8)
|
||||
After=network-online.target
|
||||
Wants=network-online.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/node /home/openclaw/.npm-global/lib/node_modules/openclaw/dist/entry.js gateway --port 18789
|
||||
Restart=always
|
||||
Environment=OPENCLAW_SERVICE_MARKER=openclaw
|
||||
Environment=OPENCLAW_SERVICE_KIND=gateway
|
||||
Environment=OPENCLAW_SERVICE_VERSION=2026.3.8
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
`;
|
||||
|
||||
// Real content from the openclaw-test.service unit file (a non-gateway openclaw service).
|
||||
const TEST_SERVICE_CONTENTS = `\
|
||||
[Unit]
|
||||
Description=OpenClaw test service
|
||||
After=default.target
|
||||
|
||||
[Service]
|
||||
Type=simple
|
||||
ExecStart=/bin/sh -c 'while true; do sleep 60; done'
|
||||
Restart=on-failure
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
`;
|
||||
|
||||
const CLAWDBOT_GATEWAY_CONTENTS = `\
|
||||
[Unit]
|
||||
Description=Clawdbot Gateway
|
||||
[Service]
|
||||
ExecStart=/usr/bin/node /opt/clawdbot/dist/entry.js gateway --port 18789
|
||||
Environment=HOME=/home/clawdbot
|
||||
`;
|
||||
|
||||
describe("detectMarkerLineWithGateway", () => {
|
||||
it("returns null for openclaw-test.service (openclaw only in description, no gateway on same line)", () => {
|
||||
expect(detectMarkerLineWithGateway(TEST_SERVICE_CONTENTS)).toBeNull();
|
||||
});
|
||||
|
||||
it("returns openclaw for the canonical gateway unit (ExecStart has both openclaw and gateway)", () => {
|
||||
expect(detectMarkerLineWithGateway(GATEWAY_SERVICE_CONTENTS)).toBe("openclaw");
|
||||
});
|
||||
|
||||
it("returns clawdbot for a clawdbot gateway unit", () => {
|
||||
expect(detectMarkerLineWithGateway(CLAWDBOT_GATEWAY_CONTENTS)).toBe("clawdbot");
|
||||
});
|
||||
|
||||
it("handles line continuations — marker and gateway split across physical lines", () => {
|
||||
const contents = `[Service]\nExecStart=/usr/bin/node /opt/openclaw/dist/entry.js \\\n gateway --port 18789\n`;
|
||||
expect(detectMarkerLineWithGateway(contents)).toBe("openclaw");
|
||||
});
|
||||
});
|
||||
|
||||
describe("findExtraGatewayServices (linux / scanSystemdDir) — real filesystem", () => {
|
||||
// These tests write real .service files to a temp dir and call findExtraGatewayServices
|
||||
// with that dir as HOME. No platform mocking or fs mocking needed.
|
||||
// Only runs on Linux/macOS where the linux branch of findExtraGatewayServices is active.
|
||||
const isLinux = process.platform === "linux";
|
||||
|
||||
it.skipIf(!isLinux)("does not report openclaw-test.service as a gateway service", async () => {
|
||||
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
||||
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
||||
try {
|
||||
await fs.mkdir(systemdDir, { recursive: true });
|
||||
await fs.writeFile(path.join(systemdDir, "openclaw-test.service"), TEST_SERVICE_CONTENTS);
|
||||
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
||||
expect(result).toEqual([]);
|
||||
} finally {
|
||||
await fs.rm(tmpHome, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it.skipIf(!isLinux)(
|
||||
"does not report the canonical openclaw-gateway.service as an extra service",
|
||||
async () => {
|
||||
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
||||
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
||||
try {
|
||||
await fs.mkdir(systemdDir, { recursive: true });
|
||||
await fs.writeFile(
|
||||
path.join(systemdDir, "openclaw-gateway.service"),
|
||||
GATEWAY_SERVICE_CONTENTS,
|
||||
);
|
||||
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
||||
expect(result).toEqual([]);
|
||||
} finally {
|
||||
await fs.rm(tmpHome, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
it.skipIf(!isLinux)(
|
||||
"reports a legacy clawdbot-gateway service as an extra gateway service",
|
||||
async () => {
|
||||
const tmpHome = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-test-"));
|
||||
const systemdDir = path.join(tmpHome, ".config", "systemd", "user");
|
||||
const unitPath = path.join(systemdDir, "clawdbot-gateway.service");
|
||||
try {
|
||||
await fs.mkdir(systemdDir, { recursive: true });
|
||||
await fs.writeFile(unitPath, CLAWDBOT_GATEWAY_CONTENTS);
|
||||
const result = await findExtraGatewayServices({ HOME: tmpHome });
|
||||
expect(result).toEqual([
|
||||
{
|
||||
platform: "linux",
|
||||
label: "clawdbot-gateway.service",
|
||||
detail: `unit: ${unitPath}`,
|
||||
scope: "user",
|
||||
marker: "clawdbot",
|
||||
legacy: true,
|
||||
},
|
||||
]);
|
||||
} finally {
|
||||
await fs.rm(tmpHome, { recursive: true, force: true });
|
||||
}
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe("findExtraGatewayServices (win32)", () => {
|
||||
const originalPlatform = process.platform;
|
||||
|
||||
|
|
|
|||
|
|
@ -62,6 +62,22 @@ function detectMarker(content: string): Marker | null {
|
|||
return null;
|
||||
}
|
||||
|
||||
export function detectMarkerLineWithGateway(contents: string): Marker | null {
|
||||
// Join line continuations (trailing backslash) into single lines
|
||||
const lower = contents.replace(/\\\r?\n\s*/g, " ").toLowerCase();
|
||||
for (const line of lower.split(/\r?\n/)) {
|
||||
if (!line.includes("gateway")) {
|
||||
continue;
|
||||
}
|
||||
for (const marker of EXTRA_MARKERS) {
|
||||
if (line.includes(marker)) {
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasGatewayServiceMarker(content: string): boolean {
|
||||
const lower = content.toLowerCase();
|
||||
const markerKeys = ["openclaw_service_marker"];
|
||||
|
|
@ -237,7 +253,7 @@ async function scanSystemdDir(params: {
|
|||
});
|
||||
|
||||
for (const { entry, name, fullPath, contents } of candidates) {
|
||||
const marker = detectMarker(contents);
|
||||
const marker = detectMarkerLineWithGateway(contents);
|
||||
if (!marker) {
|
||||
continue;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue