/** * ISO Build Orchestration * * Builds the EcoOS custom Ubuntu ISO */ import * as path from '@std/path'; const ROOT = path.dirname(path.dirname(path.fromFileUrl(import.meta.url))); const DAEMON_DIR = path.join(path.dirname(ROOT), 'ecoos_daemon'); const BUILD_DIR = path.join(ROOT, 'build'); const OUTPUT_DIR = path.join(ROOT, 'output'); const CONFIG_DIR = path.join(ROOT, 'config'); export async function build(): Promise { console.log('=== EcoOS ISO Builder ===\n'); // Step 1: Check prerequisites console.log('[1/7] Checking prerequisites...'); await checkPrerequisites(); // Step 2: Bundle the daemon console.log('[2/7] Bundling ecoos_daemon...'); await bundleDaemon(); // Step 3: Prepare build directory console.log('[3/7] Preparing build directory...'); await prepareBuildDir(); // Step 4: Configure live-build console.log('[4/7] Configuring live-build...'); await configureLiveBuild(); // Step 5: Copy package lists console.log('[5/7] Copying package lists...'); await copyPackageLists(); // Step 6: Copy daemon and configs to chroot includes console.log('[6/7] Preparing chroot includes...'); await prepareChrootIncludes(); // Step 7: Build ISO console.log('[7/7] Building ISO (this may take a while)...'); await buildIso(); console.log('\n=== Build Complete ==='); console.log(`ISO: ${OUTPUT_DIR}/ecoos.iso`); } async function checkPrerequisites(): Promise { const commands = ['lb', 'debootstrap', 'xorriso']; for (const cmd of commands) { try { const result = await run('which', [cmd]); if (!result.success) { throw new Error(`${cmd} not found`); } } catch { console.error(`Missing prerequisite: ${cmd}`); console.error('Install with: sudo apt install live-build debootstrap xorriso'); Deno.exit(1); } } console.log(' All prerequisites found.'); } async function bundleDaemon(): Promise { // Compile the daemon to a single executable const bundleDir = path.join(DAEMON_DIR, 'bundle'); await Deno.mkdir(bundleDir, { recursive: true }); const result = await run('deno', [ 'compile', '--allow-all', '--output', path.join(bundleDir, 'eco-daemon'), path.join(DAEMON_DIR, 'mod.ts'), ], { cwd: DAEMON_DIR }); if (!result.success) { console.error('Failed to bundle daemon'); console.error(result.stderr); Deno.exit(1); } console.log(' Daemon bundled successfully.'); } async function prepareBuildDir(): Promise { // Clean and create build directory try { await Deno.remove(BUILD_DIR, { recursive: true }); } catch { // Directory may not exist } await Deno.mkdir(BUILD_DIR, { recursive: true }); console.log(' Build directory prepared.'); } async function configureLiveBuild(): Promise { // Initialize live-build config const result = await run('lb', ['config'], { cwd: BUILD_DIR }); if (!result.success) { console.error('Failed to initialize live-build'); console.error(result.stderr); Deno.exit(1); } // Copy our auto/config const autoDir = path.join(BUILD_DIR, 'auto'); await Deno.mkdir(autoDir, { recursive: true }); const configSrc = path.join(CONFIG_DIR, 'live-build', 'auto', 'config'); const configDst = path.join(autoDir, 'config'); await Deno.copyFile(configSrc, configDst); await Deno.chmod(configDst, 0o755); // Re-run lb config with our settings const result2 = await run('lb', ['config'], { cwd: BUILD_DIR }); if (!result2.success) { console.error('Failed to configure live-build'); Deno.exit(1); } console.log(' live-build configured.'); } async function copyPackageLists(): Promise { const srcDir = path.join(CONFIG_DIR, 'live-build', 'package-lists'); const dstDir = path.join(BUILD_DIR, 'config', 'package-lists'); await Deno.mkdir(dstDir, { recursive: true }); for await (const entry of Deno.readDir(srcDir)) { if (entry.isFile && entry.name.endsWith('.list.chroot')) { const src = path.join(srcDir, entry.name); const dst = path.join(dstDir, entry.name); await Deno.copyFile(src, dst); } } console.log(' Package lists copied.'); } async function prepareChrootIncludes(): Promise { const includesDir = path.join(BUILD_DIR, 'config', 'includes.chroot'); // Create directory structure await Deno.mkdir(path.join(includesDir, 'opt', 'eco', 'bin'), { recursive: true }); await Deno.mkdir(path.join(includesDir, 'opt', 'eco', 'daemon'), { recursive: true }); await Deno.mkdir(path.join(includesDir, 'etc', 'systemd', 'system'), { recursive: true }); // Copy bundled daemon const daemonSrc = path.join(DAEMON_DIR, 'bundle', 'eco-daemon'); const daemonDst = path.join(includesDir, 'opt', 'eco', 'bin', 'eco-daemon'); try { await Deno.copyFile(daemonSrc, daemonDst); await Deno.chmod(daemonDst, 0o755); } catch (e) { console.error('Failed to copy daemon bundle:', e); // Fall back to copying source files console.log(' Copying daemon source files instead...'); await copyDir(path.join(DAEMON_DIR, 'ts'), path.join(includesDir, 'opt', 'eco', 'daemon')); await Deno.copyFile( path.join(DAEMON_DIR, 'mod.ts'), path.join(includesDir, 'opt', 'eco', 'daemon', 'mod.ts') ); } // Copy systemd service const serviceSrc = path.join(CONFIG_DIR, 'systemd', 'eco-daemon.service'); const serviceDst = path.join(includesDir, 'etc', 'systemd', 'system', 'eco-daemon.service'); await Deno.copyFile(serviceSrc, serviceDst); // Copy autoinstall config const autoinstallDir = path.join(BUILD_DIR, 'config', 'includes.binary'); await Deno.mkdir(autoinstallDir, { recursive: true }); const userDataSrc = path.join(CONFIG_DIR, 'autoinstall', 'user-data'); const userDataDst = path.join(autoinstallDir, 'autoinstall', 'user-data'); await Deno.mkdir(path.dirname(userDataDst), { recursive: true }); await Deno.copyFile(userDataSrc, userDataDst); // Create empty meta-data file (required for cloud-init) await Deno.writeTextFile(path.join(path.dirname(userDataDst), 'meta-data'), ''); console.log(' Chroot includes prepared.'); } async function buildIso(): Promise { const result = await run('sudo', ['lb', 'build'], { cwd: BUILD_DIR, stdout: 'inherit', stderr: 'inherit', }); if (!result.success) { console.error('ISO build failed'); Deno.exit(1); } // Move ISO to output directory await Deno.mkdir(OUTPUT_DIR, { recursive: true }); for await (const entry of Deno.readDir(BUILD_DIR)) { if (entry.isFile && entry.name.endsWith('.iso')) { const src = path.join(BUILD_DIR, entry.name); const dst = path.join(OUTPUT_DIR, 'ecoos.iso'); await Deno.rename(src, dst); break; } } } async function run( cmd: string, args: string[], options: { cwd?: string; stdout?: 'piped' | 'inherit'; stderr?: 'piped' | 'inherit' } = {} ): Promise<{ success: boolean; stdout: string; stderr: string }> { const command = new Deno.Command(cmd, { args, cwd: options.cwd, stdout: options.stdout ?? 'piped', stderr: options.stderr ?? 'piped', }); const result = await command.output(); return { success: result.success, stdout: new TextDecoder().decode(result.stdout), stderr: new TextDecoder().decode(result.stderr), }; } async function copyDir(src: string, dst: string): Promise { await Deno.mkdir(dst, { recursive: true }); for await (const entry of Deno.readDir(src)) { const srcPath = path.join(src, entry.name); const dstPath = path.join(dst, entry.name); if (entry.isDirectory) { await copyDir(srcPath, dstPath); } else { await Deno.copyFile(srcPath, dstPath); } } }