service status on atproto
at main 2.6 kB view raw
1import { spawn } from "bun"; 2import P from "parsimmon"; 3import { err, ok, type Result } from "./utils"; 4 5interface SystemctlShowOutput { 6 [key: string]: string; 7} 8 9// Parsimmon parsers for systemctl output 10const newline = P.string("\n"); 11const equals = P.string("="); 12 13// Key: anything except = and newline 14const key = P.regexp(/[^=\n]+/).map((s) => s.trim()); 15 16// Single line value: everything until newline (or end of input) 17const singleLineValue = P.regexp(/[^\n]*/); 18 19// Continuation line: newline followed by whitespace and content 20const continuationLine = P.seq( 21 newline, 22 P.regexp(/[ \t]*/), // optional whitespace 23 P.regexp(/[^\n]*/), // content 24).map(([, , content]) => "\n" + content); 25 26// Multi-line value: first line + any continuation lines 27const multiLineValue = P.seq(singleLineValue, continuationLine.many()).map( 28 ([first, continuations]) => (first + continuations.join("")).trim(), 29); 30 31// Key-value pair: key = value 32const keyValuePair = P.seq(key, equals, multiLineValue).map(([k, , v]) => ({ 33 key: k, 34 value: v, 35})); 36 37// Empty line (just whitespace) 38const emptyLine = P.regexp(/[ \t]*/).result(null); 39 40// A line is either a key-value pair or empty line 41const line = P.alt(keyValuePair, emptyLine); 42 43// Complete systemctl output: lines separated by newlines, ending with optional newline 44const systemctlOutput = P.seq(line.sepBy(newline), P.alt(newline, P.eof)).map( 45 ([lines]) => 46 lines.filter((l): l is { key: string; value: string } => l !== null), 47); 48 49const parseSystemctlOutput = ( 50 output: string, 51): Result<SystemctlShowOutput, string> => { 52 const result = systemctlOutput.parse(output); 53 54 if (!result.status) { 55 return err( 56 `Parse error at position ${result.index.offset}: ${result.expected.join(", ")}`, 57 ); 58 } 59 60 const kvMap: SystemctlShowOutput = {}; 61 62 for (const { key, value } of result.value) { 63 if (value.length > 0) { 64 kvMap[key.toLowerCase()] = value; 65 } 66 } 67 68 return ok(kvMap); 69}; 70 71export const systemctlShow = async ( 72 serviceName: string, 73): Promise<Result<SystemctlShowOutput, string>> => { 74 try { 75 const proc = spawn(["systemctl", "show", `${serviceName}.service`], { 76 stdout: "pipe", 77 stderr: "pipe", 78 }); 79 80 const output = await new Response(proc.stdout).text(); 81 const exitCode = await proc.exited; 82 83 if (exitCode !== 0) { 84 const error = await new Response(proc.stderr).text(); 85 return err(`systemctl show failed with exit code ${exitCode}: ${error}`); 86 } 87 88 return parseSystemctlOutput(output); 89 } catch (error) { 90 return err(`failed to execute systemctl show: ${error}`); 91 } 92};