/** * ISO Builder Orchestrator * Coordinates all ISO building operations */ import { log } from '../logging.ts'; import { path } from '../plugins.ts'; import { ensureDir } from '../paths.ts'; import type { IIsoConfig } from '../interfaces/iso-config.interface.ts'; import { IsoCache } from './iso-cache.ts'; import { IsoDownloader } from './iso-downloader.ts'; import { IsoExtractor } from './iso-extractor.ts'; import { IsoPacker } from './iso-packer.ts'; import { CloudInitGenerator } from './cloud-init-generator.ts'; export class IsoBuilder { private cache: IsoCache; private extractor: IsoExtractor; private packer: IsoPacker; private cloudInitGen: CloudInitGenerator; constructor() { this.cache = new IsoCache(); this.extractor = new IsoExtractor(); this.packer = new IsoPacker(); this.cloudInitGen = new CloudInitGenerator(); } /** * Build a customized ISO from configuration */ async build(config: IIsoConfig, onProgress?: (step: string, progress: number) => void): Promise { log.info('Starting ISO build process...'); // Step 1: Check dependencies onProgress?.('Checking dependencies', 10); await this.extractor.ensureDependencies(); await this.packer.ensureDependencies(); // Step 2: Get or download base ISO onProgress?.('Obtaining base ISO', 20); const baseIsoPath = await this.getBaseIso(config.iso.ubuntu_version, config.iso.architecture); // Step 3: Extract ISO onProgress?.('Extracting ISO', 40); const extractedDir = await this.extractor.extract(baseIsoPath); // Step 4: Generate cloud-init configuration onProgress?.('Generating cloud-init configuration', 60); const cloudInitDir = path.join(extractedDir, 'nocloud'); await ensureDir(cloudInitDir); if (config.cloud_init) { const networkConfig = config.network ? { wifi: config.network.wifi, } : undefined; await this.cloudInitGen.writeCloudInitFiles( cloudInitDir, config.cloud_init, networkConfig, ); } // Step 5: Inject custom boot scripts (if any) if (config.boot_scripts && config.boot_scripts.length > 0) { onProgress?.('Injecting boot scripts', 70); await this.injectBootScripts(extractedDir, config.boot_scripts); } // Step 6: Update grub configuration to use cloud-init onProgress?.('Updating boot configuration', 80); await this.updateBootConfig(extractedDir); // Step 7: Repack ISO onProgress?.('Creating customized ISO', 90); const outputPath = path.join(config.output.path, config.output.filename); await ensureDir(config.output.path); await this.packer.pack(extractedDir, outputPath, 'UBUNTU_CUSTOM'); // Step 8: Cleanup onProgress?.('Cleaning up', 95); await Deno.remove(extractedDir, { recursive: true }); onProgress?.('Complete', 100); log.success(`ISO built successfully: ${outputPath}`); } /** * Get base ISO (from cache or download) */ private async getBaseIso(version: string, arch: 'amd64' | 'arm64'): Promise { // Check cache first const cachedPath = await this.cache.getPath(version, arch); if (cachedPath) { log.info(`Using cached ISO: ${cachedPath}`); return cachedPath; } // Download to cache log.info(`Downloading Ubuntu ${version} ${arch}...`); return await this.cache.downloadAndCache(version, arch, (downloaded, total) => { const percent = ((downloaded / total) * 100).toFixed(1); process.stdout.write(`\r📥 Download progress: ${percent}%`); }); } /** * Inject custom boot scripts */ private async injectBootScripts( extractedDir: string, bootScripts: Array<{ name: string; path: string; enable?: boolean }>, ): Promise { for (const script of bootScripts) { log.info(`Injecting boot script: ${script.name}`); // Copy script to ISO const destPath = path.join(extractedDir, 'scripts', script.name); await ensureDir(path.dirname(destPath)); await Deno.copyFile(script.path, destPath); // Make executable await Deno.chmod(destPath, 0o755); // TODO: Create systemd service if enable is true } } /** * Update boot configuration to enable cloud-init with nocloud datasource */ private async updateBootConfig(extractedDir: string): Promise { // Update grub config to add cloud-init nocloud datasource const grubCfgPath = path.join(extractedDir, 'boot', 'grub', 'grub.cfg'); try { let grubContent = await Deno.readTextFile(grubCfgPath); // Add cloud-init datasource parameter grubContent = grubContent.replace( /linux\s+\/casper\/vmlinuz/g, 'linux /casper/vmlinuz ds=nocloud;s=/cdrom/nocloud/', ); await Deno.writeTextFile(grubCfgPath, grubContent); log.success('Updated boot configuration for cloud-init'); } catch (err) { log.warn(`Could not update grub config: ${err instanceof Error ? err.message : String(err)}`); } } }