mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-06 06:20:43 +00:00
fix: retry systemd unit activation after reload
This commit is contained in:
@@ -82,6 +82,7 @@ Docs: https://docs.openclaw.ai
|
||||
browser command is sent, and reconnect stale persistent Playwright CDP
|
||||
sessions for safe tab-list reads without replaying mutating browser actions.
|
||||
Fixes #67728.
|
||||
- Gateway/Linux: retry `systemctl --user enable` after a second daemon reload when the freshly written gateway unit is not visible yet on migrated systemd installs. Fixes #65184. Thanks @liushuaiiu.
|
||||
- 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.
|
||||
|
||||
@@ -816,6 +816,52 @@ describe("systemd service install and uninstall", () => {
|
||||
});
|
||||
});
|
||||
|
||||
it("retries enable after reloading again when systemd cannot see the written unit yet", async () => {
|
||||
await withNodeSystemdFixture(async ({ env }) => {
|
||||
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(
|
||||
createExecFileError("enable failed"),
|
||||
"",
|
||||
"Unit file openclaw-node.service does not exist.",
|
||||
);
|
||||
})
|
||||
.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",
|
||||
},
|
||||
});
|
||||
|
||||
expect(execFileMock).toHaveBeenCalledTimes(6);
|
||||
});
|
||||
});
|
||||
|
||||
it("disables the OPENCLAW_SYSTEMD_UNIT override during uninstall", async () => {
|
||||
await withNodeSystemdFixture(async ({ env, unitPath }) => {
|
||||
await fs.mkdir(path.dirname(unitPath), { recursive: true });
|
||||
|
||||
@@ -294,6 +294,18 @@ function isSystemdUnitNotEnabled(detail: string): boolean {
|
||||
);
|
||||
}
|
||||
|
||||
function isSystemdUnitMissingDetail(detail: string): boolean {
|
||||
if (!detail) {
|
||||
return false;
|
||||
}
|
||||
const normalized = normalizeLowercaseStringOrEmpty(detail);
|
||||
return (
|
||||
(normalized.includes("unit file") && normalized.includes("does not exist")) ||
|
||||
normalized.includes("not-found") ||
|
||||
normalized.includes("could not be found")
|
||||
);
|
||||
}
|
||||
|
||||
const isSystemctlBusUnavailable = isSystemdUserBusUnavailableDetail;
|
||||
|
||||
function isSystemdUserScopeUnavailable(detail: string): boolean {
|
||||
@@ -541,17 +553,32 @@ export async function stageSystemdService({
|
||||
async function activateSystemdService(params: { env: GatewayServiceEnv }) {
|
||||
const serviceName = resolveSystemdServiceName(params.env);
|
||||
const unitName = `${serviceName}.service`;
|
||||
const reload = await execSystemctlUser(params.env, ["daemon-reload"]);
|
||||
const reloadSystemd = async () => await execSystemctlUser(params.env, ["daemon-reload"]);
|
||||
const reload = await reloadSystemd();
|
||||
if (reload.code !== 0) {
|
||||
throw new Error(`systemctl daemon-reload failed: ${reload.stderr || reload.stdout}`.trim());
|
||||
}
|
||||
|
||||
const enable = await execSystemctlUser(params.env, ["enable", unitName]);
|
||||
const runAfterReloadRetry = async (action: "enable" | "restart") => {
|
||||
const result = await execSystemctlUser(params.env, [action, unitName]);
|
||||
if (result.code === 0 || !isSystemdUnitMissingDetail(readSystemctlDetail(result))) {
|
||||
return result;
|
||||
}
|
||||
const retryReload = await reloadSystemd();
|
||||
if (retryReload.code !== 0) {
|
||||
throw new Error(
|
||||
`systemctl daemon-reload failed: ${retryReload.stderr || retryReload.stdout}`.trim(),
|
||||
);
|
||||
}
|
||||
return await execSystemctlUser(params.env, [action, unitName]);
|
||||
};
|
||||
|
||||
const enable = await runAfterReloadRetry("enable");
|
||||
if (enable.code !== 0) {
|
||||
throw new Error(`systemctl enable failed: ${enable.stderr || enable.stdout}`.trim());
|
||||
}
|
||||
|
||||
const restart = await execSystemctlUser(params.env, ["restart", unitName]);
|
||||
const restart = await runAfterReloadRetry("restart");
|
||||
if (restart.code !== 0) {
|
||||
throw new Error(`systemctl restart failed: ${restart.stderr || restart.stdout}`.trim());
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user