openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
277 lines (276 loc) • 10.9 kB
JavaScript
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 };