UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

277 lines (276 loc) 10.9 kB
import { f as resolveGatewayServiceDescription, t as GATEWAY_LAUNCH_AGENT_LABEL } from "./constants-ChqKLfPp.js"; import { a as normalizeEnvVarKey } from "./host-env-security-BAvlDaJf.js"; import { n as resolveGatewayStateDir, t as resolveDaemonHomeDir } from "./paths-CzCbqt0l.js"; import { c as buildLaunchAgentPlist$1, d as readLaunchAgentProgramArgumentsFromFile, p as resolveLaunchAgentLabel, s as LAUNCH_AGENT_ENV_WRAPPER_SHELL, t as assertNoSystemLaunchDaemonOwnership, u as quoteLaunchAgentEnvironmentValue } from "./launchd-system-BlHvFe-Q.js"; import { n as formatLine, r as normalizeWindowsPathSeparators } from "./service-mutation-BxHhMRDF.js"; import { o as resolveGatewaySupervisorLogPaths } from "./restart-logs-uOTpYNS7.js"; import path from "node:path"; import fs from "node:fs/promises"; import { randomUUID } from "node:crypto"; //#region src/daemon/launchd-service-files.ts /** LaunchAgent plist, environment-file, and atomic publication ownership. */ const LAUNCH_AGENT_DIR_MODE = 493; const LAUNCH_AGENT_PRIVATE_DIR_MODE = 448; const LAUNCH_AGENT_ENV_DIR_NAME = "service-env"; const LAUNCH_AGENT_STDERR_PATH = "/dev/null"; function resolveLaunchAgentPlistPathForLabel(env, label) { const home = normalizeWindowsPathSeparators(resolveDaemonHomeDir(env)); return path.posix.join(home, "Library", "LaunchAgents", `${label}.plist`); } function resolveLaunchAgentEnvDir(env) { return path.join(resolveGatewayStateDir(env), LAUNCH_AGENT_ENV_DIR_NAME); } function resolveLaunchAgentEnvFilePath(env, label) { return path.join(resolveLaunchAgentEnvDir(env), `${label}.env`); } function resolveLaunchAgentEnvWrapperPath(env, label) { return path.join(resolveLaunchAgentEnvDir(env), `${label}-env-wrapper.sh`); } function collectLaunchAgentEnvironmentEntries(environment) { const entries = []; for (const [rawKey, rawValue] of Object.entries(environment ?? {})) { const key = normalizeEnvVarKey(rawKey, { portable: true }); const value = rawValue?.trim(); if (!key || value === void 0 || !value && key !== "NODE_OPTIONS") continue; entries.push([key, value]); } return entries.toSorted(([left], [right]) => left.localeCompare(right)); } function buildLaunchAgentEnvironmentFile(entries) { return [ "# Generated by OpenClaw. Do not edit while the gateway service is installed.", ...entries.map(([key, value]) => `export ${key}=${quoteLaunchAgentEnvironmentValue(value)}`), "" ].join("\n"); } function buildLaunchAgentEnvironmentWrapper() { return `#!/bin/sh set -eu env_file="$1" shift if [ -f "$env_file" ]; then . "$env_file" fi exec "$@" `; } async function resolveLaunchAgentEnvironmentWrapperOverwriteWarnings(params) { const existingWrapper = await fs.readFile(params.wrapperPath, "utf8").catch(() => null); if (existingWrapper === null || existingWrapper === params.generatedWrapper) return []; return [`Existing generated LaunchAgent env wrapper at ${params.wrapperPath} contains custom behavior and will be overwritten; move custom behavior to openclaw gateway install --wrapper <path> or OPENCLAW_WRAPPER.`]; } function writeLaunchAgentOverwriteWarnings(stdout, warn, warnings) { for (const warning of warnings) { if (warn) { warn(warning); continue; } if (!stdout) continue; stdout.write(`${formatLine("Warning", warning)}\n`); } } function isLaunchAgentEnvironmentWrapperArgs(params) { return params.programArguments[0] === params.wrapperPath && params.programArguments[1] === params.envFilePath || params.programArguments[0] === "/bin/sh" && params.programArguments[1] === params.wrapperPath && params.programArguments[2] === params.envFilePath; } async function prepareLaunchAgentProgramArguments(params) { const entries = collectLaunchAgentEnvironmentEntries(params.environment); if (entries.length === 0) return { programArguments: params.programArguments }; const envDir = resolveLaunchAgentEnvDir(params.env); const envFilePath = resolveLaunchAgentEnvFilePath(params.env, params.label); const wrapperPath = resolveLaunchAgentEnvWrapperPath(params.env, params.label); const generatedWrapper = buildLaunchAgentEnvironmentWrapper(); await ensureSecureDirectory(envDir, LAUNCH_AGENT_PRIVATE_DIR_MODE); await fs.writeFile(envFilePath, buildLaunchAgentEnvironmentFile(entries), { encoding: "utf8", mode: 384 }); await fs.chmod(envFilePath, 384).catch(() => void 0); const overwriteWarnings = await resolveLaunchAgentEnvironmentWrapperOverwriteWarnings({ wrapperPath, generatedWrapper }); writeLaunchAgentOverwriteWarnings(params.stdout, params.warn, overwriteWarnings); await fs.writeFile(wrapperPath, generatedWrapper, { encoding: "utf8", mode: 448 }); await fs.chmod(wrapperPath, 448).catch(() => void 0); if (isLaunchAgentEnvironmentWrapperArgs({ programArguments: params.programArguments, envFilePath, wrapperPath })) return { programArguments: params.programArguments }; return { programArguments: [ LAUNCH_AGENT_ENV_WRAPPER_SHELL, wrapperPath, envFilePath, ...params.programArguments ] }; } function resolveLaunchAgentPlistPath(env) { return resolveLaunchAgentPlistPathForLabel(env, resolveLaunchAgentLabel(env)); } function resolveLaunchAgentEnvironmentReadOptions(env, label) { return { expectedEnvironmentWrapperPath: resolveLaunchAgentEnvWrapperPath(env, label), expectedEnvironmentFilePath: resolveLaunchAgentEnvFilePath(env, label), generatedEnvironmentLabel: label }; } function buildLaunchAgentPlist({ label = GATEWAY_LAUNCH_AGENT_LABEL, comment, programArguments, workingDirectory, stdoutPath, stderrPath, environment }) { return buildLaunchAgentPlist$1({ label, comment, programArguments, workingDirectory, stdoutPath, stderrPath, environment }); } async function ensureLaunchAgentPlistReadable(plistPath) { await fs.chmod(plistPath, 420).catch(() => void 0); } async function readExistingLaunchAgentPlist(plistPath) { try { return await fs.readFile(plistPath); } catch (error) { if (error.code === "ENOENT") return null; throw error; } } async function publishLaunchAgentPlist(params) { const previousContents = await readExistingLaunchAgentPlist(params.plistPath); const temporaryPath = `${params.plistPath}.openclaw-${randomUUID()}.tmp`; await fs.writeFile(temporaryPath, params.contents, { encoding: "utf8", flag: "wx", mode: 420 }); try { await assertNoSystemLaunchDaemonOwnership(params.label); await fs.rename(temporaryPath, params.plistPath); try { await assertNoSystemLaunchDaemonOwnership(params.label); } catch (ownershipError) { try { if (previousContents === null) await fs.unlink(params.plistPath); else { const rollbackPath = `${params.plistPath}.openclaw-${randomUUID()}.rollback`; try { await fs.writeFile(rollbackPath, previousContents, { flag: "wx", mode: 420 }); await fs.rename(rollbackPath, params.plistPath); } finally { await fs.unlink(rollbackPath).catch(() => void 0); } } } catch (rollbackError) { const ownershipDetail = ownershipError instanceof Error ? ownershipError.message : String(ownershipError); throw new Error(`${ownershipDetail}\nThe previous LaunchAgent plist at ${params.plistPath} could not be restored.`, { cause: rollbackError }); } throw ownershipError; } } finally { await fs.unlink(temporaryPath).catch(() => void 0); } await ensureLaunchAgentPlistReadable(params.plistPath); } async function ensureSecureDirectory(targetPath, dirMode = LAUNCH_AGENT_DIR_MODE) { await fs.mkdir(targetPath, { recursive: true, mode: dirMode }); try { const mode = (await fs.stat(targetPath)).mode & 511; const tightenedMode = mode & ~(dirMode === LAUNCH_AGENT_PRIVATE_DIR_MODE ? 63 : 18); if (tightenedMode !== mode) await fs.chmod(targetPath, tightenedMode); } catch {} } async function ensureLaunchAgentEnvironmentDirectories(environment) { const tmpDir = environment?.TMPDIR?.trim(); if (tmpDir) await ensureSecureDirectory(tmpDir, LAUNCH_AGENT_PRIVATE_DIR_MODE); } async function writeLaunchAgentPlist({ env, programArguments, workingDirectory, environment, description, stdout, warn }) { const label = resolveLaunchAgentLabel(env); await assertNoSystemLaunchDaemonOwnership(label); const { logDir, stdoutPath } = resolveGatewaySupervisorLogPaths(env, { platform: "darwin" }); await ensureSecureDirectory(logDir); const plistPath = resolveLaunchAgentPlistPathForLabel(env, label); const home = normalizeWindowsPathSeparators(resolveDaemonHomeDir(env)); const libraryDir = path.posix.join(home, "Library"); await ensureSecureDirectory(home); await ensureSecureDirectory(libraryDir); await ensureSecureDirectory(path.dirname(plistPath)); await ensureLaunchAgentEnvironmentDirectories(environment); const prepared = await prepareLaunchAgentProgramArguments({ env, label, programArguments, environment, stdout, warn }); await publishLaunchAgentPlist({ label, plistPath, contents: buildLaunchAgentPlist({ label, comment: resolveGatewayServiceDescription({ env, description }), programArguments: prepared.programArguments, workingDirectory, stdoutPath, stderrPath: LAUNCH_AGENT_STDERR_PATH, environment: prepared.inlineEnvironment }) }); return { plistPath, stdoutPath }; } async function rewriteLaunchAgentPlistForRestart({ env, label, plistPath, stdout, warn }) { const existing = await readLaunchAgentProgramArgumentsFromFile(plistPath, resolveLaunchAgentEnvironmentReadOptions(env, label)); if (!existing?.programArguments.length) return false; const { logDir, stdoutPath } = resolveGatewaySupervisorLogPaths(env, { platform: "darwin" }); await ensureSecureDirectory(logDir); const serviceDescription = resolveGatewayServiceDescription({ env }); const canonicalEnvironment = { ...existing.environment, OPENCLAW_SERVICE_VERSION: void 0 }; const prepared = await prepareLaunchAgentProgramArguments({ env, label, programArguments: existing.programArguments, environment: canonicalEnvironment, stdout, warn }); const plist = buildLaunchAgentPlist({ label, comment: serviceDescription, programArguments: prepared.programArguments, workingDirectory: existing.workingDirectory, stdoutPath, stderrPath: LAUNCH_AGENT_STDERR_PATH, environment: prepared.inlineEnvironment }); if (await fs.readFile(plistPath, "utf8").catch(() => "") === plist) { await ensureLaunchAgentPlistReadable(plistPath); return false; } await publishLaunchAgentPlist({ label, plistPath, contents: plist }); return true; } //#endregion export { resolveLaunchAgentEnvironmentReadOptions as a, rewriteLaunchAgentPlistForRestart as c, resolveLaunchAgentEnvWrapperPath as i, writeLaunchAgentPlist as l, readExistingLaunchAgentPlist as n, resolveLaunchAgentPlistPath as o, resolveLaunchAgentEnvFilePath as r, resolveLaunchAgentPlistPathForLabel as s, publishLaunchAgentPlist as t };