/** * 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 const platformServices = this.platformServices.getAllPlatformServices(); const platformServicesStatus = platformServices.map((ps) => ({ type: ps.type, status: ps.status, })); return { docker: { running: dockerRunning, version: dockerRunning ? await this.docker.getDockerVersion() : null, }, reverseProxy: proxyStatus, dns: { configured: dnsConfigured, }, ssl: { configured: sslConfigured, certbotInstalled: await this.ssl.isCertbotInstalled(), }, services: { total: totalServices, running: runningServices, stopped: totalServices - runningServices, }, platformServices: platformServicesStatus, }; } 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)}`); } } }