import { type ChildProcess } from "node:child_process"; import { existsSync, mkdirSync, copyFileSync } from "node:fs"; import { join } from "node:path"; import { CONFIG } from "./config.js"; import * as api from "./firecracker-api.js"; import { deleteTap } from "./network.js"; import { ensureBaseImage, ensureSshKeypair, injectSshKey } from "./rootfs.js"; import { waitForSsh } from "./ssh.js"; import { setupNetwork, spawnFirecracker, bootVM, killFirecracker, } from "./firecracker-vm.js"; function log(msg: string) { process.stderr.write(`[snapshot] ${msg}\n`); } export function snapshotExists(): boolean { return ( existsSync(CONFIG.snapshot.statePath) && existsSync(CONFIG.snapshot.memPath) && existsSync(CONFIG.snapshot.rootfsPath) ); } export async function createSnapshot() { ensureBaseImage(); ensureSshKeypair(); const snap = CONFIG.snapshot; const socketPath = join(CONFIG.socketDir, "snapshot.sock"); log("Preparing snapshot rootfs..."); mkdirSync(CONFIG.socketDir, { recursive: true }); copyFileSync(CONFIG.baseRootfs, snap.rootfsPath); injectSshKey(snap.rootfsPath); log("Setting up network..."); setupNetwork(snap.tapDevice); let proc: ChildProcess | null = null; try { log("Booting VM for snapshot..."); proc = await spawnFirecracker(socketPath); await bootVM({ socketPath, rootfsPath: snap.rootfsPath, tapDevice: snap.tapDevice, ip: snap.ip, octet: snap.octet, }); log("Waiting for SSH..."); await waitForSsh(snap.ip); log("Pausing VM..."); await api.patchVm(socketPath, "Paused"); log("Creating snapshot..."); await api.putSnapshotCreate(socketPath, snap.statePath, snap.memPath); log("Snapshot created successfully."); log(` State: ${snap.statePath}`); log(` Memory: ${snap.memPath}`); log(` Rootfs: ${snap.rootfsPath}`); } finally { await killFirecracker(proc, socketPath, "SIGKILL"); deleteTap(snap.tapDevice); } }