Initial commit — fireclaw multi-agent system
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>
This commit is contained in:
98
src/rootfs.ts
Normal file
98
src/rootfs.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user