From 3fb8b14e417f84a166a91c18af1f45b3b8a9a99e Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Thu, 8 Jan 2026 16:31:57 +0000 Subject: [PATCH] refactor: extract shared runCommand utility and cleanup codebase - Create shared command utility in ts/utils/command.ts - Update index.ts, process-manager.ts, system-info.ts to use shared utility - Remove duplicate runCommand implementations from all files - Remove legacy Chrome wrapper methods (startChrome, stopChrome, isChromeRunning) - Consolidate Sway window selectors from 5 to 2 - Remove unused isobuild TypeScript files (mod.ts, deno.json, ts/index.ts) - Make getStatus() async to properly await system info - Add disk and system info sections to UI --- ecoos_daemon/ts/daemon/index.ts | 36 +-- ecoos_daemon/ts/daemon/process-manager.ts | 53 +--- ecoos_daemon/ts/daemon/system-info.ts | 37 +-- ecoos_daemon/ts/ui/server.ts | 30 ++- ecoos_daemon/ts/utils/command.ts | 27 ++ .../includes.chroot/opt/eco/bin/eco-daemon | Bin 86907168 -> 86907671 bytes isobuild/deno.json | 18 -- isobuild/mod.ts | 70 ----- isobuild/ts/index.ts | 254 ------------------ 9 files changed, 87 insertions(+), 438 deletions(-) create mode 100644 ecoos_daemon/ts/utils/command.ts delete mode 100644 isobuild/deno.json delete mode 100644 isobuild/mod.ts delete mode 100644 isobuild/ts/index.ts diff --git a/ecoos_daemon/ts/daemon/index.ts b/ecoos_daemon/ts/daemon/index.ts index 28ca9be..6b6c010 100644 --- a/ecoos_daemon/ts/daemon/index.ts +++ b/ecoos_daemon/ts/daemon/index.ts @@ -7,6 +7,7 @@ import { ProcessManager } from './process-manager.ts'; import { SystemInfo } from './system-info.ts'; import { UIServer } from '../ui/server.ts'; +import { runCommand } from '../utils/command.ts'; export interface DaemonConfig { uiPort: number; @@ -60,13 +61,14 @@ export class EcoDaemon { return [...this.logs]; } - getStatus(): Record { + async getStatus(): Promise> { + const systemInfo = await this.systemInfo.getInfo(); return { sway: this.swayStatus.state === 'running', swayStatus: this.swayStatus, chromium: this.chromiumStatus.state === 'running', chromiumStatus: this.chromiumStatus, - systemInfo: this.systemInfo.getInfo(), + systemInfo, logs: this.logs.slice(-50), }; } @@ -166,14 +168,14 @@ export class EcoDaemon { } private async ensureSeatd(): Promise { - const status = await this.runCommand('systemctl', ['is-active', 'seatd']); + const status = await runCommand('systemctl', ['is-active', 'seatd']); if (status.success && status.stdout.trim() === 'active') { this.log('seatd is already running'); return; } this.log('Starting seatd service...'); - const result = await this.runCommand('systemctl', ['start', 'seatd']); + const result = await runCommand('systemctl', ['start', 'seatd']); if (!result.success) { this.log('Warning: Failed to start seatd: ' + result.stderr); } @@ -184,9 +186,9 @@ export class EcoDaemon { // Ensure XDG_RUNTIME_DIR exists const runtimeDir = `/run/user/${uid}`; - await this.runCommand('mkdir', ['-p', runtimeDir]); - await this.runCommand('chown', [`${this.config.user}:${this.config.user}`, runtimeDir]); - await this.runCommand('chmod', ['700', runtimeDir]); + await runCommand('mkdir', ['-p', runtimeDir]); + await runCommand('chown', [`${this.config.user}:${this.config.user}`, runtimeDir]); + await runCommand('chmod', ['700', runtimeDir]); if (mode === 'drm') { this.log('Starting Sway with DRM backend (hardware rendering)'); @@ -243,31 +245,13 @@ export class EcoDaemon { } private async getUserUid(): Promise { - const result = await this.runCommand('id', ['-u', this.config.user]); + const result = await runCommand('id', ['-u', this.config.user]); if (!result.success) { throw new Error('Failed to get user UID: ' + result.stderr); } return parseInt(result.stdout.trim(), 10); } - private async runCommand( - cmd: string, - args: string[] - ): Promise<{ success: boolean; stdout: string; stderr: string }> { - const command = new Deno.Command(cmd, { - args, - stdout: 'piped', - stderr: 'piped', - }); - - const result = await command.output(); - return { - success: result.success, - stdout: new TextDecoder().decode(result.stdout), - stderr: new TextDecoder().decode(result.stderr), - }; - } - private async runForever(): Promise { // Monitor processes and restart if needed while (true) { diff --git a/ecoos_daemon/ts/daemon/process-manager.ts b/ecoos_daemon/ts/daemon/process-manager.ts index 6f394be..dd59388 100644 --- a/ecoos_daemon/ts/daemon/process-manager.ts +++ b/ecoos_daemon/ts/daemon/process-manager.ts @@ -4,6 +4,8 @@ * Manages spawning and monitoring of Sway and Chromium processes */ +import { runCommand } from '../utils/command.ts'; + export interface SwayConfig { runtimeDir: string; backends: string; @@ -18,6 +20,7 @@ export interface BrowserConfig { runtimeDir: string; waylandDisplay: string; url: string; + kiosk?: boolean; } export class ProcessManager { @@ -70,13 +73,7 @@ focus_follows_mouse yes # Force all windows fullscreen for kiosk mode for_window [app_id=".*"] fullscreen enable - -# Chromium-specific fullscreen rules for_window [app_id="chromium-browser"] fullscreen enable -for_window [app_id="Chromium-browser"] fullscreen enable -for_window [app_id="chromium"] fullscreen enable -for_window [class="Chromium-browser"] fullscreen enable -for_window [class="chromium-browser"] fullscreen enable `; } @@ -88,37 +85,19 @@ for_window [class="chromium-browser"] fullscreen enable const configPath = `${configDir}/config`; // Create config directory - await this.runCommand('mkdir', ['-p', configDir]); - await this.runCommand('chown', [`${this.user}:${this.user}`, `/home/${this.user}/.config`]); - await this.runCommand('chown', [`${this.user}:${this.user}`, configDir]); + await runCommand('mkdir', ['-p', configDir]); + await runCommand('chown', [`${this.user}:${this.user}`, `/home/${this.user}/.config`]); + await runCommand('chown', [`${this.user}:${this.user}`, configDir]); // Write config file const configContent = this.generateSwayConfig(config); await Deno.writeTextFile(configPath, configContent); - await this.runCommand('chown', [`${this.user}:${this.user}`, configPath]); + await runCommand('chown', [`${this.user}:${this.user}`, configPath]); console.log(`[sway] Config written to ${configPath}`); return configPath; } - private async runCommand( - cmd: string, - args: string[] - ): Promise<{ success: boolean; stdout: string; stderr: string }> { - const command = new Deno.Command(cmd, { - args, - stdout: 'piped', - stderr: 'piped', - }); - - const result = await command.output(); - return { - success: result.success, - stdout: new TextDecoder().decode(result.stdout), - stderr: new TextDecoder().decode(result.stderr), - }; - } - async startSway(config: SwayConfig): Promise { // Write sway config before starting const configPath = await this.writeSwayConfig(config); @@ -287,10 +266,7 @@ for_window [class="chromium-browser"] fullscreen enable // Try multiple selectors to ensure we catch the window const selectors = [ '[app_id="chromium-browser"]', - '[app_id="Chromium-browser"]', '[app_id="chromium"]', - '[class="Chromium-browser"]', - '[class="chromium-browser"]', ]; for (const selector of selectors) { @@ -301,11 +277,6 @@ for_window [class="chromium-browser"] fullscreen enable await this.swaymsg(config, '[app_id="chromium-browser"] focus'); } - // Legacy method name for backwards compatibility - async startChrome(config: BrowserConfig & { kiosk?: boolean }): Promise { - return this.startBrowser(config); - } - isSwayRunning(): boolean { return this.swayProcess !== null; } @@ -314,11 +285,6 @@ for_window [class="chromium-browser"] fullscreen enable return this.browserProcess !== null; } - // Legacy method name for backwards compatibility - isChromeRunning(): boolean { - return this.isBrowserRunning(); - } - async stopSway(): Promise { if (this.swayProcess) { try { @@ -343,11 +309,6 @@ for_window [class="chromium-browser"] fullscreen enable } } - // Legacy method name for backwards compatibility - async stopChrome(): Promise { - return this.stopBrowser(); - } - private async pipeOutput( process: Deno.ChildProcess, name: string diff --git a/ecoos_daemon/ts/daemon/system-info.ts b/ecoos_daemon/ts/daemon/system-info.ts index 264467d..a29e681 100644 --- a/ecoos_daemon/ts/daemon/system-info.ts +++ b/ecoos_daemon/ts/daemon/system-info.ts @@ -4,6 +4,8 @@ * Gathers CPU, RAM, disk, network, and GPU information */ +import { runCommand } from '../utils/command.ts'; + export interface CpuInfo { model: string; cores: number; @@ -66,8 +68,9 @@ export class SystemInfo { private async getHostname(): Promise { try { - const result = await this.runCommand('hostname'); - return result.trim(); + const result = await runCommand('hostname', []); + if (!result.success) return 'unknown'; + return result.stdout.trim(); } catch { return 'unknown'; } @@ -120,8 +123,9 @@ export class SystemInfo { private async getDiskInfo(): Promise { try { - const output = await this.runCommand('df', ['-B1', '--output=source,target,size,used,avail']); - const lines = output.trim().split('\n').slice(1); + const result = await runCommand('df', ['-B1', '--output=source,target,size,used,avail']); + if (!result.success) return []; + const lines = result.stdout.trim().split('\n').slice(1); const disks: DiskInfo[] = []; for (const line of lines) { @@ -150,8 +154,9 @@ export class SystemInfo { private async getNetworkInfo(): Promise { try { - const output = await this.runCommand('ip', ['-j', 'addr']); - const interfaces = JSON.parse(output); + const cmdResult = await runCommand('ip', ['-j', 'addr']); + if (!cmdResult.success) return []; + const interfaces = JSON.parse(cmdResult.stdout); const result: NetworkInterface[] = []; for (const iface of interfaces) { @@ -183,8 +188,9 @@ export class SystemInfo { private async getGpuInfo(): Promise { try { - const output = await this.runCommand('lspci', ['-mm']); - const lines = output.split('\n'); + const result = await runCommand('lspci', ['-mm']); + if (!result.success) return []; + const lines = result.stdout.split('\n'); const gpus: GpuInfo[] = []; for (const line of lines) { @@ -213,19 +219,4 @@ export class SystemInfo { return 0; } } - - private async runCommand(cmd: string, args: string[] = []): Promise { - const command = new Deno.Command(cmd, { - args, - stdout: 'piped', - stderr: 'piped', - }); - - const result = await command.output(); - if (!result.success) { - throw new Error('Command failed'); - } - - return new TextDecoder().decode(result.stdout); - } } diff --git a/ecoos_daemon/ts/ui/server.ts b/ecoos_daemon/ts/ui/server.ts index 88d1e98..999eb39 100644 --- a/ecoos_daemon/ts/ui/server.ts +++ b/ecoos_daemon/ts/ui/server.ts @@ -84,7 +84,7 @@ export class UIServer { }; if (path === '/api/status') { - const status = this.daemon.getStatus(); + const status = await this.daemon.getStatus(); return new Response(JSON.stringify(status), { headers }); } @@ -233,6 +233,25 @@ export class UIServer {

Network

+
+

Disks

+
+
+
+

System

+
+
Hostname
+
-
+
+
+
Uptime
+
-
+
+
+
GPU
+
-
+
+

Logs

@@ -248,6 +267,15 @@ export class UIServer { return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i]; } + function formatUptime(seconds) { + const days = Math.floor(seconds / 86400); + const hours = Math.floor((seconds % 86400) / 3600); + const mins = Math.floor((seconds % 3600) / 60); + if (days > 0) return days + 'd ' + hours + 'h ' + mins + 'm'; + if (hours > 0) return hours + 'h ' + mins + 'm'; + return mins + 'm'; + } + function updateStatus(data) { // Services document.getElementById('sway-status').className = diff --git a/ecoos_daemon/ts/utils/command.ts b/ecoos_daemon/ts/utils/command.ts new file mode 100644 index 0000000..a3b8e88 --- /dev/null +++ b/ecoos_daemon/ts/utils/command.ts @@ -0,0 +1,27 @@ +/** + * Shared command execution utility + */ + +export interface CommandResult { + success: boolean; + stdout: string; + stderr: string; +} + +export async function runCommand( + cmd: string, + args: string[] +): Promise { + const command = new Deno.Command(cmd, { + args, + stdout: 'piped', + stderr: 'piped', + }); + + const result = await command.output(); + return { + success: result.success, + stdout: new TextDecoder().decode(result.stdout), + stderr: new TextDecoder().decode(result.stderr), + }; +} diff --git a/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon b/isobuild/config/includes.chroot/opt/eco/bin/eco-daemon index caca84613bdd510371137f344a89b2b956040206..6e1797c123d6a718942c137b61abd3bfb074ae27 100755 GIT binary patch delta 7756 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 Rdh1=O>#g_Ncv&y|{TFjr(2D>7 diff --git a/isobuild/deno.json b/isobuild/deno.json deleted file mode 100644 index 4a0996b..0000000 --- a/isobuild/deno.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "@ecobridge/eco-os-isobuild", - "version": "0.0.1", - "exports": "./mod.ts", - "tasks": { - "build": "deno run --allow-all mod.ts build", - "clean": "deno run --allow-all mod.ts clean", - "test-qemu": "deno run --allow-all mod.ts test-qemu" - }, - "imports": { - "@std/fs": "jsr:@std/fs@^1.0.0", - "@std/path": "jsr:@std/path@^1.0.0", - "@std/async": "jsr:@std/async@^1.0.0" - }, - "compilerOptions": { - "strict": true - } -} diff --git a/isobuild/mod.ts b/isobuild/mod.ts deleted file mode 100644 index 0172bb2..0000000 --- a/isobuild/mod.ts +++ /dev/null @@ -1,70 +0,0 @@ -/** - * EcoOS ISO Builder - * - * CLI for building custom Ubuntu ISO with EcoOS daemon - */ - -import { build } from './ts/index.ts'; - -const command = Deno.args[0]; - -switch (command) { - case 'build': - await build(); - break; - - case 'clean': - console.log('Cleaning build artifacts...'); - try { - await Deno.remove('./output', { recursive: true }); - await Deno.remove('./build', { recursive: true }); - console.log('Clean complete.'); - } catch { - console.log('Nothing to clean.'); - } - break; - - case 'test-qemu': - console.log('Testing ISO in QEMU...'); - const isoPath = './output/ecoos.iso'; - try { - await Deno.stat(isoPath); - } catch { - console.error('ISO not found. Run "deno task build" first.'); - Deno.exit(1); - } - - const qemu = new Deno.Command('qemu-system-x86_64', { - args: [ - '-enable-kvm', - '-m', '4G', - '-cpu', 'host', - '-smp', '2', - '-cdrom', isoPath, - '-boot', 'd', - '-vga', 'virtio', - '-display', 'gtk', - '-device', 'usb-tablet', - '-nic', 'user,hostfwd=tcp::3006-:3006', - ], - stdout: 'inherit', - stderr: 'inherit', - }); - await qemu.spawn().status; - break; - - default: - console.log(` -EcoOS ISO Builder - -Usage: - deno task build Build the ISO - deno task clean Clean build artifacts - deno task test-qemu Test ISO in QEMU - -Requirements: - - Ubuntu 24.04+ host - - live-build, debootstrap, xorriso installed - - sudo access for live-build -`); -} diff --git a/isobuild/ts/index.ts b/isobuild/ts/index.ts deleted file mode 100644 index 0e03bc6..0000000 --- a/isobuild/ts/index.ts +++ /dev/null @@ -1,254 +0,0 @@ -/** - * 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); - } - } -}