Firecracker microVM-based multi-agent system with IRC orchestration and local LLMs. Features: - Ephemeral command runner with VM snapshots (~1.1s) - Multi-agent orchestration via overseer IRC bot - 5 agent templates (worker, coder, researcher, quick, creative) - Tool access (shell + podman containers inside VMs) - Persistent workspace + memory system (MEMORY.md pattern) - Agent hot-reload (model/persona swap via SSH + SIGHUP) - Non-root agents, graceful shutdown, crash recovery - Agent-to-agent communication via IRC - DM support, /invite support - Systemd service, 20 regression tests Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
99 lines
2.2 KiB
TypeScript
99 lines
2.2 KiB
TypeScript
import { execFileSync } from "node:child_process";
|
|
import {
|
|
existsSync,
|
|
copyFileSync,
|
|
mkdirSync,
|
|
unlinkSync,
|
|
} from "node:fs";
|
|
import { join } from "node:path";
|
|
import { randomBytes } from "node:crypto";
|
|
import { CONFIG } from "./config.js";
|
|
|
|
export function ensureBaseImage() {
|
|
if (!existsSync(CONFIG.baseRootfs)) {
|
|
throw new Error(
|
|
`Base rootfs not found at ${CONFIG.baseRootfs}. Run 'fireclaw setup' first.`
|
|
);
|
|
}
|
|
if (!existsSync(CONFIG.kernelPath)) {
|
|
throw new Error(
|
|
`Kernel not found at ${CONFIG.kernelPath}. Run 'fireclaw setup' first.`
|
|
);
|
|
}
|
|
}
|
|
|
|
export function ensureSshKeypair() {
|
|
if (!existsSync(CONFIG.sshKeyPath)) {
|
|
execFileSync("ssh-keygen", [
|
|
"-t",
|
|
"ed25519",
|
|
"-f",
|
|
CONFIG.sshKeyPath,
|
|
"-N",
|
|
"",
|
|
"-C",
|
|
"fireclaw",
|
|
]);
|
|
}
|
|
}
|
|
|
|
export function createRunCopy(vmId: string): string {
|
|
mkdirSync(CONFIG.runsDir, { recursive: true });
|
|
const dest = join(CONFIG.runsDir, `${vmId}.ext4`);
|
|
copyFileSync(CONFIG.baseRootfs, dest);
|
|
return dest;
|
|
}
|
|
|
|
export function injectSshKey(rootfsPath: string) {
|
|
const mountPoint = `/tmp/fireclaw-mount-${randomBytes(4).toString("hex")}`;
|
|
mkdirSync(mountPoint, { recursive: true });
|
|
|
|
try {
|
|
execFileSync("sudo", ["mount", "-o", "loop", rootfsPath, mountPoint], {
|
|
stdio: "pipe",
|
|
});
|
|
|
|
execFileSync("sudo", ["mkdir", "-p", join(mountPoint, "root/.ssh")], {
|
|
stdio: "pipe",
|
|
});
|
|
execFileSync(
|
|
"sudo",
|
|
[
|
|
"cp",
|
|
CONFIG.sshPubKeyPath,
|
|
join(mountPoint, "root/.ssh/authorized_keys"),
|
|
],
|
|
{ stdio: "pipe" }
|
|
);
|
|
execFileSync(
|
|
"sudo",
|
|
["chmod", "600", join(mountPoint, "root/.ssh/authorized_keys")],
|
|
{ stdio: "pipe" }
|
|
);
|
|
execFileSync(
|
|
"sudo",
|
|
["chmod", "700", join(mountPoint, "root/.ssh")],
|
|
{ stdio: "pipe" }
|
|
);
|
|
} finally {
|
|
try {
|
|
execFileSync("sudo", ["umount", mountPoint], { stdio: "pipe" });
|
|
} catch {
|
|
// Best effort
|
|
}
|
|
try {
|
|
execFileSync("rmdir", [mountPoint], { stdio: "pipe" });
|
|
} catch {
|
|
// Best effort
|
|
}
|
|
}
|
|
}
|
|
|
|
export function deleteRunCopy(rootfsPath: string) {
|
|
try {
|
|
unlinkSync(rootfsPath);
|
|
} catch {
|
|
// Already gone
|
|
}
|
|
}
|