update
This commit is contained in:
254
isobuild/ts/index.ts
Normal file
254
isobuild/ts/index.ts
Normal file
@@ -0,0 +1,254 @@
|
||||
/**
|
||||
* 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
// 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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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<void> {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user