import { spawn } from "bun"; import P from "parsimmon"; import { err, ok, type Result } from "./utils"; interface SystemctlShowOutput { [key: string]: string; } // Parsimmon parsers for systemctl output const newline = P.string("\n"); const equals = P.string("="); // Key: anything except = and newline const key = P.regexp(/[^=\n]+/).map((s) => s.trim()); // Single line value: everything until newline (or end of input) const singleLineValue = P.regexp(/[^\n]*/); // Continuation line: newline followed by whitespace and content const continuationLine = P.seq( newline, P.regexp(/[ \t]*/), // optional whitespace P.regexp(/[^\n]*/), // content ).map(([, , content]) => "\n" + content); // Multi-line value: first line + any continuation lines const multiLineValue = P.seq(singleLineValue, continuationLine.many()).map( ([first, continuations]) => (first + continuations.join("")).trim(), ); // Key-value pair: key = value const keyValuePair = P.seq(key, equals, multiLineValue).map(([k, , v]) => ({ key: k, value: v, })); // Empty line (just whitespace) const emptyLine = P.regexp(/[ \t]*/).result(null); // A line is either a key-value pair or empty line const line = P.alt(keyValuePair, emptyLine); // Complete systemctl output: lines separated by newlines, ending with optional newline const systemctlOutput = P.seq(line.sepBy(newline), P.alt(newline, P.eof)).map( ([lines]) => lines.filter((l): l is { key: string; value: string } => l !== null), ); const parseSystemctlOutput = ( output: string, ): Result => { const result = systemctlOutput.parse(output); if (!result.status) { return err( `Parse error at position ${result.index.offset}: ${result.expected.join(", ")}`, ); } const kvMap: SystemctlShowOutput = {}; for (const { key, value } of result.value) { if (value.length > 0) { kvMap[key.toLowerCase()] = value; } } return ok(kvMap); }; export const systemctlShow = async ( serviceName: string, ): Promise> => { try { const proc = spawn(["systemctl", "show", `${serviceName}.service`], { stdout: "pipe", stderr: "pipe", }); const output = await new Response(proc.stdout).text(); const exitCode = await proc.exited; if (exitCode !== 0) { const error = await new Response(proc.stderr).text(); return err(`systemctl show failed with exit code ${exitCode}: ${error}`); } return parseSystemctlOutput(output); } catch (error) { return err(`failed to execute systemctl show: ${error}`); } };