import * as plugins from './plugins.js'; import type { ISmartVMOptions, IMicroVMConfig } from './interfaces/index.js'; import { SmartVMError } from './interfaces/index.js'; import { ImageManager } from './classes.imagemanager.js'; import { NetworkManager } from './classes.networkmanager.js'; import { MicroVM } from './classes.microvm.js'; /** * Top-level orchestrator for creating and managing Firecracker MicroVMs. */ export class SmartVM { private options: ISmartVMOptions; public imageManager: ImageManager; public networkManager: NetworkManager; private activeVMs: Map = new Map(); private smartExitInstance: InstanceType; private firecrackerVersion: string | null = null; private firecrackerBinaryPath: string | null = null; constructor(options: ISmartVMOptions = {}) { this.options = { dataDir: options.dataDir || '/tmp/.smartvm', arch: options.arch || 'x86_64', bridgeName: options.bridgeName || 'svbr0', subnet: options.subnet || '172.30.0.0/24', ...options, }; this.imageManager = new ImageManager(this.options.dataDir!, this.options.arch); this.networkManager = new NetworkManager({ bridgeName: this.options.bridgeName, subnet: this.options.subnet, }); // If a custom binary path is provided, use it directly if (this.options.firecrackerBinaryPath) { this.firecrackerBinaryPath = this.options.firecrackerBinaryPath; } // Register global cleanup this.smartExitInstance = new plugins.smartexit.SmartExit({ silent: true }); this.smartExitInstance.addCleanupFunction(async () => { await this.cleanup(); }); } /** * Ensure the Firecracker binary is available. * Downloads it if not present. */ public async ensureBinary(): Promise { // If custom binary path is set, just verify it exists if (this.firecrackerBinaryPath) { try { await plugins.fs.promises.access(this.firecrackerBinaryPath); return this.firecrackerBinaryPath; } catch { // File doesn't exist, fall through to error } throw new SmartVMError( `Firecracker binary not found at ${this.firecrackerBinaryPath}`, 'BINARY_NOT_FOUND', ); } // Ensure data directories exist await this.imageManager.ensureDirectories(); // Determine version let version = this.options.firecrackerVersion; if (!version) { version = await this.imageManager.getLatestVersion(); } this.firecrackerVersion = version; // Check if binary exists if (await this.imageManager.hasBinary(version)) { this.firecrackerBinaryPath = this.imageManager.getFirecrackerPath(version); return this.firecrackerBinaryPath; } // Download the binary this.firecrackerBinaryPath = await this.imageManager.downloadFirecracker(version); return this.firecrackerBinaryPath; } /** * Create a new MicroVM with the given configuration. * Returns the MicroVM instance (not yet started). */ public async createVM(config: IMicroVMConfig): Promise { // Ensure binary is available if (!this.firecrackerBinaryPath) { await this.ensureBinary(); } // Generate VM ID if not provided const vmId = config.id || plugins.smartunique.uuid4(); // Generate socket path const socketPath = this.imageManager.getSocketPath(vmId); // Create MicroVM instance const vm = new MicroVM( vmId, config, this.firecrackerBinaryPath!, socketPath, this.networkManager, ); // Register in active VMs this.activeVMs.set(vmId, vm); return vm; } /** * Get an active VM by ID. */ public getVM(id: string): MicroVM | undefined { return this.activeVMs.get(id); } /** * List all active VM IDs. */ public listVMs(): string[] { return Array.from(this.activeVMs.keys()); } /** * Get the number of active VMs. */ public get vmCount(): number { return this.activeVMs.size; } /** * Stop all running VMs. */ public async stopAll(): Promise { const stopPromises: Promise[] = []; for (const vm of this.activeVMs.values()) { if (vm.state === 'running' || vm.state === 'paused') { stopPromises.push( vm.stop().catch((err) => { console.error(`Failed to stop VM ${vm.id}: ${err.message}`); }), ); } } await Promise.all(stopPromises); } /** * Clean up all VMs and networking resources. */ public async cleanup(): Promise { // Clean up all VMs const cleanupPromises: Promise[] = []; for (const vm of this.activeVMs.values()) { cleanupPromises.push( vm.cleanup().catch((err) => { console.error(`Failed to clean up VM ${vm.id}: ${err.message}`); }), ); } await Promise.all(cleanupPromises); this.activeVMs.clear(); // Clean up networking await this.networkManager.cleanup(); } /** * Remove a VM from the active list (after it's been cleaned up). */ public removeVM(id: string): boolean { return this.activeVMs.delete(id); } }