Files
isocreator/ts/classes/iso-builder.ts

154 lines
4.9 KiB
TypeScript
Raw Permalink Normal View History

/**
* 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<void> {
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<string> {
// 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<void> {
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<void> {
// 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)}`);
}
}
}