This commit is contained in:
2026-01-09 09:41:47 +00:00
parent 51c83f846a
commit 5234411c9d
7 changed files with 71 additions and 33 deletions

View File

@@ -31,6 +31,7 @@ export class EcoDaemon {
private logs: string[] = []; private logs: string[] = [];
private swayStatus: ServiceStatus = { state: 'stopped' }; private swayStatus: ServiceStatus = { state: 'stopped' };
private chromiumStatus: ServiceStatus = { state: 'stopped' }; private chromiumStatus: ServiceStatus = { state: 'stopped' };
private manualRestartUntil: number = 0; // Timestamp until which auto-restart is disabled
constructor(config?: Partial<DaemonConfig>) { constructor(config?: Partial<DaemonConfig>) {
this.config = { this.config = {
@@ -94,6 +95,9 @@ export class EcoDaemon {
return { success: false, message: 'Cannot restart Chromium: Sway is not running' }; return { success: false, message: 'Cannot restart Chromium: Sway is not running' };
} }
// Disable auto-restart for 15 seconds to prevent restart loop
this.manualRestartUntil = Date.now() + 15000;
try { try {
// Stop existing Chromium // Stop existing Chromium
await this.processManager.stopBrowser(); await this.processManager.stopBrowser();
@@ -313,8 +317,9 @@ export class EcoDaemon {
} }
// If Sway is running but Chromium died, restart Chromium // If Sway is running but Chromium died, restart Chromium
// Skip if manual restart is in progress (prevents restart loop)
if (this.swayStatus.state === 'running' && this.chromiumStatus.state === 'running' if (this.swayStatus.state === 'running' && this.chromiumStatus.state === 'running'
&& !this.processManager.isBrowserRunning()) { && !(await this.processManager.isBrowserRunning()) && Date.now() > this.manualRestartUntil) {
this.log('Chromium process died, attempting restart...'); this.log('Chromium process died, attempting restart...');
this.chromiumStatus = { state: 'starting', lastAttempt: new Date().toISOString() }; this.chromiumStatus = { state: 'starting', lastAttempt: new Date().toISOString() };
try { try {

View File

@@ -193,33 +193,23 @@ for_window [app_id="chromium-browser"] fullscreen enable
}; };
// Chromium arguments for kiosk mode on Wayland // Chromium arguments for kiosk mode on Wayland
// Hardware acceleration is enabled where available but falls back gracefully
const browserArgs = [ const browserArgs = [
// Wayland/Ozone configuration
'--ozone-platform=wayland', '--ozone-platform=wayland',
'--enable-features=UseOzonePlatform', '--enable-features=UseOzonePlatform',
// Kiosk mode settings
'--kiosk', '--kiosk',
'--no-first-run', '--no-first-run',
'--disable-infobars', '--disable-infobars',
'--disable-session-crashed-bubble', '--disable-session-crashed-bubble',
'--disable-restore-session-state', '--disable-restore-session-state',
'--noerrdialogs',
// Disable unnecessary features for kiosk
'--disable-background-networking', '--disable-background-networking',
'--disable-sync', '--disable-sync',
'--disable-translate', '--disable-translate',
'--noerrdialogs', '--disable-features=TranslateUI',
// Required for VM/headless/sandboxed environments
'--no-sandbox',
'--disable-setuid-sandbox',
'--disable-dev-shm-usage',
// GPU/rendering flags for VM environments
'--disable-gpu',
'--disable-gpu-compositing',
'--disable-gpu-sandbox',
'--disable-software-rasterizer',
'--disable-accelerated-2d-canvas',
'--disable-accelerated-video-decode',
'--use-gl=swiftshader',
'--in-process-gpu',
// Disable features that may cause issues in kiosk mode
'--disable-features=TranslateUI,VizDisplayCompositor',
'--disable-hang-monitor', '--disable-hang-monitor',
'--disable-breakpad', '--disable-breakpad',
'--disable-component-update', '--disable-component-update',
@@ -281,8 +271,15 @@ for_window [app_id="chromium-browser"] fullscreen enable
return this.swayProcess !== null; return this.swayProcess !== null;
} }
isBrowserRunning(): boolean { async isBrowserRunning(): Promise<boolean> {
return this.browserProcess !== null; // Check if any chromium process is running (Chromium forks, so we can't just track the parent)
try {
const cmd = new Deno.Command('pgrep', { args: ['-f', 'chromium'], stdout: 'null', stderr: 'null' });
const result = await cmd.output();
return result.success;
} catch {
return false;
}
} }
async stopSway(): Promise<void> { async stopSway(): Promise<void> {
@@ -335,12 +332,12 @@ for_window [app_id="chromium-browser"] fullscreen enable
} }
})(); })();
// Monitor process exit // Monitor process exit - only nullify if still the same process (prevents race condition on restart)
process.status.then((status) => { process.status.then((status) => {
console.log(`[${name}] Process exited with code ${status.code}`); console.log(`[${name}] Process exited with code ${status.code}`);
if (name === 'sway') { if (name === 'sway' && this.swayProcess === process) {
this.swayProcess = null; this.swayProcess = null;
} else if (name === 'chromium') { } else if (name === 'chromium' && this.browserProcess === process) {
this.browserProcess = null; this.browserProcess = null;
} }
}); });

View File

@@ -5,6 +5,7 @@
*/ */
import type { EcoDaemon } from '../daemon/index.ts'; import type { EcoDaemon } from '../daemon/index.ts';
import { VERSION } from '../version.ts';
export class UIServer { export class UIServer {
private port: number; private port: number;
@@ -137,7 +138,19 @@ export class UIServer {
padding: 20px; padding: 20px;
} }
.container { max-width: 1200px; margin: 0 auto; } .container { max-width: 1200px; margin: 0 auto; }
h1 { font-size: 24px; margin-bottom: 20px; } .header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
h1 { font-size: 24px; margin: 0; }
.clock {
font-size: 18px;
font-weight: 500;
color: var(--text);
font-variant-numeric: tabular-nums;
}
.grid { .grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
@@ -240,7 +253,10 @@ export class UIServer {
</head> </head>
<body> <body>
<div class="container"> <div class="container">
<h1>EcoOS Management</h1> <div class="header">
<h1>EcoOS Management <span style="font-size: 12px; color: var(--text-dim); font-weight: normal;">v${VERSION}</span></h1>
<div class="clock" id="clock"></div>
</div>
<div class="grid"> <div class="grid">
<div class="card"> <div class="card">
<h2>Services</h2> <h2>Services</h2>
@@ -528,6 +544,24 @@ export class UIServer {
updateStatus(JSON.parse(e.data)); updateStatus(JSON.parse(e.data));
} catch {} } catch {}
}; };
// Clock update
function updateClock() {
const now = new Date();
const options = {
weekday: 'short',
year: 'numeric',
month: 'short',
day: 'numeric',
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
hour12: false
};
document.getElementById('clock').textContent = now.toLocaleString('en-US', options);
}
updateClock();
setInterval(updateClock, 1000);
</script> </script>
</body> </body>
</html>`; </html>`;

View File

@@ -0,0 +1 @@
export const VERSION = "0.1.1";

View File

@@ -48,28 +48,28 @@ else
echo "KVM not available, using software emulation (slower)" echo "KVM not available, using software emulation (slower)"
fi fi
# Start QEMU headless with VNC and serial console # Start QEMU with VirtIO-GPU (VirGL OpenGL acceleration) and serial console
> "$SERIAL_LOG" # Clear old log > "$SERIAL_LOG" # Clear old log
qemu-system-x86_64 \ qemu-system-x86_64 \
$KVM_OPTS \ $KVM_OPTS \
-m 4G \ -m 4G \
-smp 2 \ -smp 4 \
-bios /usr/share/qemu/OVMF.fd \ -bios /usr/share/qemu/OVMF.fd \
-drive file="$ISO_PATH",media=cdrom \ -drive file="$ISO_PATH",media=cdrom \
-drive file="$DISK_PATH",format=qcow2,if=virtio \ -drive file="$DISK_PATH",format=qcow2,if=virtio \
-vga qxl \ -device virtio-vga \
-display none \ -display none \
-vnc :0 \ -spice port=5930,disable-ticketing=on \
-serial unix:"$SERIAL_SOCK",server,nowait \ -serial unix:"$SERIAL_SOCK",server,nowait \
-monitor unix:"$MONITOR_SOCK",server,nowait \ -monitor unix:"$MONITOR_SOCK",server,nowait \
-nic user,model=virtio-net-pci,hostfwd=tcp::3006-:3006,hostfwd=tcp::2222-:22 \ -nic user,model=virtio-net-pci,hostfwd=tcp::3006-:3006,hostfwd=tcp::2222-:22 \
-daemonize \ -pidfile "$PID_FILE" &
-pidfile "$PID_FILE"
echo "" echo ""
sleep 1
echo "=== EcoOS Test VM Started ===" echo "=== EcoOS Test VM Started ==="
echo "PID: $(cat $PID_FILE)" echo "PID: $(cat $PID_FILE 2>/dev/null || echo 'running')"
echo "VNC: localhost:5900" echo "SPICE: spicy -h localhost -p 5930"
echo "Serial Log: $SERIAL_LOG" echo "Serial Log: $SERIAL_LOG"
echo "Management UI: http://localhost:3006" echo "Management UI: http://localhost:3006"
echo "" echo ""

View File

@@ -1,8 +1,9 @@
{ {
"name": "@ecobridge/eco-os", "name": "@ecobridge/eco-os",
"version": "0.1.1",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "pnpm run daemon:bundle && cp ecoos_daemon/bundle/eco-daemon isobuild/config/includes.chroot/opt/eco/bin/ && mkdir -p .nogit/iso && docker build --no-cache -t ecoos-builder -f isobuild/Dockerfile . && docker run --rm --privileged -v $(pwd)/.nogit/iso:/output ecoos-builder", "build": "npm version patch --no-git-tag-version && node -e \"const v=require('./package.json').version; require('fs').writeFileSync('ecoos_daemon/ts/version.ts', 'export const VERSION = \\\"'+v+'\\\";\\n');\" && pnpm run daemon:bundle && cp ecoos_daemon/bundle/eco-daemon isobuild/config/includes.chroot/opt/eco/bin/ && mkdir -p .nogit/iso && docker build --no-cache -t ecoos-builder -f isobuild/Dockerfile . && docker run --rm --privileged -v $(pwd)/.nogit/iso:/output ecoos-builder",
"daemon:dev": "cd ecoos_daemon && deno run --allow-all --watch mod.ts", "daemon:dev": "cd ecoos_daemon && deno run --allow-all --watch mod.ts",
"daemon:start": "cd ecoos_daemon && deno run --allow-all mod.ts", "daemon:start": "cd ecoos_daemon && deno run --allow-all mod.ts",
"daemon:bundle": "cd ecoos_daemon && deno compile --allow-all --output bundle/eco-daemon mod.ts", "daemon:bundle": "cd ecoos_daemon && deno compile --allow-all --output bundle/eco-daemon mod.ts",