Files
fireclaw/src/snapshot.ts

74 lines
2.0 KiB
TypeScript

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);
}
}