/** * Daemon Manager for Onebox * * Handles background monitoring, metrics collection, and automatic tasks */ import * as plugins from './onebox.plugins.ts'; import { logger } from './onebox.logging.ts'; import { projectInfo } from './onebox.info.ts'; import type { Onebox } from './onebox.classes.onebox.ts'; export class OneboxDaemon { private oneboxRef: Onebox; private smartdaemon: plugins.smartdaemon.SmartDaemon; private running = false; private monitoringInterval: number | null = null; private metricsInterval = 60000; // 1 minute constructor(oneboxRef: Onebox) { this.oneboxRef = oneboxRef; this.smartdaemon = new plugins.smartdaemon.SmartDaemon(); // Get metrics interval from settings const customInterval = this.oneboxRef.database.getSetting('metricsInterval'); if (customInterval) { this.metricsInterval = parseInt(customInterval, 10); } } /** * Install systemd service */ async installService(): Promise { try { logger.info('Installing Onebox daemon service...'); // Get installation directory const execPath = Deno.execPath(); const service = await this.smartdaemon.addService({ name: 'onebox', version: projectInfo.version, command: `${execPath} run --allow-all ${Deno.cwd()}/mod.ts daemon start`, description: 'Onebox - Self-hosted container platform', workingDir: Deno.cwd(), }); await service.save(); await service.enable(); logger.success('Onebox daemon service installed'); logger.info('Start with: sudo systemctl start smartdaemon_onebox'); } catch (error) { logger.error(`Failed to install daemon service: ${error.message}`); throw error; } } /** * Uninstall systemd service */ async uninstallService(): Promise { try { logger.info('Uninstalling Onebox daemon service...'); const service = await this.smartdaemon.getService('onebox'); if (service) { await service.stop(); await service.disable(); await service.delete(); } logger.success('Onebox daemon service uninstalled'); } catch (error) { logger.error(`Failed to uninstall daemon service: ${error.message}`); throw error; } } /** * Start daemon mode (background monitoring) */ async start(): Promise { try { if (this.running) { logger.warn('Daemon already running'); return; } logger.info('Starting Onebox daemon...'); this.running = true; // Start monitoring loop this.startMonitoring(); // Start HTTP server const httpPort = parseInt(this.oneboxRef.database.getSetting('httpPort') || '3000', 10); await this.oneboxRef.httpServer.start(httpPort); logger.success('Onebox daemon started'); logger.info(`Web UI available at http://localhost:${httpPort}`); // Keep process alive await this.keepAlive(); } catch (error) { logger.error(`Failed to start daemon: ${error.message}`); this.running = false; throw error; } } /** * Stop daemon mode */ async stop(): Promise { try { if (!this.running) { return; } logger.info('Stopping Onebox daemon...'); this.running = false; // Stop monitoring this.stopMonitoring(); // Stop HTTP server await this.oneboxRef.httpServer.stop(); logger.success('Onebox daemon stopped'); } catch (error) { logger.error(`Failed to stop daemon: ${error.message}`); throw error; } } /** * Start monitoring loop */ private startMonitoring(): void { logger.info('Starting monitoring loop...'); this.monitoringInterval = setInterval(async () => { await this.monitoringTick(); }, this.metricsInterval); // Run first tick immediately this.monitoringTick(); } /** * Stop monitoring loop */ private stopMonitoring(): void { if (this.monitoringInterval !== null) { clearInterval(this.monitoringInterval); this.monitoringInterval = null; logger.debug('Monitoring loop stopped'); } } /** * Single monitoring tick */ private async monitoringTick(): Promise { try { logger.debug('Running monitoring tick...'); // Collect metrics for all services await this.collectMetrics(); // Sync service statuses await this.oneboxRef.services.syncAllServiceStatuses(); // Check SSL certificate expiration await this.checkSSLExpiration(); // Check service health (TODO: implement health checks) logger.debug('Monitoring tick complete'); } catch (error) { logger.error(`Monitoring tick failed: ${error.message}`); } } /** * Collect metrics for all services */ private async collectMetrics(): Promise { try { const services = this.oneboxRef.services.listServices(); for (const service of services) { if (service.status === 'running' && service.containerID) { try { const stats = await this.oneboxRef.docker.getContainerStats(service.containerID); if (stats) { this.oneboxRef.database.addMetric({ serviceId: service.id!, timestamp: Date.now(), cpuPercent: stats.cpuPercent, memoryUsed: stats.memoryUsed, memoryLimit: stats.memoryLimit, networkRxBytes: stats.networkRx, networkTxBytes: stats.networkTx, }); } } catch (error) { logger.debug(`Failed to collect metrics for ${service.name}: ${error.message}`); } } } } catch (error) { logger.error(`Failed to collect metrics: ${error.message}`); } } /** * Check SSL certificate expiration */ private async checkSSLExpiration(): Promise { try { if (!this.oneboxRef.ssl.isConfigured()) { return; } await this.oneboxRef.ssl.renewExpiring(); } catch (error) { logger.error(`Failed to check SSL expiration: ${error.message}`); } } /** * Keep process alive */ private async keepAlive(): Promise { // Set up signal handlers const signalHandler = () => { logger.info('Received shutdown signal'); this.stop().then(() => { Deno.exit(0); }); }; Deno.addSignalListener('SIGINT', signalHandler); Deno.addSignalListener('SIGTERM', signalHandler); // Keep event loop alive while (this.running) { await new Promise((resolve) => setTimeout(resolve, 1000)); } } /** * Get daemon status */ isRunning(): boolean { return this.running; } /** * Get service status from systemd */ async getServiceStatus(): Promise { try { const command = new Deno.Command('systemctl', { args: ['status', 'smartdaemon_onebox'], stdout: 'piped', stderr: 'piped', }); const { code, stdout } = await command.output(); const output = new TextDecoder().decode(stdout); if (code === 0 || output.includes('active (running)')) { return 'running'; } else if (output.includes('inactive') || output.includes('dead')) { return 'stopped'; } else if (output.includes('failed')) { return 'failed'; } else { return 'unknown'; } } catch (error) { return 'not-installed'; } } }