Files
onebox/ts/onebox.classes.daemon.ts

293 lines
7.4 KiB
TypeScript
Raw Normal View History

/**
* 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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
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<void> {
// 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<string> {
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';
}
}
}