mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-08 22:48:27 +00:00
fix: migrate legacy gateway services
This commit is contained in:
@@ -64,10 +64,11 @@ export function formatCliBannerLine(version: string, options: BannerOptions = {}
|
||||
|
||||
const LOBSTER_ASCII = [
|
||||
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
|
||||
"█████░█████░█████░█░░░█░█████░█░░░░░░███░░█░░░█",
|
||||
"█░░░█░█░░░█░███░░░██░░█░█░░░░░█░░░░░█░░░█░█░█░█",
|
||||
"█████░████░░█████░█░░██░█████░█████░█████░██░██",
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
||||
"█████░█████░█████░█░░░█░█████░█░░░░░█████░█░░░█",
|
||||
"█░░░█░█░░░█░█░░░░░██░░█░█░░░░░█░░░░░█░░░█░█░░░█",
|
||||
"█░░░█░█████░████░░█░█░█░█░░░░░█░░░░░█████░█░█░█",
|
||||
"█░░░█░█░░░░░█░░░░░█░░██░█░░░░░█░░░░░█░░░█░██░██",
|
||||
"█████░█░░░░░█████░█░░░█░█████░█████░█░░░█░█░░░█",
|
||||
" 🦞 OPENCLAW 🦞 ",
|
||||
" ",
|
||||
];
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
import { execFile } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { promisify } from "node:util";
|
||||
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { resolveGatewayPort, resolveIsNixMode } from "../config/paths.js";
|
||||
@@ -16,6 +20,8 @@ import { buildGatewayInstallPlan } from "./daemon-install-helpers.js";
|
||||
import { DEFAULT_GATEWAY_DAEMON_RUNTIME, type GatewayDaemonRuntime } from "./daemon-runtime.js";
|
||||
import type { DoctorOptions, DoctorPrompter } from "./doctor-prompter.js";
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
function detectGatewayRuntime(programArguments: string[] | undefined): GatewayDaemonRuntime {
|
||||
const first = programArguments?.[0];
|
||||
if (first) {
|
||||
@@ -37,6 +43,42 @@ function normalizeExecutablePath(value: string): string {
|
||||
return path.resolve(value);
|
||||
}
|
||||
|
||||
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 cleanupLegacyLaunchdService(params: {
|
||||
label: string;
|
||||
plistPath: string;
|
||||
}): Promise<string | null> {
|
||||
const domain = typeof process.getuid === "function" ? `gui/${process.getuid()}` : "gui/501";
|
||||
await execFileAsync("launchctl", ["bootout", domain, params.plistPath]).catch(() => undefined);
|
||||
await execFileAsync("launchctl", ["unload", params.plistPath]).catch(() => undefined);
|
||||
|
||||
const trashDir = path.join(os.homedir(), ".Trash");
|
||||
try {
|
||||
await fs.mkdir(trashDir, { recursive: true });
|
||||
} catch {
|
||||
// ignore
|
||||
}
|
||||
|
||||
try {
|
||||
await fs.access(params.plistPath);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
|
||||
const dest = path.join(trashDir, `${params.label}-${Date.now()}.plist`);
|
||||
try {
|
||||
await fs.rename(params.plistPath, dest);
|
||||
return dest;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export async function maybeRepairGatewayServiceConfig(
|
||||
cfg: OpenClawConfig,
|
||||
mode: "local" | "remote",
|
||||
@@ -150,7 +192,11 @@ export async function maybeRepairGatewayServiceConfig(
|
||||
}
|
||||
}
|
||||
|
||||
export async function maybeScanExtraGatewayServices(options: DoctorOptions) {
|
||||
export async function maybeScanExtraGatewayServices(
|
||||
options: DoctorOptions,
|
||||
runtime: RuntimeEnv,
|
||||
prompter: DoctorPrompter,
|
||||
) {
|
||||
const extraServices = await findExtraGatewayServices(process.env, {
|
||||
deep: options.deep,
|
||||
});
|
||||
@@ -161,6 +207,47 @@ export async function maybeScanExtraGatewayServices(options: DoctorOptions) {
|
||||
"Other gateway-like services detected",
|
||||
);
|
||||
|
||||
const legacyServices = extraServices.filter((svc) => svc.legacy === true);
|
||||
if (legacyServices.length > 0) {
|
||||
const shouldRemove = await prompter.confirmSkipInNonInteractive({
|
||||
message: "Remove legacy gateway services (clawdbot/moltbot) now?",
|
||||
initialValue: true,
|
||||
});
|
||||
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);
|
||||
}
|
||||
if (removed.length > 0) {
|
||||
note(removed.map((line) => `- ${line}`).join("\n"), "Legacy gateway removed");
|
||||
}
|
||||
if (failed.length > 0) {
|
||||
note(failed.map((line) => `- ${line}`).join("\n"), "Legacy gateway cleanup skipped");
|
||||
}
|
||||
if (removed.length > 0) {
|
||||
runtime.log("Legacy gateway services removed. Installing OpenClaw gateway next.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const cleanupHints = renderGatewayServiceCleanupHints();
|
||||
if (cleanupHints.length > 0) {
|
||||
note(cleanupHints.map((hint) => `- ${hint}`).join("\n"), "Cleanup hints");
|
||||
|
||||
@@ -185,7 +185,7 @@ export async function doctorCommand(
|
||||
cfg = await maybeRepairSandboxImages(cfg, runtime, prompter);
|
||||
noteSandboxScopeWarnings(cfg);
|
||||
|
||||
await maybeScanExtraGatewayServices(options);
|
||||
await maybeScanExtraGatewayServices(options, runtime, prompter);
|
||||
await maybeRepairGatewayServiceConfig(cfg, resolveMode(cfg), runtime, prompter);
|
||||
await noteMacLaunchAgentOverrides();
|
||||
await noteMacLaunchctlGatewayEnvOverrides(cfg);
|
||||
|
||||
@@ -65,9 +65,11 @@ export function randomToken(): string {
|
||||
export function printWizardHeader(runtime: RuntimeEnv) {
|
||||
const header = [
|
||||
"▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄▄",
|
||||
"█████░█████░█████░█░░░█░█████░█░░░░░░███░░█░░░█",
|
||||
"█░░░█░█░░░█░███░░░██░░█░█░░░░░█░░░░░█░░░█░█░█░█",
|
||||
"█████░████░░█████░█░░██░█████░█████░█████░██░██",
|
||||
"█████░█████░█████░█░░░█░█████░█░░░░░█████░█░░░█",
|
||||
"█░░░█░█░░░█░█░░░░░██░░█░█░░░░░█░░░░░█░░░█░█░░░█",
|
||||
"█░░░█░█████░████░░█░█░█░█░░░░░█░░░░░█████░█░█░█",
|
||||
"█░░░█░█░░░░░█░░░░░█░░██░█░░░░░█░░░░░█░░░█░██░██",
|
||||
"█████░█░░░░░█████░█░░░█░█████░█████░█░░░█░█░░░█",
|
||||
"▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀",
|
||||
" 🦞 FRESH DAILY 🦞 ",
|
||||
" ",
|
||||
|
||||
@@ -16,13 +16,15 @@ export type ExtraGatewayService = {
|
||||
label: string;
|
||||
detail: string;
|
||||
scope: "user" | "system";
|
||||
marker?: "openclaw" | "clawdbot" | "moltbot";
|
||||
legacy?: boolean;
|
||||
};
|
||||
|
||||
export type FindExtraGatewayServicesOptions = {
|
||||
deep?: boolean;
|
||||
};
|
||||
|
||||
const EXTRA_MARKERS = ["openclaw"];
|
||||
const EXTRA_MARKERS = ["openclaw", "clawdbot", "moltbot"] as const;
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
export function renderGatewayServiceCleanupHints(
|
||||
@@ -56,9 +58,14 @@ function resolveHomeDir(env: Record<string, string | undefined>): string {
|
||||
return home;
|
||||
}
|
||||
|
||||
function containsMarker(content: string): boolean {
|
||||
type Marker = (typeof EXTRA_MARKERS)[number];
|
||||
|
||||
function detectMarker(content: string): Marker | null {
|
||||
const lower = content.toLowerCase();
|
||||
return EXTRA_MARKERS.some((marker) => lower.includes(marker));
|
||||
for (const marker of EXTRA_MARKERS) {
|
||||
if (lower.includes(marker)) return marker;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
function hasGatewayServiceMarker(content: string): boolean {
|
||||
@@ -111,6 +118,11 @@ function isIgnoredSystemdName(name: string): boolean {
|
||||
return name === resolveGatewaySystemdServiceName();
|
||||
}
|
||||
|
||||
function isLegacyLabel(label: string): boolean {
|
||||
const lower = label.toLowerCase();
|
||||
return lower.includes("clawdbot") || lower.includes("moltbot");
|
||||
}
|
||||
|
||||
async function scanLaunchdDir(params: {
|
||||
dir: string;
|
||||
scope: "user" | "system";
|
||||
@@ -134,15 +146,18 @@ async function scanLaunchdDir(params: {
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (!containsMarker(contents)) continue;
|
||||
const marker = detectMarker(contents);
|
||||
if (!marker) continue;
|
||||
const label = tryExtractPlistLabel(contents) ?? labelFromName;
|
||||
if (isIgnoredLaunchdLabel(label)) continue;
|
||||
if (isOpenClawGatewayLaunchdService(label, contents)) continue;
|
||||
if (marker === "openclaw" && isOpenClawGatewayLaunchdService(label, contents)) continue;
|
||||
results.push({
|
||||
platform: "darwin",
|
||||
label,
|
||||
detail: `plist: ${fullPath}`,
|
||||
scope: params.scope,
|
||||
marker,
|
||||
legacy: marker !== "openclaw" || isLegacyLabel(label),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -172,13 +187,16 @@ async function scanSystemdDir(params: {
|
||||
} catch {
|
||||
continue;
|
||||
}
|
||||
if (!containsMarker(contents)) continue;
|
||||
if (isOpenClawGatewaySystemdService(name, contents)) continue;
|
||||
const marker = detectMarker(contents);
|
||||
if (!marker) continue;
|
||||
if (marker === "openclaw" && isOpenClawGatewaySystemdService(name, contents)) continue;
|
||||
results.push({
|
||||
platform: "linux",
|
||||
label: entry,
|
||||
detail: `unit: ${fullPath}`,
|
||||
scope: params.scope,
|
||||
marker,
|
||||
legacy: marker !== "openclaw",
|
||||
});
|
||||
}
|
||||
|
||||
@@ -336,15 +354,21 @@ export async function findExtraGatewayServices(
|
||||
if (isOpenClawGatewayTaskName(name)) continue;
|
||||
const lowerName = name.toLowerCase();
|
||||
const lowerCommand = task.taskToRun?.toLowerCase() ?? "";
|
||||
const matches = EXTRA_MARKERS.some(
|
||||
(marker) => lowerName.includes(marker) || lowerCommand.includes(marker),
|
||||
);
|
||||
if (!matches) continue;
|
||||
let marker: Marker | null = null;
|
||||
for (const candidate of EXTRA_MARKERS) {
|
||||
if (lowerName.includes(candidate) || lowerCommand.includes(candidate)) {
|
||||
marker = candidate;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!marker) continue;
|
||||
push({
|
||||
platform: "win32",
|
||||
label: name,
|
||||
detail: task.taskToRun ? `task: ${name}, run: ${task.taskToRun}` : name,
|
||||
scope: "system",
|
||||
marker,
|
||||
legacy: marker !== "openclaw",
|
||||
});
|
||||
}
|
||||
return results;
|
||||
|
||||
Reference in New Issue
Block a user