mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 07:10:43 +00:00
fix: honor node systemd unit activation
This commit is contained in:
@@ -74,6 +74,7 @@ Docs: https://docs.openclaw.ai
|
||||
- Docker: copy patched dependency files into runtime images so downstream `pnpm install` layers keep working. Fixes #69224. Thanks @gucasbrg.
|
||||
- Agents/runtime: submit heartbeat, cron, and exec wakeups as transient runtime context instead of visible user prompts, keeping synthetic system work out of chat transcripts. Fixes #66496 and #66814. Thanks @jeades and @mandomaker.
|
||||
- Telegram: include native quote excerpts automatically for threaded replies and reply tags when the original Telegram text is available, without adding another config knob. Fixes #6975. Thanks @rex05ai.
|
||||
- Node/Linux: make `openclaw node install` enable and restart the `openclaw-node` systemd unit instead of the gateway unit on node-only VMs. Fixes #68287. Thanks @dlebee-agent.
|
||||
- Telegram: preserve exact selected quote text when sending native quote replies, and retry with legacy replies if Telegram rejects quote parameters. (#71952) Thanks @rubencu.
|
||||
- Plugins/CLI: preserve manifest name, description, format, and source metadata in cold `openclaw plugins list` output without importing plugin runtime. Thanks @shakkernerd.
|
||||
- Security/audit: read channel exposure and plugin allowlist ownership from read-only plugin index metadata so cold audits do not depend on loaded channel runtime. Thanks @shakkernerd.
|
||||
|
||||
@@ -17,6 +17,7 @@ vi.mock("node:child_process", async () => {
|
||||
import { splitArgsPreservingQuotes } from "./arg-split.js";
|
||||
import { parseSystemdExecStart } from "./systemd-unit.js";
|
||||
import {
|
||||
installSystemdService,
|
||||
isNonFatalSystemdInstallProbeError,
|
||||
isSystemdServiceEnabled,
|
||||
isSystemdUserServiceAvailable,
|
||||
@@ -26,6 +27,7 @@ import {
|
||||
resolveSystemdUserUnitPath,
|
||||
stageSystemdService,
|
||||
stopSystemdService,
|
||||
uninstallSystemdService,
|
||||
} from "./systemd.js";
|
||||
|
||||
type ExecFileError = Error & {
|
||||
@@ -36,6 +38,7 @@ type ExecFileError = Error & {
|
||||
const TEST_SERVICE_HOME = "/home/test";
|
||||
const TEST_MANAGED_HOME = "/tmp/openclaw-test-home";
|
||||
const GATEWAY_SERVICE = "openclaw-gateway.service";
|
||||
const NODE_SERVICE = "openclaw-node.service";
|
||||
|
||||
const createExecFileError = (
|
||||
message: string,
|
||||
@@ -749,6 +752,95 @@ describe("stageSystemdService", () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe("systemd service install and uninstall", () => {
|
||||
async function withNodeSystemdFixture(
|
||||
run: (context: { env: Record<string, string>; unitPath: string }) => Promise<void>,
|
||||
): Promise<void> {
|
||||
const tempHomeRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-node-systemd-"));
|
||||
const home = path.join(tempHomeRoot, "home");
|
||||
const stateDir = path.join(home, ".openclaw");
|
||||
const env = {
|
||||
HOME: home,
|
||||
OPENCLAW_STATE_DIR: stateDir,
|
||||
OPENCLAW_SYSTEMD_UNIT: "openclaw-node",
|
||||
};
|
||||
const unitPath = resolveSystemdUserUnitPath(env);
|
||||
|
||||
try {
|
||||
await fs.mkdir(stateDir, { recursive: true });
|
||||
await run({ env, unitPath });
|
||||
} finally {
|
||||
await fs.rm(tempHomeRoot, { recursive: true, force: true });
|
||||
}
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
vi.restoreAllMocks();
|
||||
execFileMock.mockReset();
|
||||
});
|
||||
|
||||
it("activates the OPENCLAW_SYSTEMD_UNIT override during install", async () => {
|
||||
await withNodeSystemdFixture(async ({ env, unitPath }) => {
|
||||
execFileMock
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
assertUserSystemctlArgs(args, "status");
|
||||
cb(null, "", "");
|
||||
})
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
assertUserSystemctlArgs(args, "daemon-reload");
|
||||
cb(null, "", "");
|
||||
})
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
assertUserSystemctlArgs(args, "enable", NODE_SERVICE);
|
||||
cb(null, "", "");
|
||||
})
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
assertUserSystemctlArgs(args, "restart", NODE_SERVICE);
|
||||
cb(null, "", "");
|
||||
});
|
||||
|
||||
await installSystemdService({
|
||||
env,
|
||||
stdout: { write: vi.fn() } as unknown as NodeJS.WritableStream,
|
||||
programArguments: ["/usr/bin/openclaw", "node", "run"],
|
||||
workingDirectory: "/tmp",
|
||||
environment: {
|
||||
OPENCLAW_SYSTEMD_UNIT: "openclaw-node",
|
||||
},
|
||||
});
|
||||
|
||||
const unit = await fs.readFile(unitPath, "utf8");
|
||||
expect(unitPath).toMatch(/openclaw-node\.service$/);
|
||||
expect(unit).toContain("openclaw node run");
|
||||
expect(execFileMock).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
});
|
||||
|
||||
it("disables the OPENCLAW_SYSTEMD_UNIT override during uninstall", async () => {
|
||||
await withNodeSystemdFixture(async ({ env, unitPath }) => {
|
||||
await fs.mkdir(path.dirname(unitPath), { recursive: true });
|
||||
await fs.writeFile(unitPath, "[Unit]\nDescription=OpenClaw Node\n", "utf8");
|
||||
|
||||
execFileMock
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
assertUserSystemctlArgs(args, "status");
|
||||
cb(null, "", "");
|
||||
})
|
||||
.mockImplementationOnce((_cmd, args, _opts, cb) => {
|
||||
assertUserSystemctlArgs(args, "disable", "--now", NODE_SERVICE);
|
||||
cb(null, "", "");
|
||||
});
|
||||
|
||||
const { write, stdout } = createWritableStreamMock();
|
||||
await uninstallSystemdService({ env, stdout });
|
||||
|
||||
await expect(fs.access(unitPath)).rejects.toMatchObject({ code: "ENOENT" });
|
||||
expect(String(write.mock.calls[0]?.[0])).toContain("Removed systemd service");
|
||||
expect(execFileMock).toHaveBeenCalledTimes(2);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("systemd service control", () => {
|
||||
const assertMachineRestartArgs = (args: string[]) => {
|
||||
assertMachineUserSystemctlArgs(args, "debian", "restart", GATEWAY_SERVICE);
|
||||
|
||||
@@ -539,7 +539,7 @@ export async function stageSystemdService({
|
||||
}
|
||||
|
||||
async function activateSystemdService(params: { env: GatewayServiceEnv }) {
|
||||
const serviceName = resolveGatewaySystemdServiceName(params.env.OPENCLAW_PROFILE);
|
||||
const serviceName = resolveSystemdServiceName(params.env);
|
||||
const unitName = `${serviceName}.service`;
|
||||
const reload = await execSystemctlUser(params.env, ["daemon-reload"]);
|
||||
if (reload.code !== 0) {
|
||||
@@ -588,7 +588,7 @@ export async function uninstallSystemdService({
|
||||
stdout,
|
||||
}: GatewayServiceManageArgs): Promise<void> {
|
||||
await assertSystemdAvailable(env);
|
||||
const serviceName = resolveGatewaySystemdServiceName(env.OPENCLAW_PROFILE);
|
||||
const serviceName = resolveSystemdServiceName(env);
|
||||
const unitName = `${serviceName}.service`;
|
||||
await execSystemctlUser(env, ["disable", "--now", unitName]);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user