import { c as normalizeOptionalLowercaseString, l as normalizeOptionalString, o as normalizeLowercaseStringOrEmpty } from "./string-coerce-CIXf7egm.js";
import { C as parseStrictNonNegativeInteger, S as parseStrictInteger, w as parseStrictPositiveInteger } from "./number-coercion-CLj0HTDM.js";
import { i as findExistingAncestor, n as canonicalPathFromExistingAncestor } from "./absolute-path-CYFPfAjt.js";
import "./fs-safe-gVoExnQ2.js";
import { t as hasErrnoCode } from "./errno-CkbDOfLk.js";
import { f as resolveGatewayServiceDescription, o as LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES } from "./constants-ChqKLfPp.js";
import { w as resolveStateDir } from "./paths-BBSTUjD5.js";
import { r as formatErrorMessage } from "./errors-Z0lU1pH3.js";
import { t as decodeMountInfoPath } from "./mountinfo-path-BCOIljp0.js";
import { a as sha256Hex } from "./crypto-digest-IGAbV2KW.js";
import { s as withFileLock } from "./file-lock-DRVK2jTz.js";
import "./file-lock-CS5vu_jB.js";
import { C as reloadSystemdUserManager, O as isUnresolvedShellReference, S as readSystemctlDetail, a as resolveSystemdEnvironmentFilePath, b as isSystemdUserScopeUnavailable, c as resolveSystemdUnitPathForName, d as disableSystemdUserUnitForRemoval, f as execSystemctl, g as isSystemctlMissing, h as isSystemctlAvailable, i as resolveLegacyNodeSystemdEnvironmentFilePath, k as readStateDirDotEnvFromStateDir, l as serializeSystemdEnvironmentFile, n as readSystemdEnvironmentFile, o as resolveSystemdServiceName, p as execSystemctlUser, r as readSystemdServiceExecStart, s as resolveSystemdUnitPath, t as isNodeSystemdEnvironment, u as assertSystemdAvailable, v as isSystemdUnitMissingDetail, y as isSystemdUnitNotEnabled } from "./systemd-service-files-BgofM7Qa.js";
import { a as resolveManagedGatewayServiceCommand, i as hasGatewayServiceLauncherOverride, t as assertServiceDefinitionWritable } from "./service-types-CK8rxZ9x.js";
import { a as hasEnvironmentFileSource, c as normalizeServiceEnvKey, d as readManagedServiceEnvKeysFromEnvironment, l as normalizeServiceEnvKeys, o as hasInlineEnvironmentSource, s as isEnvironmentFileOnlySource, u as readEnvironmentValueSource } from "./service-managed-env-D38lJbxp.js";
import { n as runExec, r as runCommandWithTimeout } from "./exec-B6wf9wmM.js";
import { a as writeFormattedLines, n as parseKeyValueOutput, r as formatLine, t as createGatewayLifecycleMutationReporter } from "./service-mutation-DyzHamq7.js";
import { i as renderSystemdEnvAssignment, n as parseSystemdEnvAssignments, t as buildSystemdUnit } from "./systemd-unit-gmSpEFTd.js";
import { n as findInstalledSystemdGatewayScope, o as assertNoSystemSystemdOwnership, s as isSystemSystemdOwnershipError, t as assertNoSystemGatewayOwnership } from "./systemd-scope-ChaH1BE3.js";
import { constants, promises } from "node:fs";
import os from "node:os";
import path from "node:path";
import fs$1 from "node:fs/promises";
import { randomUUID } from "node:crypto";
import { isDeepStrictEqual } from "node:util";
//#region src/daemon/systemd-definition-mutation.ts
/** Systemd service-definition authority and atomic, cross-process publication. */
const identity = (stat, contents) => [
	stat.dev,
	stat.ino,
	stat.uid,
	stat.gid,
	stat.mode,
	contents && sha256Hex(contents)
].join(":");
function resolveMutationTargets(env, environment) {
	return {
		unit: resolveSystemdUnitPath(env),
		generated: resolveSystemdEnvironmentFilePath({
			stateDir: resolveStateDir({
				...env,
				...environment
			}),
			environment
		})
	};
}
async function readStableFile(file, stat, requireReplacement) {
	const handle = await promises.open(file, constants.O_RDONLY | constants.O_NOFOLLOW | (constants.O_NONBLOCK ?? 0));
	try {
		const opened = await handle.stat();
		if (!opened.isFile() || identity(opened) !== identity(stat)) throw new Error("changed artifact");
		if (requireReplacement && process.platform === "linux") {
			const fdinfo = await promises.readFile(`/proc/self/fdinfo/${handle.fd}`, "utf8");
			const mountId = /^mnt_id:\s+(\d+)$/m.exec(fdinfo)?.[1];
			const mount = (await promises.readFile("/proc/self/mountinfo", "utf8")).split("\n").find((line) => mountId && line.startsWith(`${mountId} `))?.split(" ");
			if (!mount?.[4] || !mount[5]) throw new Error("Cannot inspect the service artifact mount.");
			if (mount[5].split(",").includes("ro") || decodeMountInfoPath(mount[4]) === await promises.realpath(file)) return { sealed: true };
		}
		return { contents: await handle.readFile() };
	} finally {
		await handle.close();
	}
}
async function inspect$1(env, environment, timeoutMs) {
	const { unit, generated } = resolveMutationTargets(env, environment);
	const snapshots = /* @__PURE__ */ new Map();
	const fingerprint = /* @__PURE__ */ new Map();
	let shared = /* @__PURE__ */ new Set();
	let sourcePath;
	const result = (capability) => ({
		capability,
		snapshots,
		fingerprint,
		shared,
		sourcePath
	});
	let artifact;
	try {
		const command = await readSystemdServiceExecStart(env, {
			requireEffective: true,
			timeoutMs
		});
		sourcePath = command?.sourcePath;
		const targets = /* @__PURE__ */ new Set([
			unit,
			generated,
			`${unit}.bak`
		]);
		const definitions = new Set(command?.definitionPaths ?? []);
		shared = new Set([...definitions].filter((file) => file !== command?.sourcePath && !targets.has(file) && path.basename(path.dirname(file)) === "service.d"));
		const definitionParents = new Set([...definitions].filter((file) => !shared.has(file)).map(path.dirname));
		const parents = /* @__PURE__ */ new Set([
			path.dirname(unit),
			path.dirname(generated),
			...definitionParents
		]);
		const artifacts = /* @__PURE__ */ new Set([
			...parents,
			...targets,
			...definitions
		]);
		for (const file of artifacts) {
			const directory = parents.has(file) && !definitions.has(file);
			const required = definitions.has(file) || definitionParents.has(file);
			artifact = !directory ? "service-file" : file === path.dirname(unit) ? "service-directory" : file === path.dirname(generated) ? "state-directory" : "definition-directory";
			const inspected = directory && !required ? await findExistingAncestor(file) ?? file : file;
			const stat = await promises.lstat(inspected).catch((error) => {
				if (required || !hasErrnoCode(error, "ENOENT")) throw error;
			});
			if (!stat) {
				fingerprint.set(file, "missing");
				continue;
			}
			if (!directory && stat.isSymbolicLink()) return result({
				kind: "unknown",
				reason: "symlink",
				artifact
			});
			const actual = directory && stat.isSymbolicLink() ? await promises.stat(inspected) : stat;
			if (!shared.has(file) && actual.uid !== process.geteuid?.()) return result({
				kind: "sealed",
				reason: "foreign-owner",
				artifact
			});
			if (directory && !actual.isDirectory()) return result({
				kind: "unknown",
				reason: "invalid-artifact",
				artifact
			});
			if (actual.mode & 18) return result({
				kind: "unknown",
				reason: "unsafe-permissions",
				artifact
			});
			if (directory) {
				await promises.access(inspected, constants.W_OK | constants.X_OK);
				fingerprint.set(file, `${inspected}:${identity(stat)}:${identity(actual)}`);
				continue;
			}
			const snapshot = await readStableFile(file, stat, !shared.has(file));
			if ("sealed" in snapshot) return result({
				kind: "sealed",
				reason: "sealed-mount",
				artifact
			});
			const { contents } = snapshot;
			fingerprint.set(file, identity(stat, contents));
			if (targets.has(file)) snapshots.set(file, {
				contents,
				mode: stat.mode & 511
			});
		}
		return result({ kind: "writable" });
	} catch {
		return result({
			kind: "unknown",
			reason: "inspection-failed",
			artifact
		});
	}
}
async function readSystemdDefinitionMutationCapability(env, options) {
	const selected = path.basename(resolveSystemdUnitPath(env));
	const names = selected === "openclaw-gateway.service" ? [selected, "openclaw.service"] : [selected];
	const deadlineAt = options?.timeoutMs ? Date.now() + options.timeoutMs : void 0;
	for (const name of names) try {
		await assertNoSystemSystemdOwnership(name, deadlineAt === void 0 ? void 0 : Math.max(1, deadlineAt - Date.now()));
	} catch (error) {
		return isSystemSystemdOwnershipError(error) && error.ownership.status !== "unverifiable" ? {
			kind: "sealed",
			reason: "system-owned"
		} : {
			kind: "unknown",
			reason: "system-ownership-unverified"
		};
	}
	return (await inspect$1(env, options?.environment ?? env, options?.timeoutMs)).capability;
}
async function withSystemdDefinitionMutation(env, environment, run) {
	let initial = await inspect$1(env, environment);
	assertServiceDefinitionWritable(initial.capability);
	const { unit, generated } = resolveMutationTargets(env, environment);
	await promises.mkdir(path.dirname(unit), {
		recursive: true,
		mode: 493
	});
	await promises.mkdir(path.dirname(generated), {
		recursive: true,
		mode: 448
	});
	const canonicalTargets = () => Promise.all([unit, generated].map(canonicalPathFromExistingAncestor));
	const lockedTargets = await canonicalTargets();
	const targets = lockedTargets.map((target) => path.join(path.dirname(target), `.openclaw-${sha256Hex(target)}`)).toSorted();
	const execute = async () => {
		const refresh = async (unchanged = false, firstUnitPublication = false) => {
			const current = await inspect$1(env, environment);
			assertServiceDefinitionWritable(current.capability);
			const expected = new Map(initial.fingerprint);
			if (firstUnitPublication && !initial.sourcePath && current.sourcePath === unit) {
				for (const [file, fingerprint] of current.fingerprint) if (current.shared.has(file) && !expected.has(file)) expected.set(file, fingerprint);
			}
			if (unchanged && !isDeepStrictEqual(current.fingerprint, expected)) throw new Error("Managed service artifacts changed during publication.");
			initial = current;
		};
		await refresh();
		if (!isDeepStrictEqual(await canonicalTargets(), lockedTargets)) throw new Error("Managed service lock targets changed during acquisition.");
		const allowed = /* @__PURE__ */ new Set([
			unit,
			generated,
			`${unit}.bak`
		]);
		const publications = /* @__PURE__ */ new Map();
		const publish = async (file, contents, mode, rollback = true) => {
			if (!allowed.has(file)) throw new Error("Not a managed service publication target.");
			await refresh(true);
			const previous = initial.snapshots.get(file) ?? null;
			const directory = await promises.realpath(path.dirname(file));
			const temporary = path.join(directory, `${path.basename(file)}.${randomUUID()}.tmp`);
			try {
				await promises.writeFile(temporary, contents, {
					flag: "wx",
					mode
				});
				const written = await promises.lstat(temporary);
				await refresh(true);
				await promises.rename(temporary, file);
				const published = identity(written, Buffer.from(contents));
				initial.fingerprint.set(file, published);
				publications.set(file, published);
				try {
					await refresh(true, file === unit && previous === null);
				} catch (error) {
					if (rollback) await restore(file, previous);
					throw error;
				}
			} finally {
				await promises.unlink(temporary).catch(() => void 0);
			}
		};
		const restore = async (file, snapshot) => {
			if (!allowed.has(file) && snapshot) throw new Error("Not a managed service publication target.");
			const published = publications.get(file);
			if (published === void 0) return;
			const current = await inspect$1(env, environment);
			if (current.capability.kind !== "writable" || current.fingerprint.get(file) !== published) return;
			initial = current;
			if (snapshot) await publish(file, snapshot.contents, snapshot.mode, false);
			else {
				await refresh(true);
				await promises.unlink(file);
				initial.fingerprint.set(file, "missing");
				await refresh(true);
			}
			publications.delete(file);
		};
		return await run({
			snapshots: initial.snapshots,
			publish,
			restore
		});
	};
	const lockOptions = {
		stale: 6e4,
		retries: {
			retries: 100,
			factor: 1,
			minTimeout: 50,
			maxTimeout: 100
		}
	};
	const acquire = async (index) => index === targets.length ? execute() : withFileLock(targets[index], lockOptions, () => acquire(index + 1));
	return await acquire(0);
}
//#endregion
//#region src/daemon/systemd-install.ts
/** systemd unit publication, installation, staging, and uninstall. */
function collectSystemdInlineManagedKeys(params) {
	const keys = readManagedServiceEnvKeysFromEnvironment(params.environment);
	for (const key of collectSystemdFileManagedKeys(params.environmentValueSources)) keys.delete(key);
	for (const [rawKey, value] of Object.entries(params.environment ?? {})) {
		if (typeof value !== "string" || !value.trim() && rawKey !== "NODE_OPTIONS") continue;
		const key = normalizeServiceEnvKey(rawKey);
		if (!key) continue;
		const source = readEnvironmentValueSource(params.environmentValueSources, rawKey);
		if (hasInlineEnvironmentSource(source) && !hasEnvironmentFileSource(source)) keys.add(key);
	}
	return keys;
}
function collectSystemdFileManagedKeys(environmentValueSources) {
	return normalizeServiceEnvKeys(Object.entries(environmentValueSources ?? {}).filter(([, source]) => isEnvironmentFileOnlySource(source)).map(([key]) => key));
}
function collectSystemdFileBackedEnvironment(params) {
	const environment = {};
	for (const [rawKey, rawValue] of Object.entries(params.environment ?? {})) {
		if (typeof rawValue !== "string" || !rawValue.trim()) continue;
		const key = normalizeServiceEnvKey(rawKey);
		if (key && params.fileManagedKeys.has(key) && !isUnresolvedShellReference(rawValue)) environment[rawKey] = rawValue;
	}
	return environment;
}
function sanitizeSystemdUnitBackupContent(params) {
	if (params.fileManagedKeys.size === 0) return params.content;
	const sanitizedLines = [];
	for (const rawLine of params.content.split("\n")) {
		const line = rawLine.trim();
		if (!line.startsWith("Environment=")) {
			sanitizedLines.push(rawLine);
			continue;
		}
		const assignments = parseSystemdEnvAssignments(line.slice(12).trim());
		const keptAssignments = assignments.filter(({ key }) => {
			const normalizedKey = normalizeServiceEnvKey(key);
			return !normalizedKey || !params.fileManagedKeys.has(normalizedKey);
		});
		if (keptAssignments.length === assignments.length) {
			sanitizedLines.push(rawLine);
			continue;
		}
		if (keptAssignments.length === 0) continue;
		const leadingWhitespace = rawLine.match(/^\s*/)?.[0] ?? "";
		sanitizedLines.push(`${leadingWhitespace}Environment=${keptAssignments.map(({ key, value }) => renderSystemdEnvAssignment(key, value)).join(" ")}`);
	}
	return sanitizedLines.join("\n");
}
async function writeSystemdUnit({ env, programArguments, workingDirectory, environment, environmentValueSources, description }) {
	await assertSystemdAvailable(env);
	await assertNoSystemGatewayOwnership(env);
	const unitPath = resolveSystemdUnitPath(env);
	return await withSystemdDefinitionMutation(env, environment ?? env, async (mutation) => {
		const priorManagedKeys = readManagedServiceEnvKeysFromEnvironment(resolveManagedGatewayServiceCommand(await readSystemdServiceExecStart(env))?.environment);
		const stateDir = resolveStateDir({
			...env,
			...environment
		});
		const environmentFilePath = resolveSystemdEnvironmentFilePath({
			stateDir,
			environment
		});
		const environmentFileSnapshot = isNodeSystemdEnvironment(env) ? void 0 : mutation.snapshots.get(environmentFilePath) ?? null;
		const existingUnit = mutation.snapshots.get(unitPath) ?? null;
		const { entries: stateDirDotEnvEntries, skippedShellReferenceKeys } = readStateDirDotEnvFromStateDir(stateDir);
		const stateDirDotEnvVars = new Map(Object.entries(stateDirDotEnvEntries).filter(([key, value]) => {
			const inlineValue = environment?.[key];
			return typeof inlineValue !== "string" || inlineValue.trim() === value.trim();
		}));
		const inlineManagedKeys = collectSystemdInlineManagedKeys({
			environment,
			environmentValueSources
		});
		const fileManagedKeys = collectSystemdFileManagedKeys(environmentValueSources);
		const existingEnvironment = await readSystemdGatewayEnvironmentFiles(stateDir, environment);
		if (existingUnit) await mutation.publish(`${unitPath}.bak`, sanitizeSystemdUnitBackupContent({
			content: existingUnit.contents.toString("utf8"),
			fileManagedKeys
		}), existingUnit.mode || 384);
		try {
			const incoming = collectSystemdFileBackedEnvironment({
				environment,
				fileManagedKeys
			});
			for (const [key, value] of Object.entries(incoming)) if (/[\r\n]/.test(value)) throw new Error(`state-dir .env contains a multiline value for ${key}; systemd EnvironmentFile values must be single-line`);
			const managedKeysToDrop = normalizeServiceEnvKeys([
				...inlineManagedKeys,
				...fileManagedKeys,
				...priorManagedKeys,
				...stateDirDotEnvVars.keys(),
				...skippedShellReferenceKeys
			]);
			const { existing, literalShellReferenceKeys } = existingEnvironment;
			const merged = {
				...Object.fromEntries(Object.entries(existing).filter(([key, value]) => {
					const normalized = normalizeServiceEnvKey(key);
					if (normalized && managedKeysToDrop.has(normalized)) return false;
					return literalShellReferenceKeys.has(key) || !isUnresolvedShellReference(value);
				})),
				...incoming
			};
			const hasGeneratedValues = Object.keys(merged).length > 0;
			const environmentKeys = normalizeServiceEnvKeys(Object.keys(merged));
			if (hasGeneratedValues || mutation.snapshots.has(environmentFilePath)) {
				const content = hasGeneratedValues ? `${serializeSystemdEnvironmentFile(merged)}\n` : "";
				await mutation.publish(environmentFilePath, content, 384);
			}
			const environmentSansDotEnvEntries = Object.fromEntries(Object.entries(environment ?? {}).filter(([key, value]) => {
				if (typeof value !== "string") return false;
				const source = readEnvironmentValueSource(environmentValueSources, key);
				const normalized = normalizeServiceEnvKey(key);
				const generated = normalized && environmentKeys.has(normalized) && !inlineManagedKeys.has(normalized);
				return !(hasEnvironmentFileSource(source) && isUnresolvedShellReference(value)) && !generated && value.trim() !== stateDirDotEnvVars.get(key)?.trim();
			}));
			const unit = buildSystemdUnit({
				description: resolveGatewayServiceDescription({
					env,
					description
				}),
				programArguments,
				workingDirectory,
				environment: environmentSansDotEnvEntries,
				environmentFiles: hasGeneratedValues ? [environmentFilePath] : []
			});
			await assertNoSystemGatewayOwnership(env);
			await mutation.publish(unitPath, unit, existingUnit?.mode ?? 420);
			try {
				await assertNoSystemGatewayOwnership(env);
			} catch (ownershipError) {
				await mutation.restore(unitPath, existingUnit);
				throw ownershipError;
			}
		} catch (error) {
			if (environmentFileSnapshot !== void 0) await mutation.restore(environmentFilePath, environmentFileSnapshot);
			throw error;
		}
		return {
			unitPath,
			backedUp: existingUnit !== null
		};
	});
}
async function readSystemdGatewayEnvironmentFiles(stateDir, environment) {
	const existing = {};
	const literalShellReferenceKeys = /* @__PURE__ */ new Set();
	for (const sourcePath of [resolveLegacyNodeSystemdEnvironmentFilePath({
		stateDir,
		environment
	}), resolveSystemdEnvironmentFilePath({
		stateDir,
		environment
	})]) {
		if (!sourcePath) continue;
		try {
			const fromFile = await readSystemdEnvironmentFile(sourcePath);
			for (const [key, value] of Object.entries(fromFile.environment)) {
				existing[key] = value;
				literalShellReferenceKeys.delete(key);
				if (fromFile.literalShellReferenceKeys.has(key)) literalShellReferenceKeys.add(key);
			}
		} catch (error) {
			if (!hasErrnoCode(error, "ENOENT")) throw error;
		}
	}
	return {
		existing,
		literalShellReferenceKeys
	};
}
async function removeNodeSystemdManagedEnvironmentKeys(env) {
	if (!isNodeSystemdEnvironment(env)) return;
	const envFilePath = resolveSystemdEnvironmentFilePath({
		stateDir: resolveStateDir(env),
		environment: env
	});
	let existingFile;
	try {
		existingFile = await readSystemdEnvironmentFile(envFilePath);
	} catch {
		return;
	}
	const managedKeys = /* @__PURE__ */ new Set(["OPENCLAW_GATEWAY_TOKEN", "OPENCLAW_GATEWAY_PASSWORD"]);
	const remaining = Object.fromEntries(Object.entries(existingFile.environment).filter(([key, value]) => {
		const normalized = normalizeServiceEnvKey(key);
		if (normalized && managedKeys.has(normalized)) return false;
		return existingFile.literalShellReferenceKeys.has(key) || !isUnresolvedShellReference(value);
	}));
	if (Object.keys(remaining).length === 0) {
		await fs$1.rm(envFilePath, { force: true });
		return;
	}
	const content = serializeSystemdEnvironmentFile(remaining);
	await fs$1.writeFile(envFilePath, `${content}\n`, {
		encoding: "utf8",
		mode: 384
	});
	await fs$1.chmod(envFilePath, 384);
}
function reportSystemdServicePublication(stdout, label, unitPath, backedUp) {
	const lines = [{
		label,
		value: unitPath
	}];
	if (backedUp) lines.push({
		label: "Previous unit backed up to",
		value: `${unitPath}.bak`
	});
	writeFormattedLines(stdout, lines, { leadingBlankLine: true });
}
async function stageSystemdService({ stdout, ...args }) {
	const { unitPath, backedUp } = await writeSystemdUnit(args);
	reportSystemdServicePublication(stdout, "Staged systemd service", unitPath, backedUp);
	return { unitPath };
}
async function activateSystemdService(params) {
	const unitName = `${resolveSystemdServiceName(params.env)}.service`;
	await assertNoSystemGatewayOwnership(params.env);
	const runActivation = async (action, retryMissing = true) => {
		const args = action === "daemon-reload" ? [action] : [action, unitName];
		const result = await execSystemctlUser(params.env, args);
		if (result.code === 0) return;
		const detail = readSystemctlDetail(result);
		if (action !== "daemon-reload" && retryMissing && result.termination === "exit" && isSystemdUnitMissingDetail(detail)) {
			await runActivation("daemon-reload");
			return await runActivation(action, false);
		}
		if (isSystemdUserScopeUnavailable(detail)) throw new Error(`systemctl --user unavailable: ${detail || "unknown error"}`.trim());
		throw new Error(`systemctl ${action} failed: ${detail || "unknown error"}`.trim());
	};
	for (const action of [
		"daemon-reload",
		"enable",
		"restart"
	]) await runActivation(action);
}
async function installSystemdService(args) {
	const { unitPath, backedUp } = await writeSystemdUnit(args);
	await activateSystemdService({ env: args.env });
	if (args.warn && hasGatewayServiceLauncherOverride(await readSystemdServiceExecStart(args.env).catch(() => null))) args.warn("Systemd drop-in overrides the managed service command or working directory; inspect, update, or remove the drop-in because reinstalling the base unit does not change the effective launcher.");
	reportSystemdServicePublication(args.stdout, "Installed systemd service", unitPath, backedUp);
	return { unitPath };
}
async function uninstallSystemdService({ env, stdout }) {
	await assertSystemdAvailable(env);
	await disableSystemdUserUnitForRemoval(env, `${resolveSystemdServiceName(env)}.service`);
	const unitPath = resolveSystemdUnitPath(env);
	let removed = false;
	try {
		await fs$1.unlink(unitPath);
		removed = true;
	} catch (error) {
		if (error.code !== "ENOENT") throw error;
	}
	await removeNodeSystemdManagedEnvironmentKeys(env);
	if (removed) stdout.write(`${formatLine("Removed systemd service", unitPath)}\n`);
	else stdout.write(`Systemd service not found at ${unitPath}\n`);
}
//#endregion
//#region src/daemon/systemd-lifecycle.ts
/** systemd start, stop, restart, and obsolete-unit removal. */
function isRunningAsRoot() {
	if (typeof process.geteuid === "function") try {
		return process.geteuid() === 0;
	} catch {
		return false;
	}
	return false;
}
async function runSystemdServiceAction(params) {
	const env = params.env ?? process.env;
	const installed = await findInstalledSystemdGatewayScope(env);
	const unitName = installed?.unitName ?? `${resolveSystemdServiceName(env)}.service`;
	let runSystemctl;
	if (installed?.scope === "system") {
		if (!isRunningAsRoot()) throw new Error(`${unitName} is a system-scope unit (${installed.unitPath}); run \`sudo systemctl ${params.action} ${unitName}\` to ${params.action} it`);
		runSystemctl = (args) => execSystemctl(args, env);
	} else {
		await assertSystemdAvailable(env);
		if (params.action !== "stop") await assertNoSystemGatewayOwnership(env);
		runSystemctl = (args) => execSystemctlUser(env, args);
	}
	if (params.action !== "stop") await runSystemctl(["reset-failed", unitName]);
	const res = await runSystemctl([params.action, unitName]);
	if (res.code !== 0) throw new Error(`systemctl ${params.action} failed: ${res.stderr || res.stdout}`.trim());
	params.onMutation?.();
	params.stdout.write(`${formatLine(params.label, unitName)}\n`);
}
async function startSystemdService({ stdout, env, onMutation }) {
	const reportMutation = createGatewayLifecycleMutationReporter(onMutation);
	await runSystemdServiceAction({
		stdout,
		env,
		action: "start",
		label: "Started systemd service",
		onMutation: () => reportMutation("systemctl-start")
	});
}
async function stopSystemdService({ stdout, env, onMutation }) {
	const reportMutation = createGatewayLifecycleMutationReporter(onMutation);
	await runSystemdServiceAction({
		stdout,
		env,
		action: "stop",
		label: "Stopped systemd service",
		onMutation: () => reportMutation("systemctl-stop")
	});
}
async function restartSystemdService({ stdout, env, onMutation }) {
	const reportMutation = createGatewayLifecycleMutationReporter(onMutation);
	await runSystemdServiceAction({
		stdout,
		env,
		action: "restart",
		label: "Restarted systemd service",
		onMutation: () => reportMutation("systemctl-restart")
	});
	return { outcome: "completed" };
}
async function findLegacySystemdUnits(env) {
	const results = [];
	const systemctlAvailable = await isSystemctlAvailable(env);
	for (const name of LEGACY_GATEWAY_SYSTEMD_SERVICE_NAMES) {
		const unitPath = resolveSystemdUnitPathForName(env, name);
		let exists = false;
		try {
			await fs$1.access(unitPath);
			exists = true;
		} catch {}
		let enabled = false;
		if (systemctlAvailable) enabled = (await execSystemctlUser(env, ["is-enabled", `${name}.service`])).code === 0;
		if (exists || enabled) results.push({
			name,
			unitPath,
			enabled,
			exists
		});
	}
	return results;
}
async function uninstallLegacySystemdUnits({ env, stdout }) {
	const units = await findLegacySystemdUnits(env);
	if (units.length === 0) return units;
	const systemctlAvailable = await isSystemctlAvailable(env);
	let removedAny = false;
	for (const unit of units) {
		if (systemctlAvailable) await disableSystemdUserUnitForRemoval(env, `${unit.name}.service`);
		else stdout.write(`systemctl unavailable; removed legacy unit file only: ${unit.name}.service\n`);
		try {
			await fs$1.unlink(unit.unitPath);
			removedAny = true;
			stdout.write(`${formatLine("Removed legacy systemd service", unit.unitPath)}\n`);
		} catch (error) {
			if (error.code !== "ENOENT") throw error;
			stdout.write(`Legacy systemd unit not found at ${unit.unitPath}\n`);
		}
	}
	if (systemctlAvailable && removedAny) await reloadSystemdUserManager(env);
	return units;
}
/**
* Removes the canonical *user-scope* gateway unit, leaving any system-scope
* unit untouched. Used by doctor to resolve a `dueling` installation by
* dropping the redundant user-scope leftover (issue #79375). Removing a unit
* under `$HOME` needs no root, unlike the system-scope unit.
*/
async function uninstallUserSystemdGatewayUnit({ env, stdout }) {
	const unitName = `${resolveSystemdServiceName(env)}.service`;
	const unitPath = resolveSystemdUnitPath(env);
	let disabled = false;
	if (await isSystemctlAvailable(env)) {
		await disableSystemdUserUnitForRemoval(env, unitName);
		disabled = true;
	} else stdout.write(`systemctl unavailable; removing unit file only: ${unitName}. A loaded unit keeps running until systemd reloads.\n`);
	let removed = false;
	try {
		await fs$1.unlink(unitPath);
		removed = true;
		stdout.write(`${formatLine("Removed user-scope systemd service", unitPath)}\n`);
	} catch (error) {
		if (error.code !== "ENOENT") throw error;
		stdout.write(`User-scope systemd unit not found at ${unitPath}\n`);
	}
	if (removed && disabled) await reloadSystemdUserManager(env);
	return {
		unitName,
		unitPath,
		removed,
		disabled
	};
}
//#endregion
//#region src/daemon/systemd-linger.ts
/** Reads and enables systemd user linger for headless daemon sessions. */
function resolveLoginctlUser(env) {
	const fromEnv = normalizeOptionalString(env.USER) || normalizeOptionalString(env.LOGNAME);
	if (fromEnv) return fromEnv;
	try {
		return os.userInfo().username;
	} catch {
		return null;
	}
}
/** Reads systemd user linger status through loginctl when available. */
async function readSystemdUserLingerStatus(params) {
	const user = params.user ?? resolveLoginctlUser(params.env);
	if (!user) return null;
	try {
		const { stdout } = await runExec("loginctl", [
			"show-user",
			user,
			"-p",
			"Linger"
		], { timeoutMs: 5e3 });
		const value = normalizeOptionalLowercaseString(stdout.split("\n").map((entry) => entry.trim()).find((entry) => entry.startsWith("Linger="))?.split("=")[1]);
		if (value === "yes" || value === "no") return {
			user,
			linger: value
		};
	} catch {}
	return null;
}
/** Enables systemd user linger through loginctl, with optional sudo mode. */
async function enableSystemdUserLinger(params) {
	const user = params.user ?? resolveLoginctlUser(params.env);
	if (!user) return {
		ok: false,
		stdout: "",
		stderr: "Missing user",
		code: 1
	};
	const argv = [
		...(typeof process.getuid === "function" ? process.getuid() !== 0 : true) && params.sudoMode !== void 0 ? ["sudo", ...params.sudoMode === "non-interactive" ? ["-n"] : []] : [],
		"loginctl",
		"enable-linger",
		user
	];
	try {
		const result = await runCommandWithTimeout(argv, { timeoutMs: 3e4 });
		return {
			ok: result.code === 0,
			stdout: result.stdout,
			stderr: result.stderr,
			code: result.code ?? 1
		};
	} catch (error) {
		return {
			ok: false,
			stdout: "",
			stderr: formatErrorMessage(error),
			code: 1
		};
	}
}
//#endregion
//#region src/daemon/systemd-runtime.ts
/** systemd service enabled-state and runtime inspection. */
function parseSystemdShow(output) {
	const entries = parseKeyValueOutput(output, "=");
	const info = {};
	const activeState = entries.activestate;
	if (activeState) info.activeState = activeState;
	const subState = entries.substate;
	if (subState) info.subState = subState;
	const mainPidValue = entries.mainpid;
	if (mainPidValue) {
		const pid = parseStrictPositiveInteger(mainPidValue);
		if (pid !== void 0) info.mainPid = pid;
	}
	const execMainStatusValue = entries.execmainstatus;
	if (execMainStatusValue) {
		const status = parseStrictInteger(execMainStatusValue);
		if (status !== void 0) info.execMainStatus = status;
	}
	const execMainCode = entries.execmaincode;
	if (execMainCode) info.execMainCode = execMainCode;
	const result = entries.result;
	if (result) info.result = result;
	const nRestartsValue = entries.nrestarts;
	if (nRestartsValue) {
		const nRestarts = parseStrictInteger(nRestartsValue);
		if (nRestarts !== void 0) info.nRestarts = nRestarts;
	}
	const startLimitBurstValue = entries.startlimitburst;
	if (startLimitBurstValue) {
		const startLimitBurst = parseStrictInteger(startLimitBurstValue);
		if (startLimitBurst !== void 0) info.startLimitBurst = startLimitBurst;
	}
	const unit = entries.id;
	if (unit) info.unit = unit;
	const killMode = entries.killmode;
	if (killMode) info.killMode = killMode;
	const tasksCurrentValue = entries.taskscurrent;
	if (tasksCurrentValue) {
		const tasksCurrent = parseStrictNonNegativeInteger(tasksCurrentValue);
		if (tasksCurrent !== void 0) info.tasksCurrent = tasksCurrent;
	}
	const memoryCurrentValue = entries.memorycurrent;
	if (memoryCurrentValue) {
		const memoryCurrent = parseStrictNonNegativeInteger(memoryCurrentValue);
		if (memoryCurrent !== void 0) info.memoryCurrent = memoryCurrent;
	}
	return info;
}
async function isSystemdServiceEnabled(args) {
	const env = args.env ?? process.env;
	const installed = await findInstalledSystemdGatewayScope(env);
	if (!installed) return false;
	const res = installed.scope === "system" ? await execSystemctl(["is-enabled", installed.unitName], env, args.timeoutMs) : await execSystemctlUser(env, ["is-enabled", installed.unitName], args.timeoutMs);
	if (res.code === 0) return true;
	const detail = readSystemctlDetail(res);
	if (res.termination === "exit" && !isSystemctlMissing(res) && isSystemdUnitNotEnabled(detail)) return false;
	throw new Error(`systemctl is-enabled unavailable: ${detail || "unknown error"}`.trim());
}
async function readSystemdServiceRuntime(env = process.env, opts) {
	const timeoutMs = opts?.timeoutMs;
	const installed = await findInstalledSystemdGatewayScope(env).catch(() => null);
	if (installed?.scope !== "system") try {
		await assertSystemdAvailable(env, timeoutMs);
	} catch (err) {
		return {
			status: "unknown",
			detail: formatErrorMessage(err)
		};
	}
	const unitName = installed?.unitName ?? `${resolveSystemdServiceName(env)}.service`;
	const showArgs = [
		"show",
		unitName,
		"--no-page",
		"--property",
		"Id,ActiveState,SubState,Result,NRestarts,StartLimitBurst,MainPID,ExecMainStatus,ExecMainCode,KillMode,TasksCurrent,MemoryCurrent"
	];
	const res = installed?.scope === "system" ? await execSystemctl(showArgs, env, timeoutMs) : await execSystemctlUser(env, showArgs, timeoutMs);
	if (res.code !== 0) {
		const detail = (res.stderr || res.stdout).trim();
		const missing = res.termination === "exit" && !installed && isSystemdUnitMissingDetail(detail);
		return {
			status: missing ? "stopped" : "unknown",
			...!missing && detail ? { detail } : {},
			missingUnit: missing
		};
	}
	const parsed = parseSystemdShow(res.stdout || "");
	const activeState = normalizeLowercaseStringOrEmpty(parsed.activeState);
	return {
		status: activeState === "active" ? "running" : activeState ? "stopped" : "unknown",
		state: parsed.activeState,
		subState: parsed.subState,
		pid: parsed.mainPid,
		lastExitStatus: parsed.execMainStatus,
		lastExitReason: parsed.execMainCode,
		systemd: {
			unit: parsed.unit ?? unitName,
			killMode: parsed.killMode,
			tasksCurrent: parsed.tasksCurrent,
			memoryCurrent: parsed.memoryCurrent,
			result: parsed.result,
			nRestarts: parsed.nRestarts,
			startLimitBurst: parsed.startLimitBurst
		}
	};
}
//#endregion
export { restartSystemdService as a, uninstallLegacySystemdUnits as c, stageSystemdService as d, uninstallSystemdService as f, readSystemdUserLingerStatus as i, uninstallUserSystemdGatewayUnit as l, readSystemdServiceRuntime as n, startSystemdService as o, readSystemdDefinitionMutationCapability as p, enableSystemdUserLinger as r, stopSystemdService as s, isSystemdServiceEnabled as t, installSystemdService as u };
