Extract shared VM lifecycle helpers into firecracker-vm.ts

This commit is contained in:
2026-04-07 16:32:24 +00:00
parent a2cef20a89
commit 27cb6508dc
4 changed files with 221 additions and 266 deletions

View File

@@ -1,4 +1,3 @@
import { spawn } from "node:child_process";
import {
existsSync,
mkdirSync,
@@ -12,18 +11,18 @@ import { join } from "node:path";
import { execFileSync } from "node:child_process";
import { CONFIG } from "./config.js";
import {
ensureBridge,
ensureNat,
allocateIp,
releaseIp,
createTap,
deleteTap,
macFromOctet,
applyNetworkPolicy,
removeNetworkPolicy,
type NetworkPolicy,
} from "./network.js";
import * as api from "./firecracker-api.js";
import {
setupNetwork,
spawnFirecracker,
bootVM,
} from "./firecracker-vm.js";
export interface AgentInfo {
name: string;
@@ -201,24 +200,6 @@ function ensureWorkspace(agentName: string): string {
return imgPath;
}
function waitForSocket(socketPath: string): Promise<void> {
return new Promise((resolve, reject) => {
const deadline = Date.now() + 5_000;
const check = () => {
if (existsSync(socketPath)) {
setTimeout(resolve, 200);
return;
}
if (Date.now() > deadline) {
reject(new Error("Firecracker socket did not appear"));
return;
}
setTimeout(check, 50);
};
check();
});
}
export async function startAgent(
templateName: string,
overrides?: { name?: string; model?: string }
@@ -266,46 +247,18 @@ export async function startAgent(
const workspacePath = ensureWorkspace(name);
// Setup network
ensureBridge();
ensureNat();
deleteTap(tapDevice); // clean stale tap from previous run
createTap(tapDevice);
setupNetwork(tapDevice);
// Boot VM
const proc = spawn(
CONFIG.firecrackerBin,
["--api-sock", socketPath],
{ stdio: "pipe", detached: true }
);
proc.unref();
await waitForSocket(socketPath);
const bootArgs = [
"console=ttyS0",
"reboot=k",
"panic=1",
"pci=off",
"root=/dev/vda",
"rw",
`ip=${ip}::${CONFIG.bridge.gateway}:${CONFIG.bridge.netmask}::eth0:off`,
].join(" ");
await api.putBootSource(socketPath, CONFIG.kernelPath, bootArgs);
await api.putDrive(socketPath, "rootfs", rootfsPath);
await api.putDrive(socketPath, "workspace", workspacePath, false, false);
await api.putNetworkInterface(
const proc = await spawnFirecracker(socketPath, { detached: true });
await bootVM({
socketPath,
"eth0",
rootfsPath,
extraDrives: [{ id: "workspace", path: workspacePath }],
tapDevice,
macFromOctet(octet)
);
await api.putMachineConfig(
socketPath,
CONFIG.vm.vcpuCount,
CONFIG.vm.memSizeMib
);
await api.startInstance(socketPath);
ip,
octet,
});
// Apply network policy
const networkPolicy: NetworkPolicy = template.network ?? "full";