UNPKG

@phren/cli

Version:

Knowledge layer for AI agents - CLI, MCP server, and data layer

120 lines (119 loc) • 5.86 kB
import { execFile } from "node:child_process"; import { chmod, lstat, mkdir, mkdtemp, readFile, rename, rm, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import path from "node:path"; import { promisify } from "node:util"; import { forcedCommand, peerForcedCommand } from "./install.js"; import { BridgeError, bridgeRoot, computerName } from "./protocol.js"; const exec = promisify(execFile); export { computerName }; export const dispatchKeyPath = (root = bridgeRoot()) => path.join(root, "id_ed25519_dispatch"); export function publicComputerKey(value) { const match = /^ssh-ed25519 ([A-Za-z0-9+/]+={0,2})(?: [^\r\n]*)?$/.exec(value.trim()); if (!match) throw new BridgeError(400, "Supply one ed25519 public key."); const blob = Buffer.from(match[1], "base64"); if (blob.length !== 51 || blob.readUInt32BE(0) !== 11 || blob.subarray(4, 15).toString() !== "ssh-ed25519" || blob.readUInt32BE(15) !== 32 || blob.toString("base64") !== match[1]) { throw new BridgeError(400, "Invalid ed25519 public key."); } return `ssh-ed25519 ${match[1]}`; } export function computerKeyLine(name, publicKey) { return `restrict,${peerForcedCommand.replace(" peer\"", ` peer:${computerName.parse(name)}\"`)} ${publicComputerKey(publicKey)} phren-computer:${name}`; } async function regularFile(file) { const info = await lstat(file).catch((error) => { if (error.code === "ENOENT") return undefined; throw error; }); if (!info) return false; if (!info.isFile() || info.isSymbolicLink()) throw new BridgeError(409, "Refusing an unexpected enrollment file."); return true; } /** Never replace a dispatch identity implicitly: every peer enrolled this key. */ export async function enrollComputer(name, root = bridgeRoot()) { computerName.parse(name); await mkdir(root, { recursive: true, mode: 0o700 }); await chmod(root, 0o700); const lock = path.join(root, "enroll-computer.lock"); try { await mkdir(lock, { mode: 0o700 }); } catch { throw new BridgeError(409, "Computer enrollment is already running; inspect enroll-computer.lock if interrupted."); } try { const key = dispatchKeyPath(root); if (!(await regularFile(key))) { const temporary = await mkdtemp(path.join(root, "enroll-")); try { const generated = path.join(temporary, "key"); await exec("ssh-keygen", ["-q", "-t", "ed25519", "-N", "", "-C", "phren-computer", "-f", generated], { timeout: 10_000 }); await rename(generated, key); } finally { await rm(temporary, { recursive: true, force: true }); } } await chmod(key, 0o600); const { stdout } = await exec("ssh-keygen", ["-y", "-P", "", "-f", key], { timeout: 10_000, maxBuffer: 4096 }); return computerKeyLine(name, stdout.trim()); } finally { await rm(lock, { recursive: true, force: true }); } } /** Accept a public key, rebuilding the restrictions rather than trusting supplied options. */ export async function acceptComputer(name, input, sshDirectory = path.join(homedir(), ".ssh")) { computerName.parse(name); const prefixes = [`restrict,${peerForcedCommand} `, `restrict,pty,${forcedCommand} `]; const raw = input.trim(); const namedPeer = /^restrict,command="sh ~\/\.local\/share\/phren\/bridge\/dispatch-scoped peer:[A-Za-z0-9_.-]+" /.exec(raw)?.[0]; const prefix = namedPeer ?? prefixes.find(value => raw.startsWith(value)); const publicKey = publicComputerKey(prefix ? raw.slice(prefix.length) : raw); const line = computerKeyLine(name, publicKey); const encoded = publicKey.split(" ")[1]; await appendAuthorizedKey(line, existing => existing.endsWith(` phren-computer:${name}`) || existing.split(/\s+/).includes(encoded), "This name or key is already enrolled differently. Revoke the old line explicitly first.", sshDirectory); } /** * Append one authorized_keys line under a lock, refusing when `conflicts` * matches an existing line and leaving an identical line in place. */ export async function appendAuthorizedKey(line, conflicts, conflictMessage, sshDirectory = path.join(homedir(), ".ssh")) { const directory = await lstat(sshDirectory).catch(() => undefined); if (directory && (!directory.isDirectory() || directory.isSymbolicLink())) throw new BridgeError(409, "Refusing an unexpected SSH directory."); await mkdir(sshDirectory, { recursive: true, mode: 0o700 }); await chmod(sshDirectory, 0o700); const lock = path.join(sshDirectory, ".phren-enroll.lock"); try { await mkdir(lock, { mode: 0o700 }); } catch { throw new BridgeError(409, "SSH enrollment is already running; inspect .phren-enroll.lock if interrupted."); } const file = path.join(sshDirectory, "authorized_keys"); const temporary = path.join(lock, "authorized_keys"); try { const before = await regularFile(file) ? await readFile(file, "utf8") : ""; const lines = before.split("\n").filter(Boolean); if (lines.includes(line)) { await chmod(file, 0o600); return; } if (lines.some(conflicts)) throw new BridgeError(409, conflictMessage); await writeFile(temporary, before + (before && !before.endsWith("\n") ? "\n" : "") + line + "\n", { mode: 0o600, flag: "wx" }); const current = await regularFile(file) ? await readFile(file, "utf8") : ""; if (current !== before) throw new BridgeError(409, "authorized_keys changed during enrollment. Try again."); await rename(temporary, file); } finally { await rm(lock, { recursive: true, force: true }); } }