Initial commit: Onebox v1.0.0
- Complete Deno-based architecture following nupst/spark patterns - SQLite database with full schema - Docker container management - Service orchestration (Docker + Nginx + DNS + SSL) - Registry authentication - Nginx reverse proxy configuration - Cloudflare DNS integration - Let's Encrypt SSL automation - Background daemon with metrics collection - HTTP API server - Comprehensive CLI - Cross-platform compilation setup - NPM distribution wrapper - Shell installer script Core features: - Deploy containers with single command - Automatic domain configuration - Automatic SSL certificates - Multi-registry support - Metrics and logging - Systemd integration Ready for Angular UI implementation and testing.
This commit is contained in:
292
ts/onebox.classes.daemon.ts
Normal file
292
ts/onebox.classes.daemon.ts
Normal file
@@ -0,0 +1,292 @@
|
||||
/**
|
||||
* 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';
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user