feat: expand daemon status diagnostics

This commit is contained in:
Peter Steinberger
2026-01-08 08:24:28 +01:00
parent 15dd6b65b6
commit a676e16fbb
7 changed files with 208 additions and 16 deletions

View File

@@ -39,7 +39,16 @@ export function resolveGatewayLogPaths(
stderrPath: string;
} {
const home = resolveHomeDir(env);
const logDir = path.join(home, ".clawdbot", "logs");
const stateOverride =
env.CLAWDBOT_STATE_DIR?.trim() || env.CLAWDIS_STATE_DIR?.trim();
const profile = env.CLAWDBOT_PROFILE?.trim();
const suffix =
profile && profile.toLowerCase() !== "default" ? `-${profile}` : "";
const defaultStateDir = path.join(home, `.clawdbot${suffix}`);
const stateDir = stateOverride
? resolveUserPathWithHome(stateOverride, home)
: defaultStateDir;
const logDir = path.join(stateDir, "logs");
return {
logDir,
stdoutPath: path.join(logDir, "gateway.log"),
@@ -47,6 +56,16 @@ export function resolveGatewayLogPaths(
};
}
function resolveUserPathWithHome(input: string, home: string): string {
const trimmed = input.trim();
if (!trimmed) return trimmed;
if (trimmed.startsWith("~")) {
const expanded = trimmed.replace(/^~(?=$|[\\/])/, home);
return path.resolve(expanded);
}
return path.resolve(trimmed);
}
function plistEscape(value: string): string {
return value
.replaceAll("&", "&")
@@ -88,7 +107,12 @@ function renderEnvDict(
export async function readLaunchAgentProgramArguments(
env: Record<string, string | undefined>,
): Promise<{ programArguments: string[]; workingDirectory?: string } | null> {
): Promise<{
programArguments: string[];
workingDirectory?: string;
environment?: Record<string, string>;
sourcePath?: string;
} | null> {
const plistPath = resolveLaunchAgentPlistPath(env);
try {
const plist = await fs.readFile(plistPath, "utf8");
@@ -105,9 +129,25 @@ export async function readLaunchAgentProgramArguments(
const workingDirectory = workingDirMatch
? plistUnescape(workingDirMatch[1] ?? "").trim()
: "";
const envMatch = plist.match(
/<key>EnvironmentVariables<\/key>\s*<dict>([\s\S]*?)<\/dict>/i,
);
const environment: Record<string, string> = {};
if (envMatch) {
for (const pair of envMatch[1].matchAll(
/<key>([\s\S]*?)<\/key>\s*<string>([\s\S]*?)<\/string>/gi,
)) {
const key = plistUnescape(pair[1] ?? "").trim();
if (!key) continue;
const value = plistUnescape(pair[2] ?? "").trim();
environment[key] = value;
}
}
return {
programArguments: args.filter(Boolean),
...(workingDirectory ? { workingDirectory } : {}),
...(Object.keys(environment).length > 0 ? { environment } : {}),
sourcePath: plistPath,
};
} catch {
return null;

View File

@@ -52,6 +52,8 @@ export type GatewayService = {
readCommand: (env: Record<string, string | undefined>) => Promise<{
programArguments: string[];
workingDirectory?: string;
environment?: Record<string, string>;
sourcePath?: string;
} | null>;
readRuntime: (
env: Record<string, string | undefined>,

View File

@@ -191,12 +191,18 @@ function parseSystemdExecStart(value: string): string[] {
export async function readSystemdServiceExecStart(
env: Record<string, string | undefined>,
): Promise<{ programArguments: string[]; workingDirectory?: string } | null> {
): Promise<{
programArguments: string[];
workingDirectory?: string;
environment?: Record<string, string>;
sourcePath?: string;
} | null> {
const unitPath = resolveSystemdUnitPath(env);
try {
const content = await fs.readFile(unitPath, "utf8");
let execStart = "";
let workingDirectory = "";
const environment: Record<string, string> = {};
for (const rawLine of content.split("\n")) {
const line = rawLine.trim();
if (!line || line.startsWith("#")) continue;
@@ -204,6 +210,10 @@ export async function readSystemdServiceExecStart(
execStart = line.slice("ExecStart=".length).trim();
} else if (line.startsWith("WorkingDirectory=")) {
workingDirectory = line.slice("WorkingDirectory=".length).trim();
} else if (line.startsWith("Environment=")) {
const raw = line.slice("Environment=".length).trim();
const parsed = parseSystemdEnvAssignment(raw);
if (parsed) environment[parsed.key] = parsed.value;
}
}
if (!execStart) return null;
@@ -211,12 +221,47 @@ export async function readSystemdServiceExecStart(
return {
programArguments,
...(workingDirectory ? { workingDirectory } : {}),
...(Object.keys(environment).length > 0 ? { environment } : {}),
sourcePath: unitPath,
};
} catch {
return null;
}
}
function parseSystemdEnvAssignment(
raw: string,
): { key: string; value: string } | null {
const trimmed = raw.trim();
if (!trimmed) return null;
const unquoted = (() => {
if (!(trimmed.startsWith('"') && trimmed.endsWith('"'))) 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;
const value = unquoted.slice(eq + 1);
return { key, value };
}
export type SystemdServiceInfo = {
activeState?: string;
subState?: string;