feat(bridge): establish local pi status bridge
Deliver the initial local bridge, Noctalia v4/v5 adapters, desktop client, service unit, tests, and implementation documentation for persistent Pi status and control.
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
export const runtimeDirectoryMode = RUNTIME_DIRECTORY_MODE;
|
||||
Reference in New Issue
Block a user