import { chmod, lstat, mkdir, unlink } from "node:fs/promises"; import path from "node:path"; import { acquireBridgeLock } from "./instance-lock.js"; import { startUnixSocketServer } from "./unix-server.js"; const RUNTIME_DIRECTORY_MODE = 0o700; export async function createRuntimePaths( runtimeDir = process.env.XDG_RUNTIME_DIR, ) { if (!runtimeDir || !path.isAbsolute(runtimeDir)) { throw new Error("XDG_RUNTIME_DIR must be an absolute path"); } const directory = path.join(runtimeDir, "pi-status-bridge"); await mkdir(directory, { recursive: true, mode: RUNTIME_DIRECTORY_MODE }); await chmod(directory, RUNTIME_DIRECTORY_MODE); return { directory, socketPath: path.join(directory, "bridge.sock"), lockPath: path.join(directory, "bridge.lock"), }; } async function removeStaleSocket(socketPath) { try { const socket = await lstat(socketPath); if (!socket.isSocket()) throw new Error(`refusing to remove non-socket path: ${socketPath}`); await unlink(socketPath); } catch (error) { if (error?.code !== "ENOENT") throw error; } } export async function startBridgeServer({ handleRequest, subscribe, runtimeDir, processAlive, maxFrameBytes, }) { const paths = await createRuntimePaths(runtimeDir); const lock = await acquireBridgeLock({ lockPath: paths.lockPath, socketPath: paths.socketPath, ...(processAlive ? { processAlive } : {}), }); try { await removeStaleSocket(paths.socketPath); const socketServer = await startUnixSocketServer({ socketPath: paths.socketPath, handleRequest, ...(subscribe ? { subscribe } : {}), ...(maxFrameBytes ? { maxFrameBytes } : {}), }); return { ...paths, async close() { await socketServer.close(); await lock.release(); }, }; } catch (error) { await lock.release(); throw error; } }