openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
115 lines (114 loc) • 3.98 kB
JavaScript
import { l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js";
import { t as splitArgsPreservingQuotes } from "./arg-split-CfZZMiL-.js";
//#region src/daemon/systemd-unit.ts
/** Renders and parses systemd unit snippets for managed gateway services. */
const SYSTEMD_LINE_BREAKS = /[\r\n]/;
function assertNoSystemdLineBreaks(value, label) {
if (SYSTEMD_LINE_BREAKS.test(value)) throw new Error(`${label} cannot contain CR or LF characters.`);
}
function systemdEscapeArg(value) {
assertNoSystemdLineBreaks(value, "Systemd unit values");
if (!/[\s"\\]/.test(value)) return value;
return `"${value.replace(/\\\\/g, "\\\\\\\\").replace(/"/g, "\\\\\"")}"`;
}
function renderEnvLines(env) {
if (!env) return [];
const entries = Object.entries(env).filter(([, value]) => typeof value === "string" && value.trim());
if (entries.length === 0) return [];
return entries.map(([key, value]) => {
const rawValue = value ?? "";
assertNoSystemdLineBreaks(key, "Systemd environment variable names");
assertNoSystemdLineBreaks(rawValue, "Systemd environment variable values");
return `Environment=${systemdEscapeArg(`${key}=${rawValue.trim()}`)}`;
});
}
function renderEnvironmentFileLines(environmentFiles) {
if (!environmentFiles) return [];
return normalizeStringEntries(environmentFiles).map((entry) => {
assertNoSystemdLineBreaks(entry, "Systemd EnvironmentFile values");
return `EnvironmentFile=-${systemdEscapeArg(entry)}`;
});
}
function buildSystemdUnit({ description, programArguments, workingDirectory, environment, environmentFiles }) {
const execStart = programArguments.map(systemdEscapeArg).join(" ");
const descriptionValue = description?.trim() || "OpenClaw Gateway";
assertNoSystemdLineBreaks(descriptionValue, "Systemd Description");
const descriptionLine = `Description=${descriptionValue}`;
const workingDirLine = workingDirectory ? `WorkingDirectory=${systemdEscapeArg(workingDirectory)}` : null;
const envLines = renderEnvLines(environment);
const environmentFileLines = renderEnvironmentFileLines(environmentFiles);
return [
"[Unit]",
descriptionLine,
"After=network-online.target",
"Wants=network-online.target",
"StartLimitBurst=5",
"StartLimitIntervalSec=60",
"",
"[Service]",
`ExecStart=${execStart}`,
"Restart=always",
"RestartSec=5",
"RestartPreventExitStatus=78",
"TimeoutStopSec=30",
"TimeoutStartSec=30",
"SuccessExitStatus=0 143",
"KillMode=control-group",
workingDirLine,
...environmentFileLines,
...envLines,
"",
"[Install]",
"WantedBy=default.target",
""
].filter((line) => line !== null).join("\n");
}
function parseSystemdExecStart(value) {
return splitArgsPreservingQuotes(value, { escapeMode: "backslash" });
}
function parseSystemdEnvAssignment(raw) {
const trimmed = raw.trim();
if (!trimmed) return null;
const unquoted = (() => {
const quote = trimmed[0];
if (!((quote === "\"" || quote === "'") && trimmed.endsWith(quote))) return trimmed;
let out = "";
let escapeNext = false;
for (const ch of trimmed.slice(1, -1)) {
if (escapeNext) {
out += ch;
escapeNext = false;
continue;
}
if (ch === "\\\\") {
escapeNext = true;
continue;
}
out += ch;
}
return out;
})();
const eq = unquoted.indexOf("=");
if (eq <= 0) return null;
const key = unquoted.slice(0, eq).trim();
if (!key) return null;
return {
key,
value: unquoted.slice(eq + 1)
};
}
function parseSystemdEnvAssignments(raw) {
return splitArgsPreservingQuotes(raw, {
escapeMode: "backslash",
quoteChars: ["\"", "'"],
quoteStart: "item-start"
}).flatMap((entry) => {
const parsed = parseSystemdEnvAssignment(entry);
return parsed ? [parsed] : [];
});
}
function renderSystemdEnvAssignment(key, value) {
return systemdEscapeArg(`${key}=${value}`);
}
//#endregion
export { renderSystemdEnvAssignment as a, parseSystemdExecStart as i, parseSystemdEnvAssignment as n, parseSystemdEnvAssignments as r, buildSystemdUnit as t };