feat(daemon): add automatic update mechanism (Updater), switch to system journal logs, and expose update controls in the UI

This commit is contained in:
2026-01-09 16:55:43 +00:00
parent 1e86acff55
commit 6a3be55cee
8 changed files with 449 additions and 33 deletions

View File

@@ -6,6 +6,7 @@
import { ProcessManager } from './process-manager.ts';
import { SystemInfo } from './system-info.ts';
import { Updater } from './updater.ts';
import { UIServer } from '../ui/server.ts';
import { runCommand } from '../utils/command.ts';
import { VERSION } from '../version.ts';
@@ -28,12 +29,14 @@ export class EcoDaemon {
private config: DaemonConfig;
private processManager: ProcessManager;
private systemInfo: SystemInfo;
private updater: Updater;
private uiServer: UIServer;
private logs: string[] = [];
private serialLogs: string[] = [];
private systemLogs: string[] = [];
private swayStatus: ServiceStatus = { state: 'stopped' };
private chromiumStatus: ServiceStatus = { state: 'stopped' };
private manualRestartUntil: number = 0; // Timestamp until which auto-restart is disabled
private lastAutoUpgradeCheck: number = 0; // Timestamp of last auto-upgrade check
constructor(config?: Partial<DaemonConfig>) {
this.config = {
@@ -45,6 +48,7 @@ export class EcoDaemon {
this.processManager = new ProcessManager(this.config.user);
this.systemInfo = new SystemInfo();
this.updater = new Updater((msg) => this.log(msg));
this.uiServer = new UIServer(this.config.uiPort, this);
}
@@ -64,8 +68,8 @@ export class EcoDaemon {
return [...this.logs];
}
getSerialLogs(): string[] {
return [...this.serialLogs];
getSystemLogs(): string[] {
return [...this.systemLogs];
}
async getStatus(): Promise<Record<string, unknown>> {
@@ -78,7 +82,7 @@ export class EcoDaemon {
chromiumStatus: this.chromiumStatus,
systemInfo,
logs: this.logs.slice(-50),
serialLogs: this.serialLogs.slice(-50),
systemLogs: this.systemLogs.slice(-50),
};
}
@@ -131,6 +135,18 @@ export class EcoDaemon {
}
}
async getUpdateInfo(): Promise<unknown> {
return this.updater.getUpdateInfo();
}
async checkForUpdates(): Promise<void> {
await this.updater.checkForUpdates();
}
async upgradeToVersion(version: string): Promise<{ success: boolean; message: string }> {
return this.updater.upgradeToVersion(version);
}
async start(): Promise<void> {
this.log('EcoOS Daemon starting...');
@@ -139,8 +155,11 @@ export class EcoDaemon {
await this.uiServer.start();
this.log('Management UI started successfully');
// Start serial console reader in the background
this.startSerialReader();
// Start system journal reader in the background
this.startJournalReader();
// Check for updates on startup
this.updater.checkForUpdates().catch((e) => this.log(`Initial update check failed: ${e}`));
// Start the Sway/Chromium initialization in the background
// This allows the UI server to remain responsive even if Sway fails
@@ -313,12 +332,19 @@ export class EcoDaemon {
return parseInt(result.stdout.trim(), 10);
}
private startSerialReader(): void {
private startJournalReader(): void {
(async () => {
try {
const file = await Deno.open('/dev/ttyS0', { read: true });
this.log('Serial console reader started on /dev/ttyS0');
const reader = file.readable.getReader();
const cmd = new Deno.Command('journalctl', {
args: ['-f', '--no-pager', '-n', '100', '-o', 'short-iso'],
stdout: 'piped',
stderr: 'piped',
});
const process = cmd.spawn();
this.log('System journal reader started');
const reader = process.stdout.getReader();
const decoder = new TextDecoder();
while (true) {
@@ -326,14 +352,14 @@ export class EcoDaemon {
if (done) break;
const text = decoder.decode(value);
for (const line of text.split('\n').filter((l) => l.trim())) {
this.serialLogs.push(line);
if (this.serialLogs.length > 1000) {
this.serialLogs = this.serialLogs.slice(-1000);
this.systemLogs.push(line);
if (this.systemLogs.length > 1000) {
this.systemLogs = this.systemLogs.slice(-1000);
}
}
}
} catch (error) {
this.log(`Serial reader not available: ${error}`);
this.log(`Journal reader not available: ${error}`);
}
})();
}
@@ -383,6 +409,16 @@ export class EcoDaemon {
await this.tryStartSwayAndChromium();
}
}
// Check for auto-upgrades every hour
const now = Date.now();
const oneHour = 60 * 60 * 1000;
if (now - this.lastAutoUpgradeCheck > oneHour) {
this.lastAutoUpgradeCheck = now;
this.updater.checkAutoUpgrade().catch((e) =>
this.log(`Auto-upgrade check failed: ${e}`)
);
}
} catch (error) {
this.log(`Error in monitoring loop: ${error}`);
}