/** * ISO Downloader * Downloads Ubuntu Server ISOs from official mirrors */ import { log } from '../logging.ts'; import { path } from '../plugins.ts'; export interface IDownloadOptions { ubuntuVersion: string; // e.g., "24.04", "22.04" architecture: 'amd64' | 'arm64'; outputPath: string; onProgress?: (downloaded: number, total: number) => void; } export class IsoDownloader { /** * Ubuntu release URLs */ private static readonly UBUNTU_MIRROR = 'https://releases.ubuntu.com'; /** * Get the ISO filename for a given version and architecture */ static getIsoFilename(version: string, arch: 'amd64' | 'arm64'): string { // Ubuntu Server ISO naming pattern if (arch === 'amd64') { return `ubuntu-${version}-live-server-${arch}.iso`; } else { // ARM64 uses preinstalled server images return `ubuntu-${version}-preinstalled-server-${arch}.img.xz`; } } /** * Get the download URL for a given Ubuntu version and architecture */ static getDownloadUrl(version: string, arch: 'amd64' | 'arm64'): string { const filename = this.getIsoFilename(version, arch); return `${this.UBUNTU_MIRROR}/${version}/${filename}`; } /** * Get the checksum URL for verification */ static getChecksumUrl(version: string): string { return `${this.UBUNTU_MIRROR}/${version}/SHA256SUMS`; } /** * Download an Ubuntu ISO */ async download(options: IDownloadOptions): Promise { const { ubuntuVersion, architecture, outputPath, onProgress } = options; const url = IsoDownloader.getDownloadUrl(ubuntuVersion, architecture); log.info(`Downloading Ubuntu ${ubuntuVersion} ${architecture} from ${url}`); // Download the ISO const response = await fetch(url); if (!response.ok) { throw new Error(`Failed to download ISO: HTTP ${response.status}`); } const totalSize = parseInt(response.headers.get('content-length') || '0', 10); let downloadedSize = 0; // Open file for writing const file = await Deno.open(outputPath, { write: true, create: true, truncate: true }); try { const reader = response.body?.getReader(); if (!reader) { throw new Error('Failed to get response body reader'); } while (true) { const { done, value } = await reader.read(); if (done) break; await file.write(value); downloadedSize += value.length; if (onProgress && totalSize > 0) { onProgress(downloadedSize, totalSize); } } log.success(`ISO downloaded successfully to ${outputPath}`); } finally { file.close(); } } /** * Download and verify checksum */ async downloadWithVerification(options: IDownloadOptions): Promise { const { ubuntuVersion, architecture, outputPath } = options; // Download the ISO await this.download(options); // Download checksums log.info('Downloading checksums for verification...'); const checksumUrl = IsoDownloader.getChecksumUrl(ubuntuVersion); const checksumResponse = await fetch(checksumUrl); if (!checksumResponse.ok) { log.warn('Could not download checksums for verification'); return; } const checksumText = await checksumResponse.text(); const filename = path.basename(outputPath); // Find the checksum for our file const lines = checksumText.split('\n'); let expectedChecksum: string | null = null; for (const line of lines) { if (line.includes(filename)) { expectedChecksum = line.split(/\s+/)[0]; break; } } if (!expectedChecksum) { log.warn(`No checksum found for ${filename}`); return; } // Verify the checksum log.info('Verifying checksum...'); const actualChecksum = await this.calculateSha256(outputPath); if (actualChecksum === expectedChecksum) { log.success('Checksum verified successfully ✓'); } else { throw new Error(`Checksum mismatch!\nExpected: ${expectedChecksum}\nActual: ${actualChecksum}`); } } /** * Calculate SHA256 checksum of a file */ private async calculateSha256(filePath: string): Promise { const file = await Deno.open(filePath, { read: true }); const buffer = new Uint8Array(8192); const hasher = await crypto.subtle.digest('SHA-256', new Uint8Array(0)); // Initialize try { while (true) { const bytesRead = await file.read(buffer); if (bytesRead === null) break; // Hash the chunk const chunk = buffer.slice(0, bytesRead); await crypto.subtle.digest('SHA-256', chunk); } } finally { file.close(); } // Convert to hex string const hashArray = Array.from(new Uint8Array(hasher)); const hashHex = hashArray.map((b) => b.toString(16).padStart(2, '0')).join(''); return hashHex; } }