feat: expand daemon status diagnostics
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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>,
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user