doctor: clean up legacy Linux gateway services (#21188)

* Doctor: clean up legacy Linux gateway services

* doctor: refactor legacy service cleanup flow

* doctor: fix legacy systemd cleanup map key typing

* doctor: add changelog entry for legacy Linux service cleanup

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
This commit is contained in:
Phineas1500
2026-02-22 19:18:59 -05:00
committed by GitHub
parent 1c2c7843a8
commit 8a8faf066e
5 changed files with 190 additions and 25 deletions

View File

@@ -5,7 +5,11 @@ import path from "node:path";
import { promisify } from "node:util";
import type { OpenClawConfig } from "../config/config.js";
import { resolveGatewayPort, resolveIsNixMode } from "../config/paths.js";
import { findExtraGatewayServices, renderGatewayServiceCleanupHints } from "../daemon/inspect.js";
import {
findExtraGatewayServices,
renderGatewayServiceCleanupHints,
type ExtraGatewayService,
} from "../daemon/inspect.js";
import { renderSystemNodeWarning, resolveSystemNodeInfo } from "../daemon/runtime-paths.js";
import {
auditGatewayServiceConfig,
@@ -13,6 +17,7 @@ import {
SERVICE_AUDIT_CODES,
} from "../daemon/service-audit.js";
import { resolveGatewayService } from "../daemon/service.js";
import { uninstallLegacySystemdUnits } from "../daemon/systemd.js";
import type { RuntimeEnv } from "../runtime.js";
import { note } from "../terminal/note.js";
import { buildGatewayInstallPlan } from "./daemon-install-helpers.js";
@@ -98,6 +103,95 @@ async function cleanupLegacyLaunchdService(params: {
}
}
function classifyLegacyServices(legacyServices: ExtraGatewayService[]): {
darwinUserServices: ExtraGatewayService[];
linuxUserServices: ExtraGatewayService[];
failed: string[];
} {
const darwinUserServices: ExtraGatewayService[] = [];
const linuxUserServices: ExtraGatewayService[] = [];
const failed: string[] = [];
for (const svc of legacyServices) {
if (svc.platform === "darwin") {
if (svc.scope === "user") {
darwinUserServices.push(svc);
} else {
failed.push(`${svc.label} (${svc.scope})`);
}
continue;
}
if (svc.platform === "linux") {
if (svc.scope === "user") {
linuxUserServices.push(svc);
} else {
failed.push(`${svc.label} (${svc.scope})`);
}
continue;
}
failed.push(`${svc.label} (${svc.platform})`);
}
return { darwinUserServices, linuxUserServices, failed };
}
async function cleanupLegacyDarwinServices(
services: ExtraGatewayService[],
): Promise<{ removed: string[]; failed: string[] }> {
const removed: string[] = [];
const failed: string[] = [];
for (const svc of services) {
const plistPath = extractDetailPath(svc.detail, "plist:");
if (!plistPath) {
failed.push(`${svc.label} (missing plist path)`);
continue;
}
const dest = await cleanupLegacyLaunchdService({
label: svc.label,
plistPath,
});
removed.push(dest ? `${svc.label} -> ${dest}` : svc.label);
}
return { removed, failed };
}
async function cleanupLegacyLinuxUserServices(
services: ExtraGatewayService[],
runtime: RuntimeEnv,
): Promise<{ removed: string[]; failed: string[] }> {
const removed: string[] = [];
const failed: string[] = [];
try {
const removedUnits = await uninstallLegacySystemdUnits({
env: process.env,
stdout: process.stdout,
});
const removedByLabel: Map<string, (typeof removedUnits)[number]> = new Map(
removedUnits.map((unit) => [`${unit.name}.service`, unit] as const),
);
for (const svc of services) {
const removedUnit = removedByLabel.get(svc.label);
if (!removedUnit) {
failed.push(`${svc.label} (legacy unit name not recognized)`);
continue;
}
removed.push(`${svc.label} -> ${removedUnit.unitPath}`);
}
} catch (err) {
runtime.error(`Legacy Linux gateway cleanup failed: ${String(err)}`);
for (const svc of services) {
failed.push(`${svc.label} (linux cleanup failed)`);
}
}
return { removed, failed };
}
export async function maybeRepairGatewayServiceConfig(
cfg: OpenClawConfig,
mode: "local" | "remote",
@@ -246,27 +340,21 @@ export async function maybeScanExtraGatewayServices(
});
if (shouldRemove) {
const removed: string[] = [];
const failed: string[] = [];
for (const svc of legacyServices) {
if (svc.platform !== "darwin") {
failed.push(`${svc.label} (${svc.platform})`);
continue;
}
if (svc.scope !== "user") {
failed.push(`${svc.label} (${svc.scope})`);
continue;
}
const plistPath = extractDetailPath(svc.detail, "plist:");
if (!plistPath) {
failed.push(`${svc.label} (missing plist path)`);
continue;
}
const dest = await cleanupLegacyLaunchdService({
label: svc.label,
plistPath,
});
removed.push(dest ? `${svc.label} -> ${dest}` : svc.label);
const { darwinUserServices, linuxUserServices, failed } =
classifyLegacyServices(legacyServices);
if (darwinUserServices.length > 0) {
const result = await cleanupLegacyDarwinServices(darwinUserServices);
removed.push(...result.removed);
failed.push(...result.failed);
}
if (linuxUserServices.length > 0) {
const result = await cleanupLegacyLinuxUserServices(linuxUserServices, runtime);
removed.push(...result.removed);
failed.push(...result.failed);
}
if (removed.length > 0) {
note(removed.map((line) => `- ${line}`).join("\n"), "Legacy gateway removed");
}