@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
120 lines (119 loc) • 5.86 kB
JavaScript
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 });
}
}