74 lines
2.0 KiB
TypeScript
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);
|
|
}
|
|
}
|