@phren/cli
Version:
Knowledge layer for AI agents - CLI, MCP server, and data layer
36 lines (35 loc) • 1.93 kB
JavaScript
import { execFile } from "node:child_process";
import { BridgeError } from "./protocol.js";
/**
* Finding a computer's sshd ed25519 host key. sshd keeps it in /etc/ssh on
* most systems, but Asustor ADM uses /usr/etc/ssh and Homebrew or Entware
* builds use their own prefix, so ask sshd first and search the usual
* directories after.
*/
export const HOST_KEY_DIRS = ["/etc/ssh", "/usr/etc/ssh", "/usr/local/etc/ssh", "/opt/etc/ssh"];
const quote = (value) => `'${value.replace(/'/g, `'\\''`)}'`;
/** A POSIX sh script that prints the first ssh-ed25519 host public key line,
* from sshd -T's hostkey entries then `dirs`; it prints nothing when there is none. */
export function hostKeyScript(dirs = HOST_KEY_DIRS, sshd = "sshd") {
const candidates = dirs.map(dir => quote(`${dir}/ssh_host_ed25519_key`)).join(" ");
return `keys=$( (PATH="$PATH:/usr/sbin:/sbin:/usr/local/sbin:/opt/sbin" ${quote(sshd)} -T 2>/dev/null) | awk '$1 == "hostkey" { print $2 }')
for k in $keys ${candidates}; do
[ -r "$k.pub" ] || continue
line=$(head -n 1 "$k.pub")
case "$line" in "ssh-ed25519 "*) printf '%s\\n' "$line"; exit 0;; esac
done
true`;
}
export function noHostKey(where, dirs = HOST_KEY_DIRS) {
return new BridgeError(409, `${where} has no ssh-ed25519 host key: none in sshd -T's hostkey entries or ${dirs.join(", ")}. Generate one with sudo ssh-keygen -A and restart sshd.`);
}
/** The first ed25519 host public key line out of hostKeyScript's output. */
export function hostKeyLine(stdout) {
return stdout.split("\n").map(line => line.trim()).find(line => line.startsWith("ssh-ed25519 "));
}
/** This computer's ed25519 host public key line, or undefined when it has none. */
export function localHostKeyLine(dirs, sshd) {
return new Promise(resolve => {
execFile("sh", ["-c", hostKeyScript(dirs, sshd)], { timeout: 10_000 }, (error, stdout) => resolve(hostKeyLine(stdout ?? "")));
});
}