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

173 lines
4.8 KiB
TypeScript
Raw Permalink Normal View History

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