mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-03 03:03:24 -04:00
chore: Enable "curly" rule to avoid single-statement if confusion/errors.
This commit is contained in:
@@ -16,7 +16,9 @@ export const LEGACY_GATEWAY_WINDOWS_TASK_NAMES: string[] = [];
|
||||
|
||||
export function normalizeGatewayProfile(profile?: string): string | null {
|
||||
const trimmed = profile?.trim();
|
||||
if (!trimmed || trimmed.toLowerCase() === "default") return null;
|
||||
if (!trimmed || trimmed.toLowerCase() === "default") {
|
||||
return null;
|
||||
}
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
@@ -40,13 +42,17 @@ export function resolveLegacyGatewayLaunchAgentLabels(profile?: string): string[
|
||||
|
||||
export function resolveGatewaySystemdServiceName(profile?: string): string {
|
||||
const suffix = resolveGatewayProfileSuffix(profile);
|
||||
if (!suffix) return GATEWAY_SYSTEMD_SERVICE_NAME;
|
||||
if (!suffix) {
|
||||
return GATEWAY_SYSTEMD_SERVICE_NAME;
|
||||
}
|
||||
return `openclaw-gateway${suffix}`;
|
||||
}
|
||||
|
||||
export function resolveGatewayWindowsTaskName(profile?: string): string {
|
||||
const normalized = normalizeGatewayProfile(profile);
|
||||
if (!normalized) return GATEWAY_WINDOWS_TASK_NAME;
|
||||
if (!normalized) {
|
||||
return GATEWAY_WINDOWS_TASK_NAME;
|
||||
}
|
||||
return `OpenClaw Gateway (${normalized})`;
|
||||
}
|
||||
|
||||
@@ -57,9 +63,15 @@ export function formatGatewayServiceDescription(params?: {
|
||||
const profile = normalizeGatewayProfile(params?.profile);
|
||||
const version = params?.version?.trim();
|
||||
const parts: string[] = [];
|
||||
if (profile) parts.push(`profile: ${profile}`);
|
||||
if (version) parts.push(`v${version}`);
|
||||
if (parts.length === 0) return "OpenClaw Gateway";
|
||||
if (profile) {
|
||||
parts.push(`profile: ${profile}`);
|
||||
}
|
||||
if (version) {
|
||||
parts.push(`v${version}`);
|
||||
}
|
||||
if (parts.length === 0) {
|
||||
return "OpenClaw Gateway";
|
||||
}
|
||||
return `OpenClaw Gateway (${parts.join(", ")})`;
|
||||
}
|
||||
|
||||
@@ -77,6 +89,8 @@ export function resolveNodeWindowsTaskName(): string {
|
||||
|
||||
export function formatNodeServiceDescription(params?: { version?: string }): string {
|
||||
const version = params?.version?.trim();
|
||||
if (!version) return "OpenClaw Node Host";
|
||||
if (!version) {
|
||||
return "OpenClaw Node Host";
|
||||
}
|
||||
return `OpenClaw Node Host (v${version})`;
|
||||
}
|
||||
|
||||
@@ -15,7 +15,9 @@ async function readLastLogLine(filePath: string): Promise<string | null> {
|
||||
const raw = await fs.readFile(filePath, "utf8");
|
||||
const lines = raw.split(/\r?\n/).map((line) => line.trim());
|
||||
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
||||
if (lines[i]) return lines[i];
|
||||
if (lines[i]) {
|
||||
return lines[i];
|
||||
}
|
||||
}
|
||||
return null;
|
||||
} catch {
|
||||
@@ -32,7 +34,9 @@ export async function readLastGatewayErrorLine(env: NodeJS.ProcessEnv): Promise<
|
||||
);
|
||||
for (let i = lines.length - 1; i >= 0; i -= 1) {
|
||||
const line = lines[i];
|
||||
if (!line) continue;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
if (GATEWAY_LOG_ERROR_PATTERNS.some((pattern) => pattern.test(line))) {
|
||||
return line;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,9 @@ export function renderGatewayServiceCleanupHints(
|
||||
|
||||
function resolveHomeDir(env: Record<string, string | undefined>): string {
|
||||
const home = env.HOME?.trim() || env.USERPROFILE?.trim();
|
||||
if (!home) throw new Error("Missing HOME");
|
||||
if (!home) {
|
||||
throw new Error("Missing HOME");
|
||||
}
|
||||
return home;
|
||||
}
|
||||
|
||||
@@ -63,7 +65,9 @@ type Marker = (typeof EXTRA_MARKERS)[number];
|
||||
function detectMarker(content: string): Marker | null {
|
||||
const lower = content.toLowerCase();
|
||||
for (const marker of EXTRA_MARKERS) {
|
||||
if (lower.includes(marker)) return marker;
|
||||
if (lower.includes(marker)) {
|
||||
return marker;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
@@ -85,28 +89,40 @@ function hasGatewayServiceMarker(content: string): boolean {
|
||||
}
|
||||
|
||||
function isOpenClawGatewayLaunchdService(label: string, contents: string): boolean {
|
||||
if (hasGatewayServiceMarker(contents)) return true;
|
||||
if (hasGatewayServiceMarker(contents)) {
|
||||
return true;
|
||||
}
|
||||
const lowerContents = contents.toLowerCase();
|
||||
if (!lowerContents.includes("gateway")) return false;
|
||||
if (!lowerContents.includes("gateway")) {
|
||||
return false;
|
||||
}
|
||||
return label.startsWith("ai.openclaw.");
|
||||
}
|
||||
|
||||
function isOpenClawGatewaySystemdService(name: string, contents: string): boolean {
|
||||
if (hasGatewayServiceMarker(contents)) return true;
|
||||
if (!name.startsWith("openclaw-gateway")) return false;
|
||||
if (hasGatewayServiceMarker(contents)) {
|
||||
return true;
|
||||
}
|
||||
if (!name.startsWith("openclaw-gateway")) {
|
||||
return false;
|
||||
}
|
||||
return contents.toLowerCase().includes("gateway");
|
||||
}
|
||||
|
||||
function isOpenClawGatewayTaskName(name: string): boolean {
|
||||
const normalized = name.trim().toLowerCase();
|
||||
if (!normalized) return false;
|
||||
if (!normalized) {
|
||||
return false;
|
||||
}
|
||||
const defaultName = resolveGatewayWindowsTaskName().toLowerCase();
|
||||
return normalized === defaultName || normalized.startsWith("openclaw gateway");
|
||||
}
|
||||
|
||||
function tryExtractPlistLabel(contents: string): string | null {
|
||||
const match = contents.match(/<key>Label<\/key>\s*<string>([\s\S]*?)<\/string>/i);
|
||||
if (!match) return null;
|
||||
if (!match) {
|
||||
return null;
|
||||
}
|
||||
return match[1]?.trim() || null;
|
||||
}
|
||||
|
||||
@@ -136,9 +152,13 @@ async function scanLaunchdDir(params: {
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.endsWith(".plist")) continue;
|
||||
if (!entry.endsWith(".plist")) {
|
||||
continue;
|
||||
}
|
||||
const labelFromName = entry.replace(/\.plist$/, "");
|
||||
if (isIgnoredLaunchdLabel(labelFromName)) continue;
|
||||
if (isIgnoredLaunchdLabel(labelFromName)) {
|
||||
continue;
|
||||
}
|
||||
const fullPath = path.join(params.dir, entry);
|
||||
let contents = "";
|
||||
try {
|
||||
@@ -150,7 +170,9 @@ async function scanLaunchdDir(params: {
|
||||
const label = tryExtractPlistLabel(contents) ?? labelFromName;
|
||||
if (!marker) {
|
||||
const legacyLabel = isLegacyLabel(labelFromName) || isLegacyLabel(label);
|
||||
if (!legacyLabel) continue;
|
||||
if (!legacyLabel) {
|
||||
continue;
|
||||
}
|
||||
results.push({
|
||||
platform: "darwin",
|
||||
label,
|
||||
@@ -161,8 +183,12 @@ async function scanLaunchdDir(params: {
|
||||
});
|
||||
continue;
|
||||
}
|
||||
if (isIgnoredLaunchdLabel(label)) continue;
|
||||
if (marker === "openclaw" && isOpenClawGatewayLaunchdService(label, contents)) continue;
|
||||
if (isIgnoredLaunchdLabel(label)) {
|
||||
continue;
|
||||
}
|
||||
if (marker === "openclaw" && isOpenClawGatewayLaunchdService(label, contents)) {
|
||||
continue;
|
||||
}
|
||||
results.push({
|
||||
platform: "darwin",
|
||||
label,
|
||||
@@ -189,9 +215,13 @@ async function scanSystemdDir(params: {
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.endsWith(".service")) continue;
|
||||
if (!entry.endsWith(".service")) {
|
||||
continue;
|
||||
}
|
||||
const name = entry.replace(/\.service$/, "");
|
||||
if (isIgnoredSystemdName(name)) continue;
|
||||
if (isIgnoredSystemdName(name)) {
|
||||
continue;
|
||||
}
|
||||
const fullPath = path.join(params.dir, entry);
|
||||
let contents = "";
|
||||
try {
|
||||
@@ -200,8 +230,12 @@ async function scanSystemdDir(params: {
|
||||
continue;
|
||||
}
|
||||
const marker = detectMarker(contents);
|
||||
if (!marker) continue;
|
||||
if (marker === "openclaw" && isOpenClawGatewaySystemdService(name, contents)) continue;
|
||||
if (!marker) {
|
||||
continue;
|
||||
}
|
||||
if (marker === "openclaw" && isOpenClawGatewaySystemdService(name, contents)) {
|
||||
continue;
|
||||
}
|
||||
results.push({
|
||||
platform: "linux",
|
||||
label: entry,
|
||||
@@ -234,22 +268,32 @@ function parseSchtasksList(output: string): ScheduledTaskInfo[] {
|
||||
continue;
|
||||
}
|
||||
const idx = line.indexOf(":");
|
||||
if (idx <= 0) continue;
|
||||
if (idx <= 0) {
|
||||
continue;
|
||||
}
|
||||
const key = line.slice(0, idx).trim().toLowerCase();
|
||||
const value = line.slice(idx + 1).trim();
|
||||
if (!value) continue;
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
if (key === "taskname") {
|
||||
if (current) tasks.push(current);
|
||||
if (current) {
|
||||
tasks.push(current);
|
||||
}
|
||||
current = { name: value };
|
||||
continue;
|
||||
}
|
||||
if (!current) continue;
|
||||
if (!current) {
|
||||
continue;
|
||||
}
|
||||
if (key === "task to run") {
|
||||
current.taskToRun = value;
|
||||
}
|
||||
}
|
||||
|
||||
if (current) tasks.push(current);
|
||||
if (current) {
|
||||
tasks.push(current);
|
||||
}
|
||||
return tasks;
|
||||
}
|
||||
|
||||
@@ -290,7 +334,9 @@ export async function findExtraGatewayServices(
|
||||
const seen = new Set<string>();
|
||||
const push = (svc: ExtraGatewayService) => {
|
||||
const key = `${svc.platform}:${svc.label}:${svc.detail}:${svc.scope}`;
|
||||
if (seen.has(key)) return;
|
||||
if (seen.has(key)) {
|
||||
return;
|
||||
}
|
||||
seen.add(key);
|
||||
results.push(svc);
|
||||
};
|
||||
@@ -356,14 +402,22 @@ export async function findExtraGatewayServices(
|
||||
}
|
||||
|
||||
if (process.platform === "win32") {
|
||||
if (!opts.deep) return results;
|
||||
if (!opts.deep) {
|
||||
return results;
|
||||
}
|
||||
const res = await execSchtasks(["/Query", "/FO", "LIST", "/V"]);
|
||||
if (res.code !== 0) return results;
|
||||
if (res.code !== 0) {
|
||||
return results;
|
||||
}
|
||||
const tasks = parseSchtasksList(res.stdout);
|
||||
for (const task of tasks) {
|
||||
const name = task.name.trim();
|
||||
if (!name) continue;
|
||||
if (isOpenClawGatewayTaskName(name)) continue;
|
||||
if (!name) {
|
||||
continue;
|
||||
}
|
||||
if (isOpenClawGatewayTaskName(name)) {
|
||||
continue;
|
||||
}
|
||||
const lowerName = name.toLowerCase();
|
||||
const lowerCommand = task.taskToRun?.toLowerCase() ?? "";
|
||||
let marker: Marker | null = null;
|
||||
@@ -373,7 +427,9 @@ export async function findExtraGatewayServices(
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!marker) continue;
|
||||
if (!marker) {
|
||||
continue;
|
||||
}
|
||||
push({
|
||||
platform: "win32",
|
||||
label: name,
|
||||
|
||||
@@ -17,11 +17,15 @@ const plistUnescape = (value: string): string =>
|
||||
.replaceAll("&", "&");
|
||||
|
||||
const renderEnvDict = (env: Record<string, string | undefined> | undefined): string => {
|
||||
if (!env) return "";
|
||||
if (!env) {
|
||||
return "";
|
||||
}
|
||||
const entries = Object.entries(env).filter(
|
||||
([, value]) => typeof value === "string" && value.trim(),
|
||||
);
|
||||
if (entries.length === 0) return "";
|
||||
if (entries.length === 0) {
|
||||
return "";
|
||||
}
|
||||
const items = entries
|
||||
.map(
|
||||
([key, value]) =>
|
||||
@@ -40,7 +44,9 @@ export async function readLaunchAgentProgramArgumentsFromFile(plistPath: string)
|
||||
try {
|
||||
const plist = await fs.readFile(plistPath, "utf8");
|
||||
const programMatch = plist.match(/<key>ProgramArguments<\/key>\s*<array>([\s\S]*?)<\/array>/i);
|
||||
if (!programMatch) return null;
|
||||
if (!programMatch) {
|
||||
return null;
|
||||
}
|
||||
const args = Array.from(programMatch[1].matchAll(/<string>([\s\S]*?)<\/string>/gi)).map(
|
||||
(match) => plistUnescape(match[1] ?? "").trim(),
|
||||
);
|
||||
@@ -55,7 +61,9 @@ export async function readLaunchAgentProgramArgumentsFromFile(plistPath: string)
|
||||
/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/gi,
|
||||
)) {
|
||||
const key = plistUnescape(pair[1] ?? "").trim();
|
||||
if (!key) continue;
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
const value = plistUnescape(pair[2] ?? "").trim();
|
||||
environment[key] = value;
|
||||
}
|
||||
|
||||
@@ -28,7 +28,9 @@ const formatLine = (label: string, value: string) => {
|
||||
|
||||
function resolveLaunchAgentLabel(args?: { env?: Record<string, string | undefined> }): string {
|
||||
const envLabel = args?.env?.OPENCLAW_LAUNCHD_LABEL?.trim();
|
||||
if (envLabel) return envLabel;
|
||||
if (envLabel) {
|
||||
return envLabel;
|
||||
}
|
||||
return resolveGatewayLaunchAgentLabel(args?.env?.OPENCLAW_PROFILE);
|
||||
}
|
||||
|
||||
@@ -130,7 +132,9 @@ async function execLaunchctl(
|
||||
}
|
||||
|
||||
function resolveGuiDomain(): string {
|
||||
if (typeof process.getuid !== "function") return "gui/501";
|
||||
if (typeof process.getuid !== "function") {
|
||||
return "gui/501";
|
||||
}
|
||||
return `gui/${process.getuid()}`;
|
||||
}
|
||||
|
||||
@@ -145,19 +149,27 @@ export function parseLaunchctlPrint(output: string): LaunchctlPrintInfo {
|
||||
const entries = parseKeyValueOutput(output, "=");
|
||||
const info: LaunchctlPrintInfo = {};
|
||||
const state = entries.state;
|
||||
if (state) info.state = state;
|
||||
if (state) {
|
||||
info.state = state;
|
||||
}
|
||||
const pidValue = entries.pid;
|
||||
if (pidValue) {
|
||||
const pid = Number.parseInt(pidValue, 10);
|
||||
if (Number.isFinite(pid)) info.pid = pid;
|
||||
if (Number.isFinite(pid)) {
|
||||
info.pid = pid;
|
||||
}
|
||||
}
|
||||
const exitStatusValue = entries["last exit status"];
|
||||
if (exitStatusValue) {
|
||||
const status = Number.parseInt(exitStatusValue, 10);
|
||||
if (Number.isFinite(status)) info.lastExitStatus = status;
|
||||
if (Number.isFinite(status)) {
|
||||
info.lastExitStatus = status;
|
||||
}
|
||||
}
|
||||
const exitReason = entries["last exit reason"];
|
||||
if (exitReason) info.lastExitReason = exitReason;
|
||||
if (exitReason) {
|
||||
info.lastExitReason = exitReason;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -175,7 +187,9 @@ export async function isLaunchAgentListed(args: {
|
||||
}): Promise<boolean> {
|
||||
const label = resolveLaunchAgentLabel({ env: args.env });
|
||||
const res = await execLaunchctl(["list"]);
|
||||
if (res.code !== 0) return false;
|
||||
if (res.code !== 0) {
|
||||
return false;
|
||||
}
|
||||
return res.stdout.split(/\r?\n/).some((line) => line.trim().split(/\s+/).at(-1) === label);
|
||||
}
|
||||
|
||||
@@ -275,7 +289,9 @@ export async function uninstallLegacyLaunchAgents({
|
||||
}): Promise<LegacyLaunchAgent[]> {
|
||||
const domain = resolveGuiDomain();
|
||||
const agents = await findLegacyLaunchAgents(env);
|
||||
if (agents.length === 0) return agents;
|
||||
if (agents.length === 0) {
|
||||
return agents;
|
||||
}
|
||||
|
||||
const home = resolveHomeDir(env);
|
||||
const trashDir = path.join(home, ".Trash");
|
||||
|
||||
@@ -7,15 +7,21 @@ const windowsUncPath = /^\\\\/;
|
||||
|
||||
export function resolveHomeDir(env: Record<string, string | undefined>): string {
|
||||
const home = env.HOME?.trim() || env.USERPROFILE?.trim();
|
||||
if (!home) throw new Error("Missing HOME");
|
||||
if (!home) {
|
||||
throw new Error("Missing HOME");
|
||||
}
|
||||
return home;
|
||||
}
|
||||
|
||||
export function resolveUserPathWithHome(input: string, home?: string): string {
|
||||
const trimmed = input.trim();
|
||||
if (!trimmed) return trimmed;
|
||||
if (!trimmed) {
|
||||
return trimmed;
|
||||
}
|
||||
if (trimmed.startsWith("~")) {
|
||||
if (!home) throw new Error("Missing HOME");
|
||||
if (!home) {
|
||||
throw new Error("Missing HOME");
|
||||
}
|
||||
const expanded = trimmed.replace(/^~(?=$|[\\/])/, home);
|
||||
return path.resolve(expanded);
|
||||
}
|
||||
|
||||
@@ -20,7 +20,9 @@ function isBunRuntime(execPath: string): boolean {
|
||||
|
||||
async function resolveCliEntrypointPathForService(): Promise<string> {
|
||||
const argv1 = process.argv[1];
|
||||
if (!argv1) throw new Error("Unable to resolve CLI entrypoint path");
|
||||
if (!argv1) {
|
||||
throw new Error("Unable to resolve CLI entrypoint path");
|
||||
}
|
||||
|
||||
const normalized = path.resolve(argv1);
|
||||
const resolvedPath = await resolveRealpathSafe(normalized);
|
||||
@@ -73,7 +75,9 @@ function buildDistCandidates(...inputs: string[]): string[] {
|
||||
const seen = new Set<string>();
|
||||
|
||||
for (const inputPath of inputs) {
|
||||
if (!inputPath) continue;
|
||||
if (!inputPath) {
|
||||
continue;
|
||||
}
|
||||
const baseDir = path.dirname(inputPath);
|
||||
appendDistCandidates(candidates, seen, path.resolve(baseDir, ".."));
|
||||
appendDistCandidates(candidates, seen, baseDir);
|
||||
@@ -92,7 +96,9 @@ function appendDistCandidates(candidates: string[], seen: Set<string>, baseDir:
|
||||
path.join(distDir, "entry.mjs"),
|
||||
];
|
||||
for (const entry of distEntries) {
|
||||
if (seen.has(entry)) continue;
|
||||
if (seen.has(entry)) {
|
||||
continue;
|
||||
}
|
||||
seen.add(entry);
|
||||
candidates.push(entry);
|
||||
}
|
||||
@@ -105,8 +111,12 @@ function appendNodeModulesBinCandidates(
|
||||
): void {
|
||||
const parts = inputPath.split(path.sep);
|
||||
const binIndex = parts.lastIndexOf(".bin");
|
||||
if (binIndex <= 0) return;
|
||||
if (parts[binIndex - 1] !== "node_modules") return;
|
||||
if (binIndex <= 0) {
|
||||
return;
|
||||
}
|
||||
if (parts[binIndex - 1] !== "node_modules") {
|
||||
return;
|
||||
}
|
||||
const binName = path.basename(inputPath);
|
||||
const nodeModulesDir = parts.slice(0, binIndex).join(path.sep);
|
||||
const packageRoot = path.join(nodeModulesDir, binName);
|
||||
@@ -115,7 +125,9 @@ function appendNodeModulesBinCandidates(
|
||||
|
||||
function resolveRepoRootForDev(): string {
|
||||
const argv1 = process.argv[1];
|
||||
if (!argv1) throw new Error("Unable to resolve repo root");
|
||||
if (!argv1) {
|
||||
throw new Error("Unable to resolve repo root");
|
||||
}
|
||||
const normalized = path.resolve(argv1);
|
||||
const parts = normalized.split(path.sep);
|
||||
const srcIndex = parts.lastIndexOf("src");
|
||||
@@ -141,7 +153,9 @@ async function resolveBinaryPath(binary: string): Promise<string> {
|
||||
try {
|
||||
const output = execSync(`${cmd} ${binary}`, { encoding: "utf8" }).trim();
|
||||
const resolved = output.split(/\r?\n/)[0]?.trim();
|
||||
if (!resolved) throw new Error("empty");
|
||||
if (!resolved) {
|
||||
throw new Error("empty");
|
||||
}
|
||||
await fs.access(resolved);
|
||||
return resolved;
|
||||
} catch {
|
||||
@@ -252,10 +266,18 @@ export async function resolveNodeProgramArguments(params: {
|
||||
nodePath?: string;
|
||||
}): Promise<GatewayProgramArgs> {
|
||||
const args = ["node", "run", "--host", params.host, "--port", String(params.port)];
|
||||
if (params.tls || params.tlsFingerprint) args.push("--tls");
|
||||
if (params.tlsFingerprint) args.push("--tls-fingerprint", params.tlsFingerprint);
|
||||
if (params.nodeId) args.push("--node-id", params.nodeId);
|
||||
if (params.displayName) args.push("--display-name", params.displayName);
|
||||
if (params.tls || params.tlsFingerprint) {
|
||||
args.push("--tls");
|
||||
}
|
||||
if (params.tlsFingerprint) {
|
||||
args.push("--tls-fingerprint", params.tlsFingerprint);
|
||||
}
|
||||
if (params.nodeId) {
|
||||
args.push("--node-id", params.nodeId);
|
||||
}
|
||||
if (params.displayName) {
|
||||
args.push("--display-name", params.displayName);
|
||||
}
|
||||
return resolveCliProgramArguments({
|
||||
args,
|
||||
dev: params.dev,
|
||||
|
||||
@@ -2,11 +2,17 @@ export function parseKeyValueOutput(output: string, separator: string): Record<s
|
||||
const entries: Record<string, string> = {};
|
||||
for (const rawLine of output.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
const idx = line.indexOf(separator);
|
||||
if (idx <= 0) continue;
|
||||
if (idx <= 0) {
|
||||
continue;
|
||||
}
|
||||
const key = line.slice(0, idx).trim().toLowerCase();
|
||||
if (!key) continue;
|
||||
if (!key) {
|
||||
continue;
|
||||
}
|
||||
const value = line.slice(idx + separator.length).trim();
|
||||
entries[key] = value;
|
||||
}
|
||||
|
||||
@@ -24,7 +24,9 @@ describe("resolvePreferredNodePath", () => {
|
||||
|
||||
it("uses system node when it meets the minimum version", async () => {
|
||||
fsMocks.access.mockImplementation(async (target: string) => {
|
||||
if (target === darwinNode) return;
|
||||
if (target === darwinNode) {
|
||||
return;
|
||||
}
|
||||
throw new Error("missing");
|
||||
});
|
||||
|
||||
@@ -43,7 +45,9 @@ describe("resolvePreferredNodePath", () => {
|
||||
|
||||
it("skips system node when it is too old", async () => {
|
||||
fsMocks.access.mockImplementation(async (target: string) => {
|
||||
if (target === darwinNode) return;
|
||||
if (target === darwinNode) {
|
||||
return;
|
||||
}
|
||||
throw new Error("missing");
|
||||
});
|
||||
|
||||
@@ -82,7 +86,9 @@ describe("resolveSystemNodeInfo", () => {
|
||||
|
||||
it("returns supported info when version is new enough", async () => {
|
||||
fsMocks.access.mockImplementation(async (target: string) => {
|
||||
if (target === darwinNode) return;
|
||||
if (target === darwinNode) {
|
||||
return;
|
||||
}
|
||||
throw new Error("missing");
|
||||
});
|
||||
|
||||
|
||||
@@ -124,7 +124,9 @@ export async function resolveSystemNodeInfo(params: {
|
||||
const env = params.env ?? process.env;
|
||||
const platform = params.platform ?? process.platform;
|
||||
const systemNode = await resolveSystemNodePath(env, platform);
|
||||
if (!systemNode) return null;
|
||||
if (!systemNode) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const version = await resolveNodeVersion(systemNode, params.execFile ?? execFileAsync);
|
||||
return {
|
||||
@@ -138,7 +140,9 @@ export function renderSystemNodeWarning(
|
||||
systemNode: SystemNodeInfo | null,
|
||||
selectedNodePath?: string,
|
||||
): string | null {
|
||||
if (!systemNode || systemNode.supported) return null;
|
||||
if (!systemNode || systemNode.supported) {
|
||||
return null;
|
||||
}
|
||||
const versionLabel = systemNode.version ?? "unknown";
|
||||
const selectedLabel = selectedNodePath ? ` Using ${selectedNodePath} for the daemon.` : "";
|
||||
return `System Node ${versionLabel} at ${systemNode.path} is below the required Node 22+.${selectedLabel} Install Node 22+ from nodejs.org or Homebrew.`;
|
||||
@@ -150,8 +154,12 @@ export async function resolvePreferredNodePath(params: {
|
||||
platform?: NodeJS.Platform;
|
||||
execFile?: ExecFileAsync;
|
||||
}): Promise<string | undefined> {
|
||||
if (params.runtime !== "node") return undefined;
|
||||
if (params.runtime !== "node") {
|
||||
return undefined;
|
||||
}
|
||||
const systemNode = await resolveSystemNodeInfo(params);
|
||||
if (!systemNode?.supported) return undefined;
|
||||
if (!systemNode?.supported) {
|
||||
return undefined;
|
||||
}
|
||||
return systemNode.path;
|
||||
}
|
||||
|
||||
@@ -18,29 +18,41 @@ const formatLine = (label: string, value: string) => {
|
||||
|
||||
function resolveTaskName(env: Record<string, string | undefined>): string {
|
||||
const override = env.OPENCLAW_WINDOWS_TASK_NAME?.trim();
|
||||
if (override) return override;
|
||||
if (override) {
|
||||
return override;
|
||||
}
|
||||
return resolveGatewayWindowsTaskName(env.OPENCLAW_PROFILE);
|
||||
}
|
||||
|
||||
export function resolveTaskScriptPath(env: Record<string, string | undefined>): string {
|
||||
const override = env.OPENCLAW_TASK_SCRIPT?.trim();
|
||||
if (override) return override;
|
||||
if (override) {
|
||||
return override;
|
||||
}
|
||||
const scriptName = env.OPENCLAW_TASK_SCRIPT_NAME?.trim() || "gateway.cmd";
|
||||
const stateDir = resolveGatewayStateDir(env);
|
||||
return path.join(stateDir, scriptName);
|
||||
}
|
||||
|
||||
function quoteCmdArg(value: string): string {
|
||||
if (!/[ \t"]/g.test(value)) return value;
|
||||
if (!/[ \t"]/g.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `"${value.replace(/"/g, '\\"')}"`;
|
||||
}
|
||||
|
||||
function resolveTaskUser(env: Record<string, string | undefined>): string | null {
|
||||
const username = env.USERNAME || env.USER || env.LOGNAME;
|
||||
if (!username) return null;
|
||||
if (username.includes("\\")) return username;
|
||||
if (!username) {
|
||||
return null;
|
||||
}
|
||||
if (username.includes("\\")) {
|
||||
return username;
|
||||
}
|
||||
const domain = env.USERDOMAIN;
|
||||
if (domain) return `${domain}\\${username}`;
|
||||
if (domain) {
|
||||
return `${domain}\\${username}`;
|
||||
}
|
||||
return username;
|
||||
}
|
||||
|
||||
@@ -73,7 +85,9 @@ function parseCommandLine(value: string): string[] {
|
||||
}
|
||||
current += char;
|
||||
}
|
||||
if (current) args.push(current);
|
||||
if (current) {
|
||||
args.push(current);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
@@ -90,16 +104,24 @@ export async function readScheduledTaskCommand(env: Record<string, string | unde
|
||||
const environment: Record<string, string> = {};
|
||||
for (const rawLine of content.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
if (line.startsWith("@echo")) continue;
|
||||
if (line.toLowerCase().startsWith("rem ")) continue;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("@echo")) {
|
||||
continue;
|
||||
}
|
||||
if (line.toLowerCase().startsWith("rem ")) {
|
||||
continue;
|
||||
}
|
||||
if (line.toLowerCase().startsWith("set ")) {
|
||||
const assignment = line.slice(4).trim();
|
||||
const index = assignment.indexOf("=");
|
||||
if (index > 0) {
|
||||
const key = assignment.slice(0, index).trim();
|
||||
const value = assignment.slice(index + 1).trim();
|
||||
if (key) environment[key] = value;
|
||||
if (key) {
|
||||
environment[key] = value;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -110,7 +132,9 @@ export async function readScheduledTaskCommand(env: Record<string, string | unde
|
||||
commandLine = line;
|
||||
break;
|
||||
}
|
||||
if (!commandLine) return null;
|
||||
if (!commandLine) {
|
||||
return null;
|
||||
}
|
||||
return {
|
||||
programArguments: parseCommandLine(commandLine),
|
||||
...(workingDirectory ? { workingDirectory } : {}),
|
||||
@@ -131,11 +155,17 @@ export function parseSchtasksQuery(output: string): ScheduledTaskInfo {
|
||||
const entries = parseKeyValueOutput(output, ":");
|
||||
const info: ScheduledTaskInfo = {};
|
||||
const status = entries.status;
|
||||
if (status) info.status = status;
|
||||
if (status) {
|
||||
info.status = status;
|
||||
}
|
||||
const lastRunTime = entries["last run time"];
|
||||
if (lastRunTime) info.lastRunTime = lastRunTime;
|
||||
if (lastRunTime) {
|
||||
info.lastRunTime = lastRunTime;
|
||||
}
|
||||
const lastRunResult = entries["last run result"];
|
||||
if (lastRunResult) info.lastRunResult = lastRunResult;
|
||||
if (lastRunResult) {
|
||||
info.lastRunResult = lastRunResult;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -159,7 +189,9 @@ function buildTaskScript({
|
||||
}
|
||||
if (environment) {
|
||||
for (const [key, value] of Object.entries(environment)) {
|
||||
if (!value) continue;
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
lines.push(`set ${key}=${value}`);
|
||||
}
|
||||
}
|
||||
@@ -199,7 +231,9 @@ async function execSchtasks(
|
||||
|
||||
async function assertSchtasksAvailable() {
|
||||
const res = await execSchtasks(["/Query"]);
|
||||
if (res.code === 0) return;
|
||||
if (res.code === 0) {
|
||||
return;
|
||||
}
|
||||
const detail = res.stderr || res.stdout;
|
||||
throw new Error(`schtasks unavailable: ${detail || "unknown error"}`.trim());
|
||||
}
|
||||
|
||||
@@ -67,21 +67,35 @@ function parseSystemdUnit(content: string): {
|
||||
|
||||
for (const rawLine of content.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) continue;
|
||||
if (line.startsWith("#") || line.startsWith(";")) continue;
|
||||
if (line.startsWith("[")) continue;
|
||||
if (!line) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("#") || line.startsWith(";")) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("[")) {
|
||||
continue;
|
||||
}
|
||||
const idx = line.indexOf("=");
|
||||
if (idx <= 0) continue;
|
||||
if (idx <= 0) {
|
||||
continue;
|
||||
}
|
||||
const key = line.slice(0, idx).trim();
|
||||
const value = line.slice(idx + 1).trim();
|
||||
if (!value) continue;
|
||||
if (!value) {
|
||||
continue;
|
||||
}
|
||||
if (key === "After") {
|
||||
for (const entry of value.split(/\s+/)) {
|
||||
if (entry) after.add(entry);
|
||||
if (entry) {
|
||||
after.add(entry);
|
||||
}
|
||||
}
|
||||
} else if (key === "Wants") {
|
||||
for (const entry of value.split(/\s+/)) {
|
||||
if (entry) wants.add(entry);
|
||||
if (entry) {
|
||||
wants.add(entry);
|
||||
}
|
||||
}
|
||||
} else if (key === "RestartSec") {
|
||||
restartSec = value;
|
||||
@@ -92,9 +106,13 @@ function parseSystemdUnit(content: string): {
|
||||
}
|
||||
|
||||
function isRestartSecPreferred(value: string | undefined): boolean {
|
||||
if (!value) return false;
|
||||
if (!value) {
|
||||
return false;
|
||||
}
|
||||
const parsed = Number.parseFloat(value);
|
||||
if (!Number.isFinite(parsed)) return false;
|
||||
if (!Number.isFinite(parsed)) {
|
||||
return false;
|
||||
}
|
||||
return Math.abs(parsed - 5) < 0.01;
|
||||
}
|
||||
|
||||
@@ -170,7 +188,9 @@ async function auditLaunchdPlist(
|
||||
}
|
||||
|
||||
function auditGatewayCommand(programArguments: string[] | undefined, issues: ServiceConfigIssue[]) {
|
||||
if (!programArguments || programArguments.length === 0) return;
|
||||
if (!programArguments || programArguments.length === 0) {
|
||||
return;
|
||||
}
|
||||
if (!hasGatewaySubcommand(programArguments)) {
|
||||
issues.push({
|
||||
code: SERVICE_AUDIT_CODES.gatewayCommandMissing,
|
||||
@@ -209,7 +229,9 @@ function auditGatewayServicePath(
|
||||
env: Record<string, string | undefined>,
|
||||
platform: NodeJS.Platform,
|
||||
) {
|
||||
if (platform === "win32") return;
|
||||
if (platform === "win32") {
|
||||
return;
|
||||
}
|
||||
const servicePath = command?.environment?.PATH;
|
||||
if (!servicePath) {
|
||||
issues.push({
|
||||
@@ -276,7 +298,9 @@ async function auditGatewayRuntime(
|
||||
platform: NodeJS.Platform,
|
||||
) {
|
||||
const execPath = command?.programArguments?.[0];
|
||||
if (!execPath) return;
|
||||
if (!execPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isBunRuntime(execPath)) {
|
||||
issues.push({
|
||||
@@ -288,7 +312,9 @@ async function auditGatewayRuntime(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!isNodeRuntime(execPath)) return;
|
||||
if (!isNodeRuntime(execPath)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isVersionManagedNodePath(execPath, platform)) {
|
||||
issues.push({
|
||||
|
||||
@@ -43,15 +43,21 @@ export function resolveLinuxUserBinDirs(
|
||||
home: string | undefined,
|
||||
env?: Record<string, string | undefined>,
|
||||
): string[] {
|
||||
if (!home) return [];
|
||||
if (!home) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dirs: string[] = [];
|
||||
|
||||
const add = (dir: string | undefined) => {
|
||||
if (dir) dirs.push(dir);
|
||||
if (dir) {
|
||||
dirs.push(dir);
|
||||
}
|
||||
};
|
||||
const appendSubdir = (base: string | undefined, subdir: string) => {
|
||||
if (!base) return undefined;
|
||||
if (!base) {
|
||||
return undefined;
|
||||
}
|
||||
return base.endsWith(`/${subdir}`) ? base : path.posix.join(base, subdir);
|
||||
};
|
||||
|
||||
@@ -82,7 +88,9 @@ export function resolveLinuxUserBinDirs(
|
||||
|
||||
export function getMinimalServicePathParts(options: MinimalServicePathOptions = {}): string[] {
|
||||
const platform = options.platform ?? process.platform;
|
||||
if (platform === "win32") return [];
|
||||
if (platform === "win32") {
|
||||
return [];
|
||||
}
|
||||
|
||||
const parts: string[] = [];
|
||||
const extraDirs = options.extraDirs ?? [];
|
||||
@@ -93,14 +101,24 @@ export function getMinimalServicePathParts(options: MinimalServicePathOptions =
|
||||
platform === "linux" ? resolveLinuxUserBinDirs(options.home, options.env) : [];
|
||||
|
||||
const add = (dir: string) => {
|
||||
if (!dir) return;
|
||||
if (!parts.includes(dir)) parts.push(dir);
|
||||
if (!dir) {
|
||||
return;
|
||||
}
|
||||
if (!parts.includes(dir)) {
|
||||
parts.push(dir);
|
||||
}
|
||||
};
|
||||
|
||||
for (const dir of extraDirs) add(dir);
|
||||
for (const dir of extraDirs) {
|
||||
add(dir);
|
||||
}
|
||||
// User dirs first so user-installed binaries take precedence
|
||||
for (const dir of linuxUserDirs) add(dir);
|
||||
for (const dir of systemDirs) add(dir);
|
||||
for (const dir of linuxUserDirs) {
|
||||
add(dir);
|
||||
}
|
||||
for (const dir of systemDirs) {
|
||||
add(dir);
|
||||
}
|
||||
|
||||
return parts;
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
import { formatCliCommand } from "../cli/command-format.js";
|
||||
|
||||
export function isSystemdUnavailableDetail(detail?: string): boolean {
|
||||
if (!detail) return false;
|
||||
if (!detail) {
|
||||
return false;
|
||||
}
|
||||
const normalized = detail.toLowerCase();
|
||||
return (
|
||||
normalized.includes("systemctl --user unavailable") ||
|
||||
|
||||
@@ -3,7 +3,9 @@ import { runCommandWithTimeout, runExec } from "../process/exec.js";
|
||||
|
||||
function resolveLoginctlUser(env: Record<string, string | undefined>): string | null {
|
||||
const fromEnv = env.USER?.trim() || env.LOGNAME?.trim();
|
||||
if (fromEnv) return fromEnv;
|
||||
if (fromEnv) {
|
||||
return fromEnv;
|
||||
}
|
||||
try {
|
||||
return os.userInfo().username;
|
||||
} catch {
|
||||
@@ -20,7 +22,9 @@ export async function readSystemdUserLingerStatus(
|
||||
env: Record<string, string | undefined>,
|
||||
): Promise<SystemdUserLingerStatus | null> {
|
||||
const user = resolveLoginctlUser(env);
|
||||
if (!user) return null;
|
||||
if (!user) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
const { stdout } = await runExec("loginctl", ["show-user", user, "-p", "Linger"], {
|
||||
timeoutMs: 5_000,
|
||||
|
||||
@@ -1,14 +1,20 @@
|
||||
function systemdEscapeArg(value: string): string {
|
||||
if (!/[\\s"\\\\]/.test(value)) return value;
|
||||
if (!/[\\s"\\\\]/.test(value)) {
|
||||
return value;
|
||||
}
|
||||
return `"${value.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, '\\\\"')}"`;
|
||||
}
|
||||
|
||||
function renderEnvLines(env: Record<string, string | undefined> | undefined): string[] {
|
||||
if (!env) return [];
|
||||
if (!env) {
|
||||
return [];
|
||||
}
|
||||
const entries = Object.entries(env).filter(
|
||||
([, value]) => typeof value === "string" && value.trim(),
|
||||
);
|
||||
if (entries.length === 0) return [];
|
||||
if (entries.length === 0) {
|
||||
return [];
|
||||
}
|
||||
return entries.map(
|
||||
([key, value]) => `Environment=${systemdEscapeArg(`${key}=${value?.trim() ?? ""}`)}`,
|
||||
);
|
||||
@@ -85,16 +91,22 @@ export function parseSystemdExecStart(value: string): string[] {
|
||||
}
|
||||
current += char;
|
||||
}
|
||||
if (current) args.push(current);
|
||||
if (current) {
|
||||
args.push(current);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
export function parseSystemdEnvAssignment(raw: string): { key: string; value: string } | null {
|
||||
const trimmed = raw.trim();
|
||||
if (!trimmed) return null;
|
||||
if (!trimmed) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const unquoted = (() => {
|
||||
if (!(trimmed.startsWith('"') && trimmed.endsWith('"'))) return trimmed;
|
||||
if (!(trimmed.startsWith('"') && trimmed.endsWith('"'))) {
|
||||
return trimmed;
|
||||
}
|
||||
let out = "";
|
||||
let escapeNext = false;
|
||||
for (const ch of trimmed.slice(1, -1)) {
|
||||
@@ -113,9 +125,13 @@ export function parseSystemdEnvAssignment(raw: string): { key: string; value: st
|
||||
})();
|
||||
|
||||
const eq = unquoted.indexOf("=");
|
||||
if (eq <= 0) return null;
|
||||
if (eq <= 0) {
|
||||
return null;
|
||||
}
|
||||
const key = unquoted.slice(0, eq).trim();
|
||||
if (!key) return null;
|
||||
if (!key) {
|
||||
return null;
|
||||
}
|
||||
const value = unquoted.slice(eq + 1);
|
||||
return { key, value };
|
||||
}
|
||||
|
||||
@@ -75,7 +75,9 @@ export async function readSystemdServiceExecStart(
|
||||
const environment: Record<string, string> = {};
|
||||
for (const rawLine of content.split("\n")) {
|
||||
const line = rawLine.trim();
|
||||
if (!line || line.startsWith("#")) continue;
|
||||
if (!line || line.startsWith("#")) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith("ExecStart=")) {
|
||||
execStart = line.slice("ExecStart=".length).trim();
|
||||
} else if (line.startsWith("WorkingDirectory=")) {
|
||||
@@ -83,10 +85,14 @@ export async function readSystemdServiceExecStart(
|
||||
} else if (line.startsWith("Environment=")) {
|
||||
const raw = line.slice("Environment=".length).trim();
|
||||
const parsed = parseSystemdEnvAssignment(raw);
|
||||
if (parsed) environment[parsed.key] = parsed.value;
|
||||
if (parsed) {
|
||||
environment[parsed.key] = parsed.value;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (!execStart) return null;
|
||||
if (!execStart) {
|
||||
return null;
|
||||
}
|
||||
const programArguments = parseSystemdExecStart(execStart);
|
||||
return {
|
||||
programArguments,
|
||||
@@ -111,21 +117,31 @@ export function parseSystemdShow(output: string): SystemdServiceInfo {
|
||||
const entries = parseKeyValueOutput(output, "=");
|
||||
const info: SystemdServiceInfo = {};
|
||||
const activeState = entries.activestate;
|
||||
if (activeState) info.activeState = activeState;
|
||||
if (activeState) {
|
||||
info.activeState = activeState;
|
||||
}
|
||||
const subState = entries.substate;
|
||||
if (subState) info.subState = subState;
|
||||
if (subState) {
|
||||
info.subState = subState;
|
||||
}
|
||||
const mainPidValue = entries.mainpid;
|
||||
if (mainPidValue) {
|
||||
const pid = Number.parseInt(mainPidValue, 10);
|
||||
if (Number.isFinite(pid) && pid > 0) info.mainPid = pid;
|
||||
if (Number.isFinite(pid) && pid > 0) {
|
||||
info.mainPid = pid;
|
||||
}
|
||||
}
|
||||
const execMainStatusValue = entries.execmainstatus;
|
||||
if (execMainStatusValue) {
|
||||
const status = Number.parseInt(execMainStatusValue, 10);
|
||||
if (Number.isFinite(status)) info.execMainStatus = status;
|
||||
if (Number.isFinite(status)) {
|
||||
info.execMainStatus = status;
|
||||
}
|
||||
}
|
||||
const execMainCode = entries.execmaincode;
|
||||
if (execMainCode) info.execMainCode = execMainCode;
|
||||
if (execMainCode) {
|
||||
info.execMainCode = execMainCode;
|
||||
}
|
||||
return info;
|
||||
}
|
||||
|
||||
@@ -159,20 +175,36 @@ async function execSystemctl(
|
||||
|
||||
export async function isSystemdUserServiceAvailable(): Promise<boolean> {
|
||||
const res = await execSystemctl(["--user", "status"]);
|
||||
if (res.code === 0) return true;
|
||||
if (res.code === 0) {
|
||||
return true;
|
||||
}
|
||||
const detail = `${res.stderr} ${res.stdout}`.toLowerCase();
|
||||
if (!detail) return false;
|
||||
if (detail.includes("not found")) return false;
|
||||
if (detail.includes("failed to connect")) return false;
|
||||
if (detail.includes("not been booted")) return false;
|
||||
if (detail.includes("no such file or directory")) return false;
|
||||
if (detail.includes("not supported")) return false;
|
||||
if (!detail) {
|
||||
return false;
|
||||
}
|
||||
if (detail.includes("not found")) {
|
||||
return false;
|
||||
}
|
||||
if (detail.includes("failed to connect")) {
|
||||
return false;
|
||||
}
|
||||
if (detail.includes("not been booted")) {
|
||||
return false;
|
||||
}
|
||||
if (detail.includes("no such file or directory")) {
|
||||
return false;
|
||||
}
|
||||
if (detail.includes("not supported")) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
async function assertSystemdAvailable() {
|
||||
const res = await execSystemctl(["--user", "status"]);
|
||||
if (res.code === 0) return;
|
||||
if (res.code === 0) {
|
||||
return;
|
||||
}
|
||||
const detail = res.stderr || res.stdout;
|
||||
if (detail.toLowerCase().includes("not found")) {
|
||||
throw new Error("systemctl not available; systemd user services are required on Linux.");
|
||||
@@ -352,7 +384,9 @@ export type LegacySystemdUnit = {
|
||||
|
||||
async function isSystemctlAvailable(): Promise<boolean> {
|
||||
const res = await execSystemctl(["--user", "status"]);
|
||||
if (res.code === 0) return true;
|
||||
if (res.code === 0) {
|
||||
return true;
|
||||
}
|
||||
const detail = `${res.stderr || res.stdout}`.toLowerCase();
|
||||
return !detail.includes("not found");
|
||||
}
|
||||
@@ -391,7 +425,9 @@ export async function uninstallLegacySystemdUnits({
|
||||
stdout: NodeJS.WritableStream;
|
||||
}): Promise<LegacySystemdUnit[]> {
|
||||
const units = await findLegacySystemdUnits(env);
|
||||
if (units.length === 0) return units;
|
||||
if (units.length === 0) {
|
||||
return units;
|
||||
}
|
||||
|
||||
const systemctlAvailable = await isSystemctlAvailable();
|
||||
for (const unit of units) {
|
||||
|
||||
Reference in New Issue
Block a user