UNPKG

forge-deploy-cli

Version:

Professional CLI for local deployments with automatic subdomain routing, SSL certificates, and infrastructure management

342 lines 14.3 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ForgeAPIServer = void 0; exports.getAPIServer = getAPIServer; exports.startAPIServer = startAPIServer; exports.stopAPIServer = stopAPIServer; const http_1 = __importDefault(require("http")); const url_1 = __importDefault(require("url")); const localDeployment_1 = require("./localDeployment"); const chalk_1 = __importDefault(require("chalk")); const fs_extra_1 = __importDefault(require("fs-extra")); const child_process_1 = require("child_process"); const system_1 = require("../utils/system"); /** * Check SSL certificate status by examining nginx config and certificate files */ async function checkSSLStatus(subdomain) { try { const nginxConfigPath = (0, system_1.isWindows)() ? `C:\\nginx\\conf\\sites-available\\${subdomain}.conf` : `/etc/nginx/sites-available/${subdomain}.conf`; // Check if nginx config exists and has SSL configuration if (!await fs_extra_1.default.pathExists(nginxConfigPath)) { return { enabled: false }; } const nginxConfig = await fs_extra_1.default.readFile(nginxConfigPath, 'utf8'); // Check for SSL configuration in nginx const hasSSLConfig = nginxConfig.includes('ssl_certificate') && nginxConfig.includes('ssl_certificate_key'); if (!hasSSLConfig) { return { enabled: false }; } // Extract certificate path from nginx config const certPathMatch = nginxConfig.match(/ssl_certificate\s+([^;]+);/); const certPath = certPathMatch ? certPathMatch[1].trim().replace(/['"]/g, '') : null; if (!certPath || !await fs_extra_1.default.pathExists(certPath)) { return { enabled: true, certificate: 'Configuration found but certificate file missing' }; } // Get certificate information using openssl try { const certInfo = (0, child_process_1.execSync)(`openssl x509 -in "${certPath}" -text -noout`, { encoding: 'utf8', timeout: 5000 }); const notAfterMatch = certInfo.match(/Not After : (.+)/); const notBeforeMatch = certInfo.match(/Not Before: (.+)/); const issuerMatch = certInfo.match(/Issuer: (.+)/); let expiresAt, validFrom, issuer, daysUntilExpiry; if (notAfterMatch) { expiresAt = new Date(notAfterMatch[1]).toISOString(); daysUntilExpiry = Math.ceil((new Date(notAfterMatch[1]).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); } if (notBeforeMatch) { validFrom = new Date(notBeforeMatch[1]).toISOString(); } if (issuerMatch) { issuer = issuerMatch[1].trim(); } return { enabled: true, certificate: certPath, expiresAt, validFrom, issuer, daysUntilExpiry }; } catch (opensslError) { // If openssl fails, try alternative method try { const certInfo = (0, child_process_1.execSync)(`openssl x509 -in "${certPath}" -enddate -issuer -startdate -noout`, { encoding: 'utf8', timeout: 5000 }); const lines = certInfo.split('\n'); let expiresAt, validFrom, issuer, daysUntilExpiry; for (const line of lines) { if (line.startsWith('notAfter=')) { const dateStr = line.replace('notAfter=', ''); expiresAt = new Date(dateStr).toISOString(); daysUntilExpiry = Math.ceil((new Date(dateStr).getTime() - Date.now()) / (1000 * 60 * 60 * 24)); } else if (line.startsWith('notBefore=')) { const dateStr = line.replace('notBefore=', ''); validFrom = new Date(dateStr).toISOString(); } else if (line.startsWith('issuer=')) { issuer = line.replace('issuer=', ''); } } return { enabled: true, certificate: certPath, expiresAt, validFrom, issuer, daysUntilExpiry }; } catch (fallbackError) { return { enabled: true, certificate: certPath, expiresAt: 'Unable to read certificate details', }; } } } catch (error) { // Fallback: check if HTTPS is working by making a request try { const domain = `${subdomain}.forgecli.tech`; const response = await fetch(`https://${domain}`, { signal: AbortSignal.timeout(5000), method: 'HEAD' }); return { enabled: response.ok, certificate: 'SSL working but certificate details unavailable' }; } catch { return { enabled: false }; } } } class ForgeAPIServer { constructor() { this.server = null; this.port = 8080; this.server = http_1.default.createServer(this.handleRequest.bind(this)); } async handleRequest(req, res) { // Enable CORS res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); if (req.method === 'OPTIONS') { res.writeHead(200); res.end(); return; } const parsedUrl = url_1.default.parse(req.url || '', true); const pathname = parsedUrl.pathname || ''; const method = req.method || 'GET'; console.log(chalk_1.default.gray(`${new Date().toISOString()} ${method} ${pathname}`)); try { if (pathname === '/health' && method === 'GET') { this.sendJSON(res, 200, { status: 'ok', timestamp: new Date().toISOString() }); } else if (pathname === '/api/deployments' && method === 'GET') { await this.handleGetDeployments(res); } else if (pathname.startsWith('/api/deployments/') && method === 'GET') { const deploymentId = pathname.split('/')[3]; await this.handleGetDeployment(res, deploymentId); } else if (pathname.startsWith('/api/deployments/') && pathname.endsWith('/stop') && method === 'POST') { const deploymentId = pathname.split('/')[3]; await this.handleStopDeployment(res, deploymentId); } else if (pathname === '/api/system' && method === 'GET') { this.handleGetSystem(res); } else { this.sendJSON(res, 404, { error: 'Not found' }); } } catch (error) { console.error('API Error:', error); this.sendJSON(res, 500, { error: error instanceof Error ? error.message : 'Unknown error' }); } } async handleGetDeployments(res) { const deployments = await localDeployment_1.LocalDeploymentManager.listDeployments(); // Update resources for all deployments for (const deployment of deployments) { await localDeployment_1.LocalDeploymentManager.updateDeploymentResources(deployment.id); } // Get updated deployments const updatedDeployments = await localDeployment_1.LocalDeploymentManager.listDeployments(); this.sendJSON(res, 200, { success: true, deployments: updatedDeployments, count: updatedDeployments.length }); } async handleGetDeployment(res, deploymentId) { const deployment = await localDeployment_1.LocalDeploymentManager.getDeploymentWithResources(deploymentId); if (!deployment) { this.sendJSON(res, 404, { success: false, error: 'Deployment not found' }); return; } // Calculate uptime const calculateUptime = (startTime) => { if (!startTime) return 'N/A'; const start = new Date(startTime); const now = new Date(); const diff = now.getTime() - start.getTime(); const days = Math.floor(diff / (1000 * 60 * 60 * 24)); const hours = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60)); const minutes = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60)); if (days > 0) { return `${days}d ${hours}h ${minutes}m`; } else if (hours > 0) { return `${hours}h ${minutes}m`; } else { return `${minutes}m`; } }; // Perform basic health check let healthStatus = 'unknown'; let responseTime = 0; if (deployment.status === 'running' && deployment.port) { try { const startTime = Date.now(); const response = await fetch(`http://localhost:${deployment.port}/health`, { signal: AbortSignal.timeout(5000) }); responseTime = Date.now() - startTime; healthStatus = response.ok ? 'healthy' : 'unhealthy'; } catch { healthStatus = 'unhealthy'; responseTime = 0; } } // Check SSL status if domain is configured let sslStatus; if (deployment.subdomain) { sslStatus = await checkSSLStatus(deployment.subdomain); } // Get recent logs const logs = [ `[${new Date().toISOString()}] Status check completed - ${deployment.status}`, `[${new Date(Date.now() - 60000).toISOString()}] Resource usage: CPU ${deployment.resources?.cpu?.toFixed(1) || 0}%, Memory ${deployment.resources?.memory?.toFixed(1) || 0}%`, `[${new Date(Date.now() - 120000).toISOString()}] Disk usage: ${deployment.resources?.diskUsed ? (deployment.resources.diskUsed / (1024 * 1024 * 1024)).toFixed(2) : 0}GB`, `[${new Date(Date.now() - 180000).toISOString()}] Health check: ${healthStatus}`, `[${new Date(Date.now() - 240000).toISOString()}] SSL status: ${sslStatus?.enabled ? 'enabled' : 'disabled'}${sslStatus?.daysUntilExpiry ? ` (expires in ${sslStatus.daysUntilExpiry} days)` : ''}`, `[${new Date(Date.now() - 300000).toISOString()}] Process ${deployment.pid ? 'running' : 'not found'} on port ${deployment.port}` ]; this.sendJSON(res, 200, { success: true, deployment: { ...deployment, uptime: calculateUptime(deployment.startedAt), health: { status: healthStatus, responseTime, lastCheck: new Date().toISOString() }, logs, ssl: sslStatus } }); } async handleStopDeployment(res, deploymentId) { await localDeployment_1.LocalDeploymentManager.stopDeployment(deploymentId); this.sendJSON(res, 200, { success: true, message: 'Deployment stopped' }); } handleGetSystem(res) { const os = require('os'); this.sendJSON(res, 200, { success: true, system: { platform: os.platform(), arch: os.arch(), memory: { total: os.totalmem(), free: os.freemem(), used: os.totalmem() - os.freemem() }, cpus: os.cpus().length, uptime: os.uptime(), loadAverage: os.loadavg() } }); } sendJSON(res, statusCode, data) { res.writeHead(statusCode, { 'Content-Type': 'application/json' }); res.end(JSON.stringify(data, null, 2)); } start() { return new Promise((resolve, reject) => { if (!this.server) { reject(new Error('Server not initialized')); return; } this.server.listen(this.port, '0.0.0.0', () => { console.log(chalk_1.default.green(`Forge API server started on port ${this.port}`)); console.log(chalk_1.default.gray(`Local API: http://localhost:${this.port}`)); console.log(chalk_1.default.gray(`Health check: http://localhost:${this.port}/health`)); resolve(); }); this.server.on('error', (error) => { if (error.code === 'EADDRINUSE') { console.log(chalk_1.default.yellow(`Port ${this.port} is already in use. API server not started.`)); resolve(); // Don't reject, just skip starting the server } else { reject(error); } }); }); } stop() { if (this.server) { this.server.close(); console.log(chalk_1.default.gray('Forge API server stopped')); } } } exports.ForgeAPIServer = ForgeAPIServer; // Singleton instance let apiServerInstance = null; function getAPIServer() { if (!apiServerInstance) { apiServerInstance = new ForgeAPIServer(); } return apiServerInstance; } function startAPIServer() { const server = getAPIServer(); return server.start(); } function stopAPIServer() { if (apiServerInstance) { apiServerInstance.stop(); apiServerInstance = null; } } //# sourceMappingURL=apiServer.js.map