/** * Main Onebox coordinator class * * Coordinates all components and provides the main API */ import { logger } from '../logging.ts'; import { getErrorMessage } from '../utils/error.ts'; import { OneboxDatabase } from './database.ts'; import { OneboxDockerManager } from './docker.ts'; import { OneboxServicesManager } from './services.ts'; import { OneboxRegistriesManager } from './registries.ts'; import { OneboxReverseProxy } from './reverseproxy.ts'; import { OneboxDnsManager } from './dns.ts'; import { OneboxSslManager } from './ssl.ts'; import { OneboxDaemon } from './daemon.ts'; import { OneboxHttpServer } from './httpserver.ts'; import { CloudflareDomainSync } from './cloudflare-sync.ts'; import { CertRequirementManager } from './cert-requirement-manager.ts'; import { RegistryManager } from './registry.ts'; import { PlatformServicesManager } from './platform-services/index.ts'; import { CaddyLogReceiver } from './caddy-log-receiver.ts'; export class Onebox { public database: OneboxDatabase; public docker: OneboxDockerManager; public services: OneboxServicesManager; public registries: OneboxRegistriesManager; public reverseProxy: OneboxReverseProxy; public dns: OneboxDnsManager; public ssl: OneboxSslManager; public daemon: OneboxDaemon; public httpServer: OneboxHttpServer; public cloudflareDomainSync: CloudflareDomainSync; public certRequirementManager: CertRequirementManager; public registry: RegistryManager; public platformServices: PlatformServicesManager; public caddyLogReceiver: CaddyLogReceiver; private initialized = false; constructor() { // Initialize database first this.database = new OneboxDatabase(); // Initialize managers (passing reference to main Onebox instance) this.docker = new OneboxDockerManager(); this.services = new OneboxServicesManager(this); this.registries = new OneboxRegistriesManager(this); this.reverseProxy = new OneboxReverseProxy(this); this.dns = new OneboxDnsManager(this); this.ssl = new OneboxSslManager(this); this.daemon = new OneboxDaemon(this); this.httpServer = new OneboxHttpServer(this); this.registry = new RegistryManager({ dataDir: './.nogit/registry-data', port: 4000, baseUrl: 'localhost:5000', }); // Initialize domain management this.cloudflareDomainSync = new CloudflareDomainSync(this.database); this.certRequirementManager = new CertRequirementManager(this.database, this.ssl); // Initialize platform services manager this.platformServices = new PlatformServicesManager(this); // Initialize Caddy log receiver this.caddyLogReceiver = new CaddyLogReceiver(9999); } /** * Initialize all components */ async init(): Promise { try { logger.info('Initializing Onebox...'); // Initialize database await this.database.init(); // Ensure default admin user exists await this.ensureDefaultUser(); // Initialize Docker await this.docker.init(); // Start Caddy log receiver BEFORE reverse proxy (so Caddy can connect to it) try { await this.caddyLogReceiver.start(); } catch (error) { logger.warn(`Failed to start Caddy log receiver: ${getErrorMessage(error)}`); } // Initialize Reverse Proxy await this.reverseProxy.init(); // Load routes and certificates for reverse proxy await this.reverseProxy.reloadRoutes(); await this.reverseProxy.reloadCertificates(); // Start HTTP reverse proxy (non-critical - don't fail init if ports are busy) // Use 8080/8443 in dev mode to avoid permission issues const isDev = Deno.env.get('ONEBOX_DEV') === 'true' || Deno.args.includes('--ephemeral'); const httpPort = isDev ? 8080 : 80; const httpsPort = isDev ? 8443 : 443; try { await this.reverseProxy.startHttp(httpPort); } catch (error) { logger.warn(`Failed to start HTTP reverse proxy: ${getErrorMessage(error)}`); } // Start HTTPS reverse proxy if certificates are available try { await this.reverseProxy.startHttps(httpsPort); } catch (error) { logger.warn(`Failed to start HTTPS reverse proxy: ${getErrorMessage(error)}`); } // Initialize DNS (non-critical) try { await this.dns.init(); } catch (error) { logger.warn('DNS initialization failed - DNS features will be disabled'); } // Initialize SSL (non-critical) try { await this.ssl.init(); } catch (error) { logger.warn('SSL initialization failed - SSL features will be limited'); } // Initialize Cloudflare domain sync (non-critical) try { await this.cloudflareDomainSync.init(); } catch (error) { logger.warn('Cloudflare domain sync initialization failed - domain sync will be limited'); } // Initialize Onebox Registry (non-critical) try { await this.registry.init(); } catch (error) { logger.warn('Onebox Registry initialization failed - local registry will be disabled'); logger.warn(`Error: ${getErrorMessage(error)}`); } // Initialize Platform Services (non-critical) try { await this.platformServices.init(); } catch (error) { logger.warn('Platform services initialization failed - MongoDB/S3 features will be limited'); logger.warn(`Error: ${getErrorMessage(error)}`); } // Login to all registries await this.registries.loginToAllRegistries(); // Start auto-update monitoring for registry services this.services.startAutoUpdateMonitoring(); this.initialized = true; logger.success('Onebox initialized successfully'); } catch (error) { logger.error(`Failed to initialize Onebox: ${getErrorMessage(error)}`); throw error; } } /** * Ensure default admin user exists */ private async ensureDefaultUser(): Promise { try { const adminUser = this.database.getUserByUsername('admin'); if (!adminUser) { logger.info('Creating default admin user...'); // Simple base64 encoding for now - should use bcrypt in production const passwordHash = btoa('admin'); await this.database.createUser({ username: 'admin', passwordHash, role: 'admin', createdAt: Date.now(), updatedAt: Date.now(), }); logger.warn('Default admin user created with username: admin, password: admin'); logger.warn('IMPORTANT: Change the default password immediately!'); } } catch (error) { logger.error(`Failed to create default user: ${getErrorMessage(error)}`); } } /** * Check if Onebox is initialized */ isInitialized(): boolean { return this.initialized; } /** * Get system status */ async getSystemStatus() { try { const dockerRunning = await this.docker.isDockerRunning(); const proxyStatus = this.reverseProxy.getStatus(); const dnsConfigured = this.dns.isConfigured(); const sslConfigured = this.ssl.isConfigured(); const services = this.services.listServices(); const runningServices = services.filter((s) => s.status === 'running').length; const totalServices = services.length; // Get platform services status with resource counts const platformServices = this.platformServices.getAllPlatformServices(); const providers = this.platformServices.getAllProviders(); const platformServicesStatus = providers.map((provider) => { const service = platformServices.find((s) => s.type === provider.type); // For Caddy, check actual runtime status since it starts without a DB record let status = service?.status || 'not-deployed'; if (provider.type === 'caddy') { status = proxyStatus.http.running ? 'running' : 'stopped'; } // Count resources for this platform service const resourceCount = service?.id ? this.database.getPlatformResourcesByPlatformService(service.id).length : 0; return { type: provider.type, displayName: provider.displayName, status, resourceCount, }; }); // Get certificate health summary const certificates = this.ssl.listCertificates(); const now = Date.now(); const thirtyDaysMs = 30 * 24 * 60 * 60 * 1000; let validCount = 0; let expiringCount = 0; let expiredCount = 0; const expiringDomains: { domain: string; daysRemaining: number }[] = []; for (const cert of certificates) { if (cert.expiryDate <= now) { expiredCount++; } else if (cert.expiryDate <= now + thirtyDaysMs) { expiringCount++; const daysRemaining = Math.floor((cert.expiryDate - now) / (24 * 60 * 60 * 1000)); expiringDomains.push({ domain: cert.domain, daysRemaining }); } else { validCount++; } } // Sort expiring domains by days remaining (ascending) expiringDomains.sort((a, b) => a.daysRemaining - b.daysRemaining); return { docker: { running: dockerRunning, version: dockerRunning ? await this.docker.getDockerVersion() : null, }, reverseProxy: proxyStatus, dns: { configured: dnsConfigured, }, ssl: { configured: sslConfigured, certificateCount: this.ssl.listCertificates().length, }, services: { total: totalServices, running: runningServices, stopped: totalServices - runningServices, }, platformServices: platformServicesStatus, certificateHealth: { valid: validCount, expiringSoon: expiringCount, expired: expiredCount, expiringDomains: expiringDomains.slice(0, 5), // Top 5 expiring }, }; } catch (error) { logger.error(`Failed to get system status: ${getErrorMessage(error)}`); throw error; } } /** * Start daemon mode */ async startDaemon(): Promise { await this.daemon.start(); } /** * Stop daemon mode */ async stopDaemon(): Promise { await this.daemon.stop(); } /** * Start HTTP server */ async startHttpServer(port?: number): Promise { await this.httpServer.start(port); } /** * Stop HTTP server */ async stopHttpServer(): Promise { await this.httpServer.stop(); } /** * Shutdown Onebox gracefully */ async shutdown(): Promise { try { logger.info('Shutting down Onebox...'); // Stop daemon if running await this.daemon.stop(); // Stop HTTP server if running await this.httpServer.stop(); // Stop reverse proxy if running await this.reverseProxy.stop(); // Stop Caddy log receiver await this.caddyLogReceiver.stop(); // Close database this.database.close(); logger.success('Onebox shutdown complete'); } catch (error) { logger.error(`Error during shutdown: ${getErrorMessage(error)}`); } } }