mirror of
https://github.com/openclaw/openclaw.git
synced 2026-10-03 01:29:56 +00:00
refactor(daemon): decouple service paths from display text (#162597)
This commit is contained in:
parent
9505aedf41
commit
a24f8141da
15 changed files with 229 additions and 176 deletions
|
|
@ -159,6 +159,13 @@ describe("printDaemonStatus", () => {
|
|||
|
||||
it("preserves Gateway metadata and input while redacting private definitions in JSON", () => {
|
||||
const server = { version: "2026.5.6", buildId: "build-2026.5.6", connId: "conn-1" };
|
||||
const extraService: ExtraGatewayService = {
|
||||
platform: "linux",
|
||||
label: "sibling.service",
|
||||
detail: "unit: /etc/systemd/system/sibling.service",
|
||||
sourcePath: "/etc/systemd/system/sibling.service",
|
||||
scope: "system",
|
||||
};
|
||||
const command: GatewayServiceCommandConfig = {
|
||||
programArguments: ["node"],
|
||||
environment: {
|
||||
|
|
@ -175,12 +182,21 @@ describe("printDaemonStatus", () => {
|
|||
};
|
||||
const original = structuredClone(command);
|
||||
printDaemonStatus(
|
||||
{ service: { command }, rpc: { ok: true, server } },
|
||||
{ service: { command }, rpc: { ok: true, server }, extraServices: [extraService] },
|
||||
{ json: true, deep: true },
|
||||
);
|
||||
expect(runtime.writeJson).toHaveBeenCalledOnce();
|
||||
const payload = runtime.writeJson.mock.calls[0]?.[0];
|
||||
expect(payload).toHaveProperty("rpc.server", server);
|
||||
expect(payload).toHaveProperty("extraServices", [
|
||||
{
|
||||
platform: "linux",
|
||||
label: "sibling.service",
|
||||
detail: "unit: /etc/systemd/system/sibling.service",
|
||||
scope: "system",
|
||||
},
|
||||
]);
|
||||
expect(extraService.sourcePath).toBe("/etc/systemd/system/sibling.service");
|
||||
expect(payload).not.toHaveProperty("service.command.managedDefinition");
|
||||
expect(payload).not.toHaveProperty("service.command.managedOverrides");
|
||||
expect(payload).not.toHaveProperty("service.command.definitionPaths");
|
||||
|
|
|
|||
|
|
@ -76,6 +76,7 @@ export function printDaemonStatus(status: DaemonStatus, opts: { json: boolean; d
|
|||
if (opts.json) {
|
||||
defaultRuntime.writeJson({
|
||||
...status,
|
||||
extraServices: status.extraServices.map(({ sourcePath: _sourcePath, ...service }) => service),
|
||||
service: projectDaemonServiceForJson(status.service, { includeDefinitionPaths: false }),
|
||||
});
|
||||
return;
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ function setupLegacyMacService() {
|
|||
platform: "darwin",
|
||||
label: LEGACY_MAC_LABEL,
|
||||
detail: `plist: ${LEGACY_MAC_PLIST}`,
|
||||
sourcePath: LEGACY_MAC_PLIST,
|
||||
scope: "user",
|
||||
legacy: true,
|
||||
},
|
||||
|
|
@ -108,6 +109,7 @@ describe("maybeScanExtraGatewayServices", () => {
|
|||
platform: "linux" as const,
|
||||
label: "custom-gateway.service",
|
||||
detail: `unit: ${unitPath}`,
|
||||
sourcePath: unitPath,
|
||||
scope,
|
||||
legacy: false,
|
||||
};
|
||||
|
|
@ -183,6 +185,7 @@ describe("maybeScanExtraGatewayServices", () => {
|
|||
platform: "darwin" as const,
|
||||
label: "com.example.openclaw-gateway",
|
||||
detail: "plist: /Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
sourcePath: "/Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
scope: "user" as const,
|
||||
legacy: false,
|
||||
};
|
||||
|
|
@ -259,6 +262,7 @@ describe("maybeScanExtraGatewayServices", () => {
|
|||
platform: "linux",
|
||||
label: "clawdbot-gateway.service",
|
||||
detail: "unit: /home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
sourcePath: "/home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
scope: "user",
|
||||
legacy: true,
|
||||
},
|
||||
|
|
@ -299,6 +303,7 @@ describe("maybeScanExtraGatewayServices", () => {
|
|||
platform: "linux",
|
||||
label,
|
||||
detail: `unit: /home/test/.config/systemd/user/${label}`,
|
||||
sourcePath: `/home/test/.config/systemd/user/${label}`,
|
||||
scope: "user",
|
||||
marker: "clawdbot",
|
||||
legacy: true,
|
||||
|
|
@ -514,6 +519,7 @@ describe("maybeScanExtraGatewayServices", () => {
|
|||
platform: "linux",
|
||||
label: "clawdbot-gateway.service",
|
||||
detail: "unit: /home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
sourcePath: "/home/test/.config/systemd/user/clawdbot-gateway.service",
|
||||
scope: "user",
|
||||
legacy: true,
|
||||
},
|
||||
|
|
|
|||
|
|
@ -116,14 +116,6 @@ async function confirmLegacyLaunchdServiceUnloaded(serviceTarget: string): Promi
|
|||
return false;
|
||||
}
|
||||
|
||||
function extractDetailPath(detail: string, prefix: string): string | null {
|
||||
if (!detail.startsWith(prefix)) {
|
||||
return null;
|
||||
}
|
||||
const value = detail.slice(prefix.length).trim();
|
||||
return value.length > 0 ? value : null;
|
||||
}
|
||||
|
||||
async function filterInactiveExtraGatewayServices(
|
||||
services: ExtraGatewayService[],
|
||||
): Promise<ExtraGatewayService[]> {
|
||||
|
|
@ -205,7 +197,7 @@ async function cleanupLegacyDarwinServices(
|
|||
const failed: string[] = [];
|
||||
|
||||
for (const svc of services) {
|
||||
const plistPath = extractDetailPath(svc.detail, "plist:");
|
||||
const plistPath = svc.sourcePath;
|
||||
if (!plistPath) {
|
||||
failed.push(`${svc.label} (missing plist path)`);
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ export type ExtraGatewayService = {
|
|||
platform: "darwin" | "linux" | "win32";
|
||||
label: string;
|
||||
detail: string;
|
||||
sourcePath?: string;
|
||||
scope: "user" | "system";
|
||||
marker?: "openclaw" | "clawdbot";
|
||||
legacy?: boolean;
|
||||
|
|
@ -116,6 +117,7 @@ export async function scanSystemdDir(params: {
|
|||
platform: "linux",
|
||||
label: entry,
|
||||
detail: `unit: ${fullPath}`,
|
||||
sourcePath: fullPath,
|
||||
scope: params.scope,
|
||||
marker,
|
||||
legacy: marker !== "openclaw",
|
||||
|
|
|
|||
138
src/daemon/inspect.cleanup-hints.test.ts
Normal file
138
src/daemon/inspect.cleanup-hints.test.ts
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
import { describe, expect, it } from "vitest";
|
||||
import { renderGatewayServiceCleanupHints } from "./inspect.js";
|
||||
|
||||
describe("renderGatewayServiceCleanupHints", () => {
|
||||
it("does not suggest removing a gateway when no extra service was detected", () => {
|
||||
expect(renderGatewayServiceCleanupHints([])).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
title: "targets the detected macOS LaunchAgent instead of the active gateway",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.openclaw-gateway",
|
||||
source: "plist: /Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
sourcePath: "/Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
scope: "user",
|
||||
firstHint: "launchctl bootout gui/$UID/com.example.openclaw-gateway",
|
||||
secondHint: "rm /Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
},
|
||||
{
|
||||
title: "uses the system domain for a detected macOS LaunchDaemon",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.openclaw-gateway",
|
||||
source: "plist: /Library/LaunchDaemons/com.example.openclaw-gateway.plist",
|
||||
sourcePath: "/Library/LaunchDaemons/com.example.openclaw-gateway.plist",
|
||||
scope: "system",
|
||||
firstHint: "sudo launchctl bootout system/com.example.openclaw-gateway",
|
||||
secondHint: "sudo rm /Library/LaunchDaemons/com.example.openclaw-gateway.plist",
|
||||
},
|
||||
{
|
||||
title: "keeps global macOS LaunchAgents in the GUI domain",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.openclaw-gateway",
|
||||
source: "plist: /Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
sourcePath: "/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
scope: "system",
|
||||
firstHint: "launchctl bootout gui/$UID/com.example.openclaw-gateway",
|
||||
secondHint: "sudo rm /Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
},
|
||||
{
|
||||
title: "inspects the detected user-level systemd unit without removing it",
|
||||
platform: "linux",
|
||||
serviceName: "custom-gateway.service",
|
||||
source: "unit: /home/test/.config/systemd/user/custom-gateway.service",
|
||||
sourcePath: "/home/test/.config/systemd/user/custom-gateway.service",
|
||||
scope: "user",
|
||||
firstHint: "systemctl --user status -- custom-gateway.service",
|
||||
secondHint: "systemctl --user cat -- custom-gateway.service",
|
||||
},
|
||||
{
|
||||
title: "inspects the detected system-level systemd unit without removing it",
|
||||
platform: "linux",
|
||||
serviceName: "custom-gateway.service",
|
||||
source: "unit: /etc/systemd/system/custom-gateway.service",
|
||||
sourcePath: "/etc/systemd/system/custom-gateway.service",
|
||||
scope: "system",
|
||||
firstHint: "systemctl --system status -- custom-gateway.service",
|
||||
secondHint: "systemctl --system cat -- custom-gateway.service",
|
||||
},
|
||||
{
|
||||
title: "terminates systemctl options before a detected unit that begins with a dash",
|
||||
platform: "linux",
|
||||
serviceName: "-custom-gateway.service",
|
||||
source: "unit: /home/test/.config/systemd/user/-custom-gateway.service",
|
||||
sourcePath: "/home/test/.config/systemd/user/-custom-gateway.service",
|
||||
scope: "user",
|
||||
firstHint: "systemctl --user status -- -custom-gateway.service",
|
||||
secondHint: "systemctl --user cat -- -custom-gateway.service",
|
||||
},
|
||||
{
|
||||
title: "shell-quotes detected POSIX service labels and paths",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.gateway; touch injected",
|
||||
source: "plist: /Users/test/Launch Agents/example's gateway.plist",
|
||||
sourcePath: "/Users/test/Launch Agents/example's gateway.plist",
|
||||
scope: "user",
|
||||
firstHint: "launchctl bootout gui/$UID/'com.example.gateway; touch injected'",
|
||||
secondHint: "rm '/Users/test/Launch Agents/example'\\''s gateway.plist'",
|
||||
},
|
||||
] as const)(
|
||||
"$title",
|
||||
({ platform, serviceName, source, sourcePath, scope, firstHint, secondHint }) => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform,
|
||||
label: serviceName,
|
||||
detail: source,
|
||||
sourcePath,
|
||||
scope,
|
||||
},
|
||||
]),
|
||||
).toEqual([firstHint, secondHint]);
|
||||
},
|
||||
);
|
||||
|
||||
it("inspects the detected Windows scheduled task without suggesting removal", () => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform: "win32",
|
||||
label: "\\OpenClaw Gateway Backup",
|
||||
detail: "task: \\OpenClaw Gateway Backup",
|
||||
scope: "system",
|
||||
},
|
||||
]),
|
||||
).toEqual(['schtasks /Query /TN "\\OpenClaw Gateway Backup" /V /FO LIST']);
|
||||
});
|
||||
|
||||
it.each(["$(Start-Process calc)", "%OPENCLAW_GATEWAY_TASK%", "unsafe&task", "task`name"])(
|
||||
"does not render a Windows task name expandable by cmd.exe or PowerShell: %s",
|
||||
(label) => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform: "win32",
|
||||
label,
|
||||
detail: `task: ${label}`,
|
||||
scope: "system",
|
||||
},
|
||||
]),
|
||||
).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not invent a removal path when service metadata omits it", () => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform: "darwin",
|
||||
label: "com.example.openclaw-gateway",
|
||||
detail: "loaded",
|
||||
scope: "user",
|
||||
},
|
||||
]),
|
||||
).toEqual(["launchctl bootout gui/$UID/com.example.openclaw-gateway"]);
|
||||
});
|
||||
});
|
||||
|
|
@ -131,131 +131,6 @@ describe("detectMarkerLineWithGateway", () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe("renderGatewayServiceCleanupHints", () => {
|
||||
it("does not suggest removing a gateway when no extra service was detected", () => {
|
||||
expect(renderGatewayServiceCleanupHints([])).toEqual([]);
|
||||
});
|
||||
|
||||
it.each([
|
||||
{
|
||||
title: "targets the detected macOS LaunchAgent instead of the active gateway",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.openclaw-gateway",
|
||||
source: "plist: /Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
scope: "user",
|
||||
firstHint: "launchctl bootout gui/$UID/com.example.openclaw-gateway",
|
||||
secondHint: "rm /Users/test/Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
},
|
||||
{
|
||||
title: "uses the system domain for a detected macOS LaunchDaemon",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.openclaw-gateway",
|
||||
source: "plist: /Library/LaunchDaemons/com.example.openclaw-gateway.plist",
|
||||
scope: "system",
|
||||
firstHint: "sudo launchctl bootout system/com.example.openclaw-gateway",
|
||||
secondHint: "sudo rm /Library/LaunchDaemons/com.example.openclaw-gateway.plist",
|
||||
},
|
||||
{
|
||||
title: "keeps global macOS LaunchAgents in the GUI domain",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.openclaw-gateway",
|
||||
source: "plist: /Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
scope: "system",
|
||||
firstHint: "launchctl bootout gui/$UID/com.example.openclaw-gateway",
|
||||
secondHint: "sudo rm /Library/LaunchAgents/com.example.openclaw-gateway.plist",
|
||||
},
|
||||
{
|
||||
title: "inspects the detected user-level systemd unit without removing it",
|
||||
platform: "linux",
|
||||
serviceName: "custom-gateway.service",
|
||||
source: "unit: /home/test/.config/systemd/user/custom-gateway.service",
|
||||
scope: "user",
|
||||
firstHint: "systemctl --user status -- custom-gateway.service",
|
||||
secondHint: "systemctl --user cat -- custom-gateway.service",
|
||||
},
|
||||
{
|
||||
title: "inspects the detected system-level systemd unit without removing it",
|
||||
platform: "linux",
|
||||
serviceName: "custom-gateway.service",
|
||||
source: "unit: /etc/systemd/system/custom-gateway.service",
|
||||
scope: "system",
|
||||
firstHint: "systemctl --system status -- custom-gateway.service",
|
||||
secondHint: "systemctl --system cat -- custom-gateway.service",
|
||||
},
|
||||
{
|
||||
title: "terminates systemctl options before a detected unit that begins with a dash",
|
||||
platform: "linux",
|
||||
serviceName: "-custom-gateway.service",
|
||||
source: "unit: /home/test/.config/systemd/user/-custom-gateway.service",
|
||||
scope: "user",
|
||||
firstHint: "systemctl --user status -- -custom-gateway.service",
|
||||
secondHint: "systemctl --user cat -- -custom-gateway.service",
|
||||
},
|
||||
{
|
||||
title: "shell-quotes detected POSIX service labels and paths",
|
||||
platform: "darwin",
|
||||
serviceName: "com.example.gateway; touch injected",
|
||||
source: "plist: /Users/test/Launch Agents/example's gateway.plist",
|
||||
scope: "user",
|
||||
firstHint: "launchctl bootout gui/$UID/'com.example.gateway; touch injected'",
|
||||
secondHint: "rm '/Users/test/Launch Agents/example'\\''s gateway.plist'",
|
||||
},
|
||||
] as const)("$title", ({ platform, serviceName, source, scope, firstHint, secondHint }) => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform,
|
||||
label: serviceName,
|
||||
detail: source,
|
||||
scope,
|
||||
},
|
||||
]),
|
||||
).toEqual([firstHint, secondHint]);
|
||||
});
|
||||
|
||||
it("inspects the detected Windows scheduled task without suggesting removal", () => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform: "win32",
|
||||
label: "\\OpenClaw Gateway Backup",
|
||||
detail: "task: \\OpenClaw Gateway Backup",
|
||||
scope: "system",
|
||||
},
|
||||
]),
|
||||
).toEqual(['schtasks /Query /TN "\\OpenClaw Gateway Backup" /V /FO LIST']);
|
||||
});
|
||||
|
||||
it.each(["$(Start-Process calc)", "%OPENCLAW_GATEWAY_TASK%", "unsafe&task", "task`name"])(
|
||||
"does not render a Windows task name expandable by cmd.exe or PowerShell: %s",
|
||||
(label) => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform: "win32",
|
||||
label,
|
||||
detail: `task: ${label}`,
|
||||
scope: "system",
|
||||
},
|
||||
]),
|
||||
).toEqual([]);
|
||||
},
|
||||
);
|
||||
|
||||
it("does not invent a removal path when service metadata omits it", () => {
|
||||
expect(
|
||||
renderGatewayServiceCleanupHints([
|
||||
{
|
||||
platform: "darwin",
|
||||
label: "com.example.openclaw-gateway",
|
||||
detail: "loaded",
|
||||
scope: "user",
|
||||
},
|
||||
]),
|
||||
).toEqual(["launchctl bootout gui/$UID/com.example.openclaw-gateway"]);
|
||||
});
|
||||
});
|
||||
|
||||
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.
|
||||
|
|
@ -299,6 +174,7 @@ describe("findExtraGatewayServices (linux / scanSystemdDir) — real filesystem"
|
|||
platform: "linux",
|
||||
label: "clawdbot-gateway.service",
|
||||
detail: `unit: ${unitPath}`,
|
||||
sourcePath: unitPath,
|
||||
scope: "user",
|
||||
marker: "clawdbot",
|
||||
legacy: true,
|
||||
|
|
@ -343,6 +219,7 @@ describe("findExtraGatewayServices (linux / scanSystemdDir) — real filesystem"
|
|||
platform: "linux",
|
||||
label: "clawdbot-gateway.service",
|
||||
detail: `unit: ${unitPath}`,
|
||||
sourcePath: unitPath,
|
||||
scope: "user",
|
||||
marker: "clawdbot",
|
||||
legacy: true,
|
||||
|
|
@ -382,6 +259,7 @@ describe("findExtraGatewayServices (linux / scanSystemdDir) — real filesystem"
|
|||
platform: "linux",
|
||||
label: "custom-openclaw.service",
|
||||
detail: `unit: ${unitPath}`,
|
||||
sourcePath: unitPath,
|
||||
scope: "user",
|
||||
marker: "openclaw",
|
||||
legacy: false,
|
||||
|
|
@ -507,6 +385,7 @@ describe("findExtraGatewayServices (darwin / scanLaunchdDir) — real filesystem
|
|||
platform: "darwin",
|
||||
label: "com.example.openclaw-gateway",
|
||||
detail: `plist: ${plistPath}`,
|
||||
sourcePath: plistPath,
|
||||
scope: "user",
|
||||
marker: "openclaw",
|
||||
legacy: false,
|
||||
|
|
@ -722,6 +601,7 @@ describe("managed Gateway inventory projections", () => {
|
|||
label: "openclaw@.service",
|
||||
scope: "system",
|
||||
detail: `unit: ${path.join("/etc/systemd/system", "openclaw@.service")}`,
|
||||
sourcePath: path.join("/etc/systemd/system", "openclaw@.service"),
|
||||
marker: "openclaw",
|
||||
legacy: false,
|
||||
});
|
||||
|
|
@ -761,7 +641,7 @@ describe("managed Gateway inventory projections", () => {
|
|||
}
|
||||
});
|
||||
|
||||
it("finds Gateways in XDG, runtime, and systemd control load paths before build admission", async () => {
|
||||
it("keeps discovered native selectors independent of display details", async () => {
|
||||
Object.defineProperty(process, "platform", { configurable: true, value: "linux" });
|
||||
const home = tempDirs.make("managed-systemd-load-paths-", os.tmpdir());
|
||||
const write = isolateNativeRoots(home);
|
||||
|
|
@ -814,6 +694,25 @@ describe("managed Gateway inventory projections", () => {
|
|||
"system-generated-gateway.service",
|
||||
"transient-gateway.service",
|
||||
]);
|
||||
const inventory = await import("./inspect.js");
|
||||
for (const service of result.services) {
|
||||
service.detail = "Discovered Gateway";
|
||||
}
|
||||
vi.spyOn(inventory, "listManagedOpenClawGatewayServices").mockResolvedValue(result);
|
||||
const { discoverManagedGatewayBindings } = await import("./managed-gateway-bindings.js");
|
||||
const bindings = await discoverManagedGatewayBindings({ HOME: home });
|
||||
expect(bindings).toHaveLength(7);
|
||||
expect(bindings.map((binding) => binding.systemdReadTarget?.unitPath)).toEqual(
|
||||
expect.arrayContaining([
|
||||
path.join(configHome, "systemd/user/config-gateway.service"),
|
||||
path.join(dataHome, "systemd/user/data-gateway.service"),
|
||||
"/etc/systemd/system.control/system-gateway.service",
|
||||
path.join(runtimeDir, "systemd/generator/generated-gateway.service"),
|
||||
path.join(runtimeDir, "systemd/transient/transient-gateway.service"),
|
||||
"/run/systemd/user/run-gateway.service",
|
||||
"/run/systemd/generator/system-generated-gateway.service",
|
||||
]),
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
|
|
@ -864,6 +763,7 @@ describe("managed Gateway inventory projections", () => {
|
|||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${file}`,
|
||||
sourcePath: file,
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
legacy: false,
|
||||
|
|
|
|||
|
|
@ -89,9 +89,7 @@ export function renderGatewayServiceCleanupHints(
|
|||
for (const service of services) {
|
||||
switch (service.platform) {
|
||||
case "darwin": {
|
||||
const plistPath = service.detail.startsWith("plist:")
|
||||
? service.detail.slice("plist:".length).trim()
|
||||
: undefined;
|
||||
const plistPath = service.sourcePath;
|
||||
// Global LaunchAgents still run in a GUI domain; only LaunchDaemons
|
||||
// belong to the system domain regardless of their shared file scope.
|
||||
const domain =
|
||||
|
|
@ -184,6 +182,7 @@ async function scanLaunchdDir(params: {
|
|||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${fullPath}`,
|
||||
sourcePath: fullPath,
|
||||
scope: params.scope,
|
||||
marker,
|
||||
legacy: marker !== "openclaw" || isLegacyLabel(label),
|
||||
|
|
@ -506,6 +505,7 @@ async function scanGatewayServices(
|
|||
platform: "linux",
|
||||
label: unit.name,
|
||||
detail: `unit: ${unit.fragmentPath}`,
|
||||
sourcePath: unit.fragmentPath,
|
||||
scope,
|
||||
marker: marker ?? "openclaw",
|
||||
legacy: marker === "clawdbot",
|
||||
|
|
|
|||
|
|
@ -183,6 +183,7 @@ it.each([
|
|||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${locations.local.plist}`,
|
||||
sourcePath: locations.local.plist,
|
||||
scope: "user",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -190,6 +191,7 @@ it.each([
|
|||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${locations.global.plist}`,
|
||||
sourcePath: locations.global.plist,
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -197,6 +199,7 @@ it.each([
|
|||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${systemPlist}`,
|
||||
sourcePath: systemPlist,
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -353,7 +356,13 @@ it.each([
|
|||
if (scenario.loaded === "system") {
|
||||
expect(
|
||||
inventory.renderGatewayServiceCleanupHints([
|
||||
{ platform: "darwin", label, detail: `plist: ${systemPlist}`, scope: "system" },
|
||||
{
|
||||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${systemPlist}`,
|
||||
sourcePath: systemPlist,
|
||||
scope: "system",
|
||||
},
|
||||
]),
|
||||
).toContain(`sudo launchctl bootout system/${label}`);
|
||||
}
|
||||
|
|
@ -380,6 +389,7 @@ it.each([false, true])(
|
|||
scope: "user" as const,
|
||||
label: "custom-sibling.service",
|
||||
detail: "unit: /synthetic/custom-sibling.service",
|
||||
sourcePath: "/synthetic/custom-sibling.service",
|
||||
};
|
||||
vi.spyOn(inventory, "listManagedOpenClawGatewayServices").mockResolvedValue({
|
||||
services: [sibling, sibling],
|
||||
|
|
|
|||
|
|
@ -98,18 +98,11 @@ function profileEnvFields(profile: string): GatewayServiceEnv {
|
|||
return profile === "default" ? {} : { OPENCLAW_PROFILE: profile };
|
||||
}
|
||||
|
||||
function detailPath(prefix: string, detail: string): string | undefined {
|
||||
if (!detail.startsWith(prefix)) {
|
||||
return undefined;
|
||||
}
|
||||
return detail.slice(prefix.length).trim();
|
||||
}
|
||||
|
||||
function bindingFromSystemdService(
|
||||
svc: ExtraGatewayService,
|
||||
env: Record<string, string | undefined>,
|
||||
): ManagedGatewayBinding {
|
||||
const unitPath = detailPath("unit:", svc.detail);
|
||||
const unitPath = svc.sourcePath;
|
||||
const unitName = resolveSystemdTemplateInstanceName(svc.label, {
|
||||
OPENCLAW_SYSTEMD_UNIT: svc.label,
|
||||
});
|
||||
|
|
@ -125,7 +118,7 @@ function bindingFromLaunchdService(
|
|||
svc: ExtraGatewayService,
|
||||
env: Record<string, string | undefined>,
|
||||
): ManagedGatewayBinding {
|
||||
const plistPath = detailPath("plist:", svc.detail);
|
||||
const plistPath = svc.sourcePath;
|
||||
return {
|
||||
scope: svc.scope,
|
||||
...(plistPath ? { launchAgentPlistPath: plistPath } : {}),
|
||||
|
|
|
|||
|
|
@ -156,6 +156,7 @@ it.each<ScopeCase>([
|
|||
marker: "openclaw" as const,
|
||||
label: otherName,
|
||||
detail: `unit: ${otherPath}`,
|
||||
sourcePath: otherPath,
|
||||
},
|
||||
]
|
||||
: []),
|
||||
|
|
@ -165,6 +166,7 @@ it.each<ScopeCase>([
|
|||
marker: "openclaw",
|
||||
label: file,
|
||||
detail: `unit: ${target.unitPath}`,
|
||||
sourcePath: target.unitPath,
|
||||
},
|
||||
]);
|
||||
vi.spyOn(fs, "access").mockRejectedValue(
|
||||
|
|
@ -511,6 +513,7 @@ it("reads the system template instance while a separate user Gateway is installe
|
|||
marker: "openclaw",
|
||||
label: "openclaw@.service",
|
||||
detail: `unit: ${templateUnit}`,
|
||||
sourcePath: templateUnit,
|
||||
},
|
||||
]);
|
||||
mockProcessPlatform("linux");
|
||||
|
|
@ -651,6 +654,7 @@ it("findSystemdGatewayInstallation expands a system template to this account's i
|
|||
platform: "linux",
|
||||
label: "openclaw@.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw@.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw@.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -4,18 +4,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|||
import { findInstalledSystemdGatewayScope, isNonFatalSystemdInstallProbeError } from "./systemd.js";
|
||||
|
||||
const findSystemGatewayServicesMock = vi.hoisted(() =>
|
||||
vi.fn<
|
||||
() => Promise<
|
||||
Array<{
|
||||
platform: "linux";
|
||||
label: string;
|
||||
detail: string;
|
||||
scope: "user" | "system";
|
||||
marker?: "openclaw" | "clawdbot";
|
||||
legacy?: boolean;
|
||||
}>
|
||||
>
|
||||
>(async () => []),
|
||||
vi.fn<typeof import("./inspect.js").findSystemGatewayServices>(async () => []),
|
||||
);
|
||||
|
||||
vi.mock("./inspect.js", () => ({
|
||||
|
|
@ -81,6 +70,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -100,6 +90,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw-darlene.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw-darlene.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw-darlene.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -118,6 +109,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw-gateway-lisa.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw-gateway-lisa.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw-gateway-lisa.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -136,6 +128,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "my-custom-gateway.service",
|
||||
detail: "unit: /etc/systemd/system/my-custom-gateway.service",
|
||||
sourcePath: "/etc/systemd/system/my-custom-gateway.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -216,6 +209,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label,
|
||||
detail: `unit: /etc/systemd/system/${label}`,
|
||||
sourcePath: `/etc/systemd/system/${label}`,
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -243,6 +237,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw@.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw@.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw@.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -265,6 +260,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw@other.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw@other.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw@other.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -303,6 +299,7 @@ describe("systemd gateway identity (openclaw#119648)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw-darlene.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw-darlene.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw-darlene.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -460,8 +460,7 @@ async function findMarkerOwnedSystemSystemdUnit(
|
|||
) {
|
||||
continue;
|
||||
}
|
||||
const match = /^unit:\s*(.+)$/.exec(svc.detail.trim());
|
||||
const unitPath = match?.[1]?.trim();
|
||||
const unitPath = svc.sourcePath;
|
||||
if (unitPath) {
|
||||
const target: SystemdServiceReadTarget = {
|
||||
scope: "system",
|
||||
|
|
|
|||
|
|
@ -27,18 +27,7 @@ const assertNoSystemSystemdOwnershipMock = vi.hoisted(() =>
|
|||
vi.fn<(unitName: string, timeoutMs?: number) => Promise<void>>(async () => {}),
|
||||
);
|
||||
const findSystemGatewayServicesMock = vi.hoisted(() =>
|
||||
vi.fn<
|
||||
() => Promise<
|
||||
Array<{
|
||||
platform: "linux";
|
||||
label: string;
|
||||
detail: string;
|
||||
scope: "user" | "system";
|
||||
marker?: "openclaw" | "clawdbot";
|
||||
legacy?: boolean;
|
||||
}>
|
||||
>
|
||||
>(async () => []),
|
||||
vi.fn<typeof import("./inspect.js").findSystemGatewayServices>(async () => []),
|
||||
);
|
||||
|
||||
vi.mock("./inspect.js", () => ({
|
||||
|
|
@ -596,6 +585,7 @@ describe("system-scope gateway unit detection (openclaw#87577)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw-rescue.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw-rescue.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw-rescue.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -640,6 +630,7 @@ describe("system-scope gateway unit detection (openclaw#87577)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -660,6 +651,7 @@ describe("system-scope gateway unit detection (openclaw#87577)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
@ -678,6 +670,7 @@ describe("system-scope gateway unit detection (openclaw#87577)", () => {
|
|||
platform: "linux",
|
||||
label: "openclaw.service",
|
||||
detail: "unit: /etc/systemd/system/openclaw.service",
|
||||
sourcePath: "/etc/systemd/system/openclaw.service",
|
||||
scope: "system",
|
||||
marker: "openclaw",
|
||||
},
|
||||
|
|
|
|||
|
|
@ -29,7 +29,9 @@ it.each([
|
|||
const label = "org.example.fence-sibling";
|
||||
const plist = path.join(home, "Library", "LaunchAgents", `${label}.plist`);
|
||||
vi.spyOn(inventory, "listManagedOpenClawGatewayServices").mockResolvedValue({
|
||||
services: [{ platform: "darwin", scope: "user", label, detail: `plist: ${plist}` }],
|
||||
services: [
|
||||
{ platform: "darwin", scope: "user", label, detail: `plist: ${plist}`, sourcePath: plist },
|
||||
],
|
||||
errors: [],
|
||||
});
|
||||
const native = vi.spyOn(launchdExec, "execLaunchctl").mockImplementation(async (args) => {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue