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:
117
src/setup.ts
Normal file
117
src/setup.ts
Normal file
@@ -0,0 +1,117 @@
|
||||
import { execFileSync, execSync } from "node:child_process";
|
||||
import { existsSync, mkdirSync } from "node:fs";
|
||||
import { CONFIG } from "./config.js";
|
||||
import { ensureBridge, ensureNat } from "./network.js";
|
||||
import { ensureSshKeypair } from "./rootfs.js";
|
||||
|
||||
function log(msg: string) {
|
||||
process.stderr.write(`[setup] ${msg}\n`);
|
||||
}
|
||||
|
||||
function download(url: string, dest: string) {
|
||||
execFileSync("curl", ["-fSL", "-o", dest, url], {
|
||||
stdio: ["pipe", "pipe", "inherit"],
|
||||
timeout: 300_000,
|
||||
});
|
||||
}
|
||||
|
||||
export async function runSetup() {
|
||||
log("Setting up fireclaw...");
|
||||
|
||||
// Create directories
|
||||
mkdirSync(CONFIG.baseDir, { recursive: true });
|
||||
mkdirSync(CONFIG.runsDir, { recursive: true });
|
||||
mkdirSync(CONFIG.socketDir, { recursive: true });
|
||||
|
||||
// Download kernel
|
||||
if (existsSync(CONFIG.kernelPath)) {
|
||||
log("Kernel already exists, skipping download.");
|
||||
} else {
|
||||
log("Downloading kernel...");
|
||||
download(CONFIG.assets.kernelUrl, CONFIG.kernelPath);
|
||||
log("Kernel downloaded.");
|
||||
}
|
||||
|
||||
// Download and convert rootfs
|
||||
if (existsSync(CONFIG.baseRootfs)) {
|
||||
log("Base rootfs already exists, skipping download.");
|
||||
} else {
|
||||
log("Downloading rootfs...");
|
||||
|
||||
// Find latest rootfs key from S3 listing
|
||||
const listing = execFileSync(
|
||||
"curl",
|
||||
["-fsSL", CONFIG.assets.rootfsListUrl],
|
||||
{ encoding: "utf-8", timeout: 30_000 }
|
||||
);
|
||||
const keys = [...listing.matchAll(/<Key>([^<]+)<\/Key>/g)].map(
|
||||
(m) => m[1]
|
||||
);
|
||||
const rootfsKey = keys.sort().pop();
|
||||
if (!rootfsKey) throw new Error("Could not find rootfs in S3 listing");
|
||||
|
||||
const squashfsPath = `${CONFIG.baseDir}/rootfs.squashfs`;
|
||||
download(`${CONFIG.assets.rootfsBaseUrl}/${rootfsKey}`, squashfsPath);
|
||||
log("Rootfs downloaded. Converting squashfs to ext4...");
|
||||
|
||||
// Convert squashfs to ext4
|
||||
const squashMount = "/tmp/fireclaw-squash";
|
||||
const ext4Mount = "/tmp/fireclaw-ext4";
|
||||
mkdirSync(squashMount, { recursive: true });
|
||||
mkdirSync(ext4Mount, { recursive: true });
|
||||
|
||||
try {
|
||||
execFileSync(
|
||||
"sudo",
|
||||
["mount", "-t", "squashfs", squashfsPath, squashMount],
|
||||
{ stdio: "pipe" }
|
||||
);
|
||||
execFileSync("truncate", ["-s", "1G", CONFIG.baseRootfs], {
|
||||
stdio: "pipe",
|
||||
});
|
||||
execFileSync("sudo", ["/usr/sbin/mkfs.ext4", CONFIG.baseRootfs], {
|
||||
stdio: "pipe",
|
||||
});
|
||||
execFileSync("sudo", ["mount", CONFIG.baseRootfs, ext4Mount], {
|
||||
stdio: "pipe",
|
||||
});
|
||||
execFileSync("sudo", ["cp", "-a", `${squashMount}/.`, ext4Mount], {
|
||||
stdio: "pipe",
|
||||
});
|
||||
|
||||
// Bake in DNS config
|
||||
execSync(
|
||||
`echo "nameserver 8.8.8.8" | sudo tee ${ext4Mount}/etc/resolv.conf > /dev/null`
|
||||
);
|
||||
|
||||
log("Rootfs converted.");
|
||||
} finally {
|
||||
try {
|
||||
execFileSync("sudo", ["umount", squashMount], { stdio: "pipe" });
|
||||
} catch {}
|
||||
try {
|
||||
execFileSync("sudo", ["umount", ext4Mount], { stdio: "pipe" });
|
||||
} catch {}
|
||||
try {
|
||||
execFileSync("rmdir", [squashMount], { stdio: "pipe" });
|
||||
} catch {}
|
||||
try {
|
||||
execFileSync("rmdir", [ext4Mount], { stdio: "pipe" });
|
||||
} catch {}
|
||||
try {
|
||||
execFileSync("rm", ["-f", squashfsPath], { stdio: "pipe" });
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
// Generate SSH keypair
|
||||
log("Ensuring SSH keypair...");
|
||||
ensureSshKeypair();
|
||||
|
||||
// Set up bridge and NAT
|
||||
log("Setting up network bridge...");
|
||||
ensureBridge();
|
||||
ensureNat();
|
||||
|
||||
log("Setup complete! Run 'fireclaw run \"uname -a\"' to test.");
|
||||
}
|
||||
Reference in New Issue
Block a user