Daemon: harden WSL2 systemctl install checks (#39294)

* Daemon: harden WSL2 systemctl install checks

* Changelog: note WSL2 daemon install hardening

* Daemon: tighten systemctl failure classification
This commit is contained in:
Vincent Koc
2026-03-07 19:43:19 -05:00
committed by GitHub
parent f195af0b22
commit a56841b98c
7 changed files with 286 additions and 1 deletions

View File

@@ -1,4 +1,5 @@
import fs from "node:fs/promises";
import os from "node:os";
import { beforeEach, describe, expect, it, vi } from "vitest";
const execFileMock = vi.hoisted(() => vi.fn());
@@ -164,6 +165,96 @@ describe("isSystemdServiceEnabled", () => {
expect(result).toBe(false);
});
it("returns false for the WSL2 Ubuntu 24.04 wrapper-only is-enabled failure", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
mockManagedUnitPresent();
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
const err = new Error(
"Command failed: systemctl --user is-enabled openclaw-gateway.service",
) as Error & { code?: number };
err.code = 1;
cb(err, "", "");
});
const result = await isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } });
expect(result).toBe(false);
});
it("returns false when is-enabled cannot connect to the user bus without machine fallback", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
mockManagedUnitPresent();
vi.spyOn(os, "userInfo").mockImplementationOnce(() => {
throw new Error("no user info");
});
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
cb(
createExecFileError("Failed to connect to bus", { stderr: "Failed to connect to bus" }),
"",
"",
);
});
const result = await isSystemdServiceEnabled({
env: { HOME: "/tmp/openclaw-test-home", USER: "", LOGNAME: "" },
});
expect(result).toBe(false);
});
it("returns false when both direct and machine-scope is-enabled checks report bus unavailability", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
mockManagedUnitPresent();
execFileMock
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
cb(
createExecFileError("Failed to connect to bus", { stderr: "Failed to connect to bus" }),
"",
"",
);
})
.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual([
"--machine",
"debian@",
"--user",
"is-enabled",
"openclaw-gateway.service",
]);
cb(
createExecFileError("Failed to connect to user scope bus via local transport", {
stderr:
"Failed to connect to user scope bus via local transport: $DBUS_SESSION_BUS_ADDRESS and $XDG_RUNTIME_DIR not defined",
}),
"",
"",
);
});
const result = await isSystemdServiceEnabled({
env: { HOME: "/tmp/openclaw-test-home", USER: "debian" },
});
expect(result).toBe(false);
});
it("throws when generic wrapper errors report infrastructure failures", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
mockManagedUnitPresent();
execFileMock.mockImplementationOnce((_cmd, args, _opts, cb) => {
expect(args).toEqual(["--user", "is-enabled", "openclaw-gateway.service"]);
const err = new Error(
"Command failed: systemctl --user is-enabled openclaw-gateway.service",
) as Error & { code?: number };
err.code = 1;
cb(err, "", "read-only file system");
});
await expect(
isSystemdServiceEnabled({ env: { HOME: "/tmp/openclaw-test-home" } }),
).rejects.toThrow("systemctl is-enabled unavailable: read-only file system");
});
it("throws when systemctl is-enabled fails for non-state errors", async () => {
const { isSystemdServiceEnabled } = await import("./systemd.js");
mockManagedUnitPresent();