mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 11:25:50 -06:00
docs: document daemon audit helpers
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
/** Builds platform-specific log and start hints for daemon status output. */
|
||||
import { toPosixPath } from "./output.js";
|
||||
import { resolveGatewayRestartLogPath, resolveGatewaySupervisorLogPaths } from "./restart-logs.js";
|
||||
|
||||
@@ -16,6 +17,8 @@ export function buildPlatformRuntimeLogHints(params: {
|
||||
const env = { ...process.env, ...params.env };
|
||||
if (platform === "darwin") {
|
||||
const logs = resolveGatewaySupervisorLogPaths(env, { platform });
|
||||
// Display launchd paths as POSIX-style paths even in cross-platform tests
|
||||
// where mocked env values may carry Windows drive prefixes.
|
||||
return [
|
||||
`Launchd stdout (if installed): ${toDarwinDisplayPath(logs.stdoutPath)}`,
|
||||
"Launchd stderr (if installed): suppressed",
|
||||
@@ -47,6 +50,8 @@ export function buildPlatformServiceStartHints(params: {
|
||||
}): string[] {
|
||||
const platform = params.platform ?? process.platform;
|
||||
const base = [params.installCommand, params.startCommand];
|
||||
// Native service-manager commands are supplemental hints; the OpenClaw
|
||||
// commands stay first because they know the generated profile/env paths.
|
||||
switch (platform) {
|
||||
case "darwin":
|
||||
return [...base, `launchctl bootstrap gui/$UID ${params.launchAgentPlistPath}`];
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Parses daemon runtime command output into normalized key-value maps. */
|
||||
import { normalizeLowercaseStringOrEmpty } from "@openclaw/normalization-core/string-coerce";
|
||||
|
||||
/** Parses command output key-value lines using a caller-supplied separator. */
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Selects stable Node runtime paths for daemon installs across platforms. */
|
||||
import { execFile } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
@@ -44,6 +45,8 @@ function buildSystemNodeCandidates(
|
||||
env: Record<string, string | undefined>,
|
||||
platform: NodeJS.Platform,
|
||||
): string[] {
|
||||
// Prefer system package-manager Node paths over shell-managed shims; daemons
|
||||
// launch without interactive shell init files.
|
||||
if (platform === "darwin") {
|
||||
return [
|
||||
"/opt/homebrew/bin/node",
|
||||
@@ -104,6 +107,7 @@ async function isVersionManagedRealNodePath(
|
||||
): Promise<boolean> {
|
||||
try {
|
||||
const realPath = await fs.realpath(nodePath);
|
||||
// Symlinks in /usr/local/bin can resolve into version-manager trees.
|
||||
return isVersionManagedNodePath(realPath, platform);
|
||||
} catch {
|
||||
return false;
|
||||
@@ -218,7 +222,8 @@ export async function resolvePreferredNodePath(params: {
|
||||
if (!isVersionManagedNodePath(currentExecPath, platform)) {
|
||||
return stableCurrentPath;
|
||||
}
|
||||
// Prefer system Node over a version-manager shim so daemon launch survives shell setup.
|
||||
// Prefer system Node over a version-manager shim so daemon launch survives
|
||||
// shell setup differences and package manager upgrades.
|
||||
const systemNode = await resolveSystemNodeInfo({
|
||||
env: params.env,
|
||||
platform,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Executes Windows Task Scheduler commands with daemon-friendly timeouts. */
|
||||
import { runCommandWithTimeout } from "../process/exec.js";
|
||||
|
||||
const SCHTASKS_TIMEOUT_MS = 15_000;
|
||||
@@ -17,6 +18,8 @@ export async function execSchtasks(
|
||||
: result.termination === "no-output-timeout"
|
||||
? `schtasks produced no output for ${SCHTASKS_NO_OUTPUT_TIMEOUT_MS}ms`
|
||||
: "";
|
||||
// schtasks can hang without output on some Windows hosts; convert both timeout
|
||||
// modes into ordinary process-like failures for service fallback logic.
|
||||
return {
|
||||
stdout: result.stdout,
|
||||
stderr: result.stderr || timeoutDetail,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Windows Task Scheduler installer, startup fallback, and lifecycle controls. */
|
||||
import { spawn, spawnSync } from "node:child_process";
|
||||
import fs from "node:fs/promises";
|
||||
import os from "node:os";
|
||||
@@ -43,6 +44,8 @@ function resolveTaskName(env: GatewayServiceEnv): string {
|
||||
}
|
||||
|
||||
function shouldFallbackToStartupEntry(params: { code: number; detail: string }): boolean {
|
||||
// Permission failures and hung schtasks calls can still be served by the
|
||||
// per-user Startup folder fallback.
|
||||
return (
|
||||
params.code === 1 ||
|
||||
/(?:access is denied|acceso denegado)/i.test(params.detail) ||
|
||||
@@ -93,6 +96,8 @@ function resolveStartupEntryPath(env: GatewayServiceEnv, extension?: "cmd" | "vb
|
||||
function resolveStartupEntryPaths(env: GatewayServiceEnv): string[] {
|
||||
const primaryPath = resolveStartupEntryPath(env);
|
||||
const legacyCmdPath = resolveStartupEntryPath(env, "cmd");
|
||||
// Hidden VBS launchers supersede cmd launchers, but uninstall must remove the
|
||||
// legacy cmd path from older installs too.
|
||||
return uniqueStrings([primaryPath, legacyCmdPath]);
|
||||
}
|
||||
|
||||
@@ -257,6 +262,7 @@ export async function readScheduledTaskCommand(
|
||||
if (lower.startsWith("set ")) {
|
||||
const assignment = parseCmdSetAssignment(line.slice(4));
|
||||
if (assignment) {
|
||||
// Generated cmd launchers inline service env before the final command.
|
||||
environment[assignment.key] = assignment.value;
|
||||
}
|
||||
continue;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Audits installed daemon service definitions for drift and repair candidates. */
|
||||
import fs from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import {
|
||||
@@ -73,6 +74,7 @@ export const SERVICE_AUDIT_CODES = {
|
||||
systemdKillModeProcessOrNone: "systemd-kill-mode-process-or-none",
|
||||
} as const;
|
||||
|
||||
/** Returns whether audit issues require migrating a daemon to a stable Node runtime. */
|
||||
export function needsNodeRuntimeMigration(issues: ServiceConfigIssue[]): boolean {
|
||||
return issues.some(
|
||||
(issue) =>
|
||||
@@ -96,6 +98,8 @@ function parseSystemdUnit(content: string): {
|
||||
let restartSec: string | undefined;
|
||||
let killMode: string | undefined;
|
||||
|
||||
// Parse only unit keys relevant to service resilience; this is not a full
|
||||
// systemd parser and intentionally ignores sections.
|
||||
for (const rawLine of content.split(/\r?\n/)) {
|
||||
const line = rawLine.trim();
|
||||
if (!line) {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Builds normalized environment plans for managed daemon service rendering. */
|
||||
import { normalizeEnvVarKey } from "../infra/host-env-security.js";
|
||||
import type { GatewayServiceEnvironmentValueSource } from "./service-types.js";
|
||||
|
||||
@@ -53,6 +54,8 @@ export function addServiceEnvPlanEntries(
|
||||
for (const [rawKey, rawValue] of Object.entries(entries)) {
|
||||
if (typeof rawValue !== "string" || !rawValue.trim()) {
|
||||
if (options.includeRawKeys) {
|
||||
// Preserve explicit blank raw keys only when callers need round-trip
|
||||
// visibility in generated service env.
|
||||
plan.environment[rawKey] = rawValue;
|
||||
plan.environmentValueSources[rawKey] = "inline";
|
||||
}
|
||||
@@ -69,6 +72,8 @@ export function addServiceEnvPlanEntries(
|
||||
? options.valueSource({ rawKey, normalizedKey })
|
||||
: options.valueSource;
|
||||
plan.environmentValueSources[rawKey] = valueSource ?? "inline";
|
||||
// Last writer wins per normalized key so later, higher-priority env sources
|
||||
// can decide render policy without scanning duplicate casing.
|
||||
plan.entriesByNormalizedKey.set(normalizedKey, {
|
||||
rawKey,
|
||||
normalizedKey,
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/** Applies platform render policy for managed daemon service environment values. */
|
||||
import type { MutableServiceEnvPlan } from "./service-env-plan.js";
|
||||
import {
|
||||
readManagedServiceEnvKeysFromEnvironment,
|
||||
@@ -38,6 +39,8 @@ export function applyManagedServiceEnvRenderPolicy(params: {
|
||||
if (entry.source !== "state-dotenv" || !managedKeys.has(entry.normalizedKey)) {
|
||||
continue;
|
||||
}
|
||||
// launchd does not read shell dotenv files; inline only the managed dotenv
|
||||
// keys declared for this service.
|
||||
params.plan.environment[entry.rawKey] = entry.value;
|
||||
params.plan.environmentValueSources[entry.rawKey] = "inline";
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user