UNPKG

forge-deploy-cli

Version:

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

919 lines (904 loc) • 42.2 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.infraCommand = void 0; const chalk_1 = __importDefault(require("chalk")); const commander_1 = require("commander"); const localDeployment_1 = require("../services/localDeployment"); const autoRestart_1 = require("../services/autoRestart"); const system_1 = require("../utils/system"); const firewall_1 = require("../utils/firewall"); const os_1 = __importDefault(require("os")); exports.infraCommand = new commander_1.Command('infra') .description('Setup infrastructure for local deployments') .option('--nginx', 'Setup nginx reverse proxy') .option('--pm2', 'Setup PM2 process manager') .option('--nodejs', 'Setup Node.js dependencies (serve, etc.)') .option('--python', 'Setup Python dependencies (uvicorn, gunicorn, etc.)') .option('--ssl', 'Setup SSL certificates with Certbot') .option('--service', 'Setup auto-restart service') .option('--check-firewall', 'Check firewall configuration for SSL readiness') .option('--all', 'Setup all infrastructure components') .action(async (options) => { try { console.log(chalk_1.default.blue.bold('šŸ”§ Forge Infrastructure Setup')); console.log(chalk_1.default.gray('Setting up local deployment infrastructure...')); console.log(); // Handle firewall check command if (options.checkFirewall) { console.log(chalk_1.default.blue.bold('šŸ” Firewall Configuration Check')); const firewallOk = await (0, firewall_1.performFirewallPreflightCheck)(); if (firewallOk) { console.log(chalk_1.default.green.bold('\nāœ… Firewall is properly configured!')); console.log(chalk_1.default.gray('SSL certificates can be issued successfully.')); } else { console.log(chalk_1.default.red.bold('\nāŒ Firewall configuration needs attention.')); console.log(chalk_1.default.gray('Follow the instructions above to configure your firewall.')); } return; } // Check system privileges const hasElevatedPrivileges = (0, system_1.checkSystemPrivileges)(); const setupAll = options.all; let hasErrors = false; // Setup Node.js and basic dependencies if (setupAll || options.nodejs) { console.log(chalk_1.default.cyan('šŸ“¦ Setting up Node.js dependencies...')); try { await setupNodeJSDependencies(); console.log(chalk_1.default.green('āœ… Node.js dependencies setup completed')); } catch (error) { console.log(chalk_1.default.red(`āŒ Node.js dependencies setup failed: ${error}`)); hasErrors = true; } console.log(); } // Setup Python dependencies if (setupAll || options.python) { console.log(chalk_1.default.cyan('šŸ Setting up Python dependencies...')); try { await setupPythonDependencies(); console.log(chalk_1.default.green('āœ… Python setup completed')); } catch (error) { console.log(chalk_1.default.red(`āŒ Python setup failed: ${error}`)); hasErrors = true; } console.log(); } // Setup PM2 if (setupAll || options.pm2) { console.log(chalk_1.default.cyan('šŸ“¦ Setting up PM2 Process Manager...')); try { await setupPM2(); console.log(chalk_1.default.green('āœ… PM2 setup completed')); } catch (error) { console.log(chalk_1.default.red(`āŒ PM2 setup failed: ${error}`)); hasErrors = true; } console.log(); } // Setup nginx (requires elevated privileges) if (setupAll || options.nginx) { console.log(chalk_1.default.cyan('🌐 Setting up Nginx Reverse Proxy...')); try { if (!hasElevatedPrivileges) { console.log(chalk_1.default.yellow('āš ļø Nginx setup requires elevated privileges')); if ((0, system_1.isWindows)()) { console.log(chalk_1.default.gray(' Run PowerShell as Administrator and retry')); } else { console.log(chalk_1.default.gray(' Run with sudo: sudo forge infra --nginx')); } hasErrors = true; } else { await setupNginxPackage(); await localDeployment_1.LocalDeploymentManager.setupNginx(); console.log(chalk_1.default.green('āœ… Nginx setup completed')); // Show nginx configuration instructions console.log(chalk_1.default.blue('šŸ“‹ Nginx Configuration:')); if ((0, system_1.isWindows)()) { console.log(chalk_1.default.gray(' • Nginx config directory: C:\\nginx\\conf\\forge-sites')); console.log(chalk_1.default.gray(' • Start nginx: C:\\nginx\\nginx.exe')); console.log(chalk_1.default.gray(' • Reload config: nginx -s reload')); } else { console.log(chalk_1.default.gray(' • Nginx config directory: /etc/nginx/sites-available')); console.log(chalk_1.default.gray(' • Enable site: sudo ln -s /etc/nginx/sites-available/site.conf /etc/nginx/sites-enabled/')); console.log(chalk_1.default.gray(' • Reload config: sudo nginx -s reload')); console.log(chalk_1.default.gray(' • Check status: sudo systemctl status nginx')); } } } catch (error) { console.log(chalk_1.default.red(`āŒ Nginx setup failed: ${error}`)); hasErrors = true; } console.log(); } // Setup Python dependencies (for Python projects) if (setupAll || options.python) { console.log(chalk_1.default.cyan('šŸ Setting up Python dependencies...')); try { await setupPythonDependencies(); console.log(chalk_1.default.green('āœ… Python dependencies setup completed')); } catch (error) { console.log(chalk_1.default.red(`āŒ Python dependencies setup failed: ${error}`)); hasErrors = true; } console.log(); } // Setup auto-restart service if (setupAll || options.service) { console.log(chalk_1.default.cyan('šŸ”„ Setting up Auto-Restart Service...')); try { await autoRestart_1.AutoRestartService.setupAutoRestart(); console.log(chalk_1.default.green('āœ… Auto-restart service setup completed')); } catch (error) { console.log(chalk_1.default.red(`āŒ Auto-restart service setup failed: ${error}`)); hasErrors = true; } console.log(); } // Setup SSL certificates with Certbot if (setupAll || options.ssl) { console.log(chalk_1.default.cyan('šŸ”’ Setting up SSL certificates with Certbot...')); try { if (!hasElevatedPrivileges) { console.log(chalk_1.default.yellow('āš ļø SSL setup requires elevated privileges')); if ((0, system_1.isWindows)()) { console.log(chalk_1.default.gray(' Run PowerShell as Administrator and retry')); } else { console.log(chalk_1.default.gray(' Run with sudo: sudo forge infra --ssl')); } hasErrors = true; } else { await setupSSLCertificates(); console.log(chalk_1.default.green('āœ… SSL certificates setup completed')); } } catch (error) { console.log(chalk_1.default.red(`āŒ SSL setup failed: ${error}`)); hasErrors = true; } console.log(); } // Final instructions if (!hasErrors) { console.log(chalk_1.default.green.bold('šŸŽ‰ Infrastructure setup completed successfully!')); console.log(); console.log(chalk_1.default.blue('šŸš€ Next Steps:')); console.log(chalk_1.default.gray(' 1. Check firewall: forge infra --check-firewall')); console.log(chalk_1.default.gray(' 2. Deploy your applications: forge deploy <repo-url>')); console.log(chalk_1.default.gray(' 3. Subdomains are automatically managed via API')); console.log(chalk_1.default.gray(' 4. SSL certificates are automatically provisioned')); console.log(); console.log(chalk_1.default.blue('šŸ”§ Management Commands:')); console.log(chalk_1.default.gray(' • forge status - Check all deployments')); console.log(chalk_1.default.gray(' • forge pause <deployment-id> - Pause a deployment')); console.log(chalk_1.default.gray(' • forge resume <deployment-id> - Resume a deployment')); console.log(chalk_1.default.gray(' • forge stop <deployment-id> - Stop a deployment')); console.log(chalk_1.default.gray(' • forge logs <deployment-id> - View deployment logs')); console.log(); console.log(chalk_1.default.yellow('šŸ’” SSL Troubleshooting:')); console.log(chalk_1.default.gray(' • forge infra --check-firewall - Test firewall configuration')); console.log(chalk_1.default.gray(' • forge infra --ssl - Reinstall SSL certificates')); } else { console.log(chalk_1.default.yellow('āš ļø Infrastructure setup completed with some errors')); console.log(chalk_1.default.gray('Check the error messages above and resolve them manually')); } } catch (error) { console.log(chalk_1.default.red(`Infrastructure setup failed: ${error}`)); process.exit(1); } }); async function setupPM2() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); try { // Check if PM2 is installed execSync('pm2 --version', { stdio: 'pipe' }); console.log(chalk_1.default.gray('PM2 is already installed')); } catch { console.log(chalk_1.default.gray('Installing PM2...')); execSync('npm install -g pm2', { stdio: 'inherit' }); } // Setup PM2 startup try { if (os_1.default.platform() === 'win32') { console.log(chalk_1.default.gray('Setting up PM2 Windows service...')); try { execSync('pm2-windows-startup install', { stdio: 'inherit' }); } catch { console.log(chalk_1.default.yellow('Warning: pm2-windows-startup not found, installing...')); execSync('npm install -g pm2-windows-startup', { stdio: 'inherit' }); execSync('pm2-windows-startup install', { stdio: 'inherit' }); } } else { console.log(chalk_1.default.gray('Setting up PM2 startup script...')); const startupCommand = execSync('pm2 startup', { encoding: 'utf8' }); console.log(chalk_1.default.yellow('Please run the following command as root:')); console.log(chalk_1.default.cyan(startupCommand.trim())); } } catch (error) { console.log(chalk_1.default.yellow('Warning: Could not setup PM2 auto-startup')); console.log(chalk_1.default.gray('You may need to configure this manually')); } // Create PM2 ecosystem configuration console.log(chalk_1.default.gray('Creating PM2 ecosystem configuration...')); const ecosystemConfig = `module.exports = { apps: [ // Forge deployments will be added here automatically ], deploy: { production: { user: 'node', host: 'localhost', ref: 'origin/main', repo: 'git@github.com:repo.git', path: '/var/www/production', 'post-deploy': 'npm install && pm2 reload ecosystem.config.js --env production' } } };`; const fs = await Promise.resolve().then(() => __importStar(require('fs-extra'))); const path = await Promise.resolve().then(() => __importStar(require('path'))); const ecosystemPath = path.join(process.cwd(), 'ecosystem.config.js'); if (!await fs.pathExists(ecosystemPath)) { await fs.writeFile(ecosystemPath, ecosystemConfig); console.log(chalk_1.default.gray(`Created PM2 ecosystem file: ${ecosystemPath}`)); } } async function setupNodeJSDependencies() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); console.log(chalk_1.default.gray('Installing global Node.js dependencies...')); const packages = ['serve', 'http-server', 'live-server']; for (const pkg of packages) { try { console.log(chalk_1.default.gray(`Installing ${pkg}...`)); execSync(`npm install -g ${pkg}`, { stdio: 'inherit' }); } catch (error) { console.log(chalk_1.default.yellow(`Warning: Could not install ${pkg}`)); } } } async function setupPythonDependencies() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); let pythonCmd = 'python'; let pipCmd = 'pip'; // Check if Python is installed try { execSync('python --version', { stdio: 'pipe' }); } catch { try { execSync('python3 --version', { stdio: 'pipe' }); pythonCmd = 'python3'; pipCmd = 'pip3'; } catch { console.log(chalk_1.default.yellow('Python not found. Installing Python...')); if ((0, system_1.isWindows)()) { console.log(chalk_1.default.gray('Please install Python from https://python.org/downloads/')); console.log(chalk_1.default.gray('Or use winget: winget install Python.Python.3')); return; } else { try { // Try to install Python on Linux try { execSync('sudo apt-get update && sudo apt-get install -y python3 python3-pip python3-venv python3-full', { stdio: 'inherit' }); } catch { try { execSync('sudo yum install -y python3 python3-pip', { stdio: 'inherit' }); } catch { try { execSync('sudo dnf install -y python3 python3-pip', { stdio: 'inherit' }); } catch { console.log(chalk_1.default.red('Could not install Python automatically')); console.log(chalk_1.default.gray('Please install Python manually and retry')); return; } } } pythonCmd = 'python3'; pipCmd = 'pip3'; console.log(chalk_1.default.green('Python installed successfully')); } catch (error) { console.log(chalk_1.default.red(`Failed to install Python: ${error}`)); return; } } } } // Check if pip is available try { execSync(`${pipCmd} --version`, { stdio: 'pipe' }); } catch { console.log(chalk_1.default.yellow('pip not found. Installing pip...')); if ((0, system_1.isWindows)()) { try { execSync(`${pythonCmd} -m ensurepip --upgrade`, { stdio: 'inherit' }); } catch { console.log(chalk_1.default.red('Could not install pip. Please install manually.')); return; } } else { try { execSync('sudo apt-get install -y python3-pip python3-venv', { stdio: 'inherit' }); } catch { try { execSync(`${pythonCmd} -m ensurepip --upgrade`, { stdio: 'inherit' }); } catch { console.log(chalk_1.default.red('Could not install pip. Please install manually.')); return; } } } } console.log(chalk_1.default.gray('Setting up Python web server dependencies...')); // For newer Python distributions, try system packages first, then pipx, then venv const packages = ['uvicorn', 'gunicorn', 'waitress']; const systemPackages = ['python3-uvicorn', 'python3-gunicorn', 'python3-waitress']; if (!(0, system_1.isWindows)()) { // Try installing system packages first (preferred for newer distributions) console.log(chalk_1.default.gray('Attempting to install system Python packages...')); for (let i = 0; i < systemPackages.length; i++) { try { console.log(chalk_1.default.gray(`Installing ${systemPackages[i]}...`)); execSync(`sudo apt-get install -y ${systemPackages[i]}`, { stdio: 'pipe' }); } catch { // If system package fails, skip for now console.log(chalk_1.default.yellow(`System package ${systemPackages[i]} not available`)); } } // Try pipx for user-installed packages try { execSync('pipx --version', { stdio: 'pipe' }); console.log(chalk_1.default.gray('Using pipx for Python package installation...')); for (const pkg of packages) { try { console.log(chalk_1.default.gray(`Installing ${pkg} with pipx...`)); execSync(`pipx install ${pkg}`, { stdio: 'inherit' }); } catch (error) { console.log(chalk_1.default.yellow(`Warning: Could not install ${pkg} with pipx`)); } } } catch { // pipx not available, try installing it try { console.log(chalk_1.default.gray('Installing pipx...')); execSync('sudo apt-get install -y pipx', { stdio: 'inherit' }); for (const pkg of packages) { try { console.log(chalk_1.default.gray(`Installing ${pkg} with pipx...`)); execSync(`pipx install ${pkg}`, { stdio: 'inherit' }); } catch (error) { console.log(chalk_1.default.yellow(`Warning: Could not install ${pkg} with pipx`)); } } } catch { // If pipx installation fails, create a system-wide virtual environment console.log(chalk_1.default.gray('Creating system virtual environment for Python packages...')); try { const venvPath = '/opt/forge-python-env'; execSync(`sudo ${pythonCmd} -m venv ${venvPath}`, { stdio: 'inherit' }); for (const pkg of packages) { try { console.log(chalk_1.default.gray(`Installing ${pkg} in virtual environment...`)); execSync(`sudo ${venvPath}/bin/pip install ${pkg}`, { stdio: 'inherit' }); } catch (error) { console.log(chalk_1.default.yellow(`Warning: Could not install ${pkg} in venv`)); } } // Create symlinks for easy access try { execSync(`sudo ln -sf ${venvPath}/bin/uvicorn /usr/local/bin/uvicorn`, { stdio: 'pipe' }); execSync(`sudo ln -sf ${venvPath}/bin/gunicorn /usr/local/bin/gunicorn`, { stdio: 'pipe' }); console.log(chalk_1.default.green('Python web server tools installed in system virtual environment')); } catch { console.log(chalk_1.default.yellow('Warning: Could not create symlinks for Python tools')); } } catch (venvError) { console.log(chalk_1.default.yellow(`Warning: Could not create virtual environment: ${venvError}`)); console.log(chalk_1.default.gray('Python web frameworks may need to be installed manually')); } } } } else { // Windows - use regular pip for (const pkg of packages) { try { console.log(chalk_1.default.gray(`Installing ${pkg}...`)); execSync(`${pipCmd} install ${pkg}`, { stdio: 'inherit' }); } catch (error) { console.log(chalk_1.default.yellow(`Warning: Could not install ${pkg}`)); } } } console.log(chalk_1.default.green('Python setup completed')); console.log(chalk_1.default.blue('šŸ“‹ Python Tools Available:')); // Check what's actually available const tools = [ { name: 'uvicorn', desc: 'ASGI server for FastAPI/Starlette' }, { name: 'gunicorn', desc: 'WSGI server for Flask/Django' }, { name: 'waitress', desc: 'Pure Python WSGI server' } ]; for (const tool of tools) { try { execSync(`which ${tool.name} || where ${tool.name}`, { stdio: 'pipe' }); console.log(chalk_1.default.green(` āœ“ ${tool.name} - ${tool.desc}`)); } catch { console.log(chalk_1.default.gray(` āœ— ${tool.name} - ${tool.desc} (not available)`)); } } } async function setupNginxPackage() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); const fs = await Promise.resolve().then(() => __importStar(require('fs-extra'))); try { // Check if nginx is already installed execSync('nginx -v', { stdio: 'pipe' }); console.log(chalk_1.default.gray('Nginx is already installed')); } catch { console.log(chalk_1.default.gray('Installing nginx...')); if ((0, system_1.isWindows)()) { console.log(chalk_1.default.yellow('Windows nginx installation:')); console.log(chalk_1.default.gray(' 1. Download nginx from http://nginx.org/en/download.html')); console.log(chalk_1.default.gray(' 2. Extract to C:\\nginx')); console.log(chalk_1.default.gray(' 3. Add C:\\nginx to your PATH')); console.log(chalk_1.default.gray(' 4. Run "nginx.exe" to start')); console.log(chalk_1.default.gray('Or use chocolatey: choco install nginx')); return; } // Linux installation with better error handling let installSuccess = false; const packageManagers = [ { cmd: 'apt-get update && apt-get install -y nginx', name: 'apt' }, { cmd: 'yum install -y nginx', name: 'yum' }, { cmd: 'dnf install -y nginx', name: 'dnf' }, { cmd: 'pacman -S --noconfirm nginx', name: 'pacman' }, { cmd: 'zypper install -y nginx', name: 'zypper' } ]; for (const pm of packageManagers) { try { console.log(chalk_1.default.gray(`Installing nginx via ${pm.name}...`)); execSync(pm.cmd, { stdio: 'inherit' }); installSuccess = true; break; } catch { continue; } } if (!installSuccess) { throw new Error('Could not install nginx automatically. Please install manually.'); } } // Configure nginx for Forge try { await setupNginxConfiguration(); console.log(chalk_1.default.green('Nginx configuration setup completed')); } catch (error) { console.log(chalk_1.default.yellow(`Warning: Nginx configuration setup failed: ${error}`)); } // Enable and start nginx service try { console.log(chalk_1.default.gray('Configuring nginx service...')); execSync('systemctl enable nginx', { stdio: 'pipe' }); execSync('systemctl start nginx', { stdio: 'pipe' }); console.log(chalk_1.default.green('Nginx service enabled and started')); } catch (error) { console.log(chalk_1.default.yellow('Warning: Could not enable/start nginx service automatically')); console.log(chalk_1.default.gray('You may need to start it manually: sudo systemctl start nginx')); } // Verify installation try { execSync('nginx -v', { stdio: 'pipe' }); const status = execSync('systemctl is-active nginx', { encoding: 'utf8', stdio: 'pipe' }).trim(); if (status === 'active') { console.log(chalk_1.default.green('āœ… Nginx installation and configuration verified')); } else { console.log(chalk_1.default.yellow('āš ļø Nginx installed but service is not active')); } } catch { throw new Error('Nginx installation failed verification'); } } async function setupNginxConfiguration() { const fs = await Promise.resolve().then(() => __importStar(require('fs-extra'))); const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); // Ensure nginx directories exist const nginxDirs = [ '/etc/nginx/sites-available', '/etc/nginx/sites-enabled', '/etc/nginx/forge-sites', '/var/www/html/.well-known/acme-challenge' ]; for (const dir of nginxDirs) { await fs.ensureDir(dir); } // Create main nginx configuration if needed const mainConfigPath = '/etc/nginx/nginx.conf'; const backupConfigPath = '/etc/nginx/nginx.conf.forge-backup'; if (await fs.pathExists(mainConfigPath)) { // Backup existing config if (!await fs.pathExists(backupConfigPath)) { await fs.copy(mainConfigPath, backupConfigPath); console.log(chalk_1.default.gray('Backed up existing nginx.conf')); } // Update config to include forge sites let config = await fs.readFile(mainConfigPath, 'utf8'); const forgeInclude = 'include /etc/nginx/forge-sites/*.conf;'; if (!config.includes(forgeInclude)) { // Add forge sites include before the default server block config = config.replace(/include \/etc\/nginx\/sites-enabled\/\*;/, `include /etc/nginx/sites-enabled/*;\n ${forgeInclude}`); await fs.writeFile(mainConfigPath, config); console.log(chalk_1.default.gray('Updated nginx.conf to include forge sites')); } } // Create default SSL configuration const sslConfigPath = '/etc/nginx/forge-ssl.conf'; const sslConfig = `# Forge SSL Configuration ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; # SSL session settings ssl_session_timeout 1d; ssl_session_cache shared:ForgeSSL:50m; ssl_stapling on; ssl_stapling_verify on; # Security headers add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always; add_header X-Frame-Options DENY always; add_header X-Content-Type-Options nosniff always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; `; await fs.writeFile(sslConfigPath, sslConfig); console.log(chalk_1.default.gray('Created SSL configuration file')); // Test nginx configuration try { execSync('nginx -t', { stdio: 'pipe' }); console.log(chalk_1.default.green('Nginx configuration test passed')); } catch (error) { console.log(chalk_1.default.red('Nginx configuration test failed')); throw error; } } async function setupSSLCertificates() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); const fs = await Promise.resolve().then(() => __importStar(require('fs-extra'))); if ((0, system_1.isWindows)()) { console.log(chalk_1.default.yellow('SSL certificate setup on Windows:')); console.log(chalk_1.default.gray(' For Windows, SSL certificates are typically handled by:')); console.log(chalk_1.default.gray(' 1. Use a reverse proxy like Cloudflare')); console.log(chalk_1.default.gray(' 2. Use IIS with SSL bindings')); console.log(chalk_1.default.gray(' 3. Use Win-ACME for Let\'s Encrypt certificates')); console.log(chalk_1.default.gray(' Download Win-ACME: https://www.win-acme.com/')); return; } console.log(chalk_1.default.gray('Setting up SSL certificate management with Certbot...')); // Perform firewall preflight check const firewallOk = await (0, firewall_1.performFirewallPreflightCheck)(); if (!firewallOk) { console.log(chalk_1.default.red('āŒ SSL setup cannot continue due to firewall issues.')); console.log(chalk_1.default.yellow('Please configure your firewall as shown above, then retry.')); throw new Error('Firewall configuration required for SSL setup'); } try { // Install certbot await installCertbot(); // Setup certbot hooks and scripts await setupCertbotIntegration(); // Configure automatic renewal await setupAutoRenewal(); console.log(chalk_1.default.green('SSL certificate management setup completed')); console.log(chalk_1.default.blue('šŸ“‹ SSL Certificate Info:')); console.log(chalk_1.default.gray(' • SSL certificates are generated per deployment/subdomain')); console.log(chalk_1.default.gray(' • Certificates are requested automatically during deployment')); console.log(chalk_1.default.gray(' • Automatic renewal is enabled via systemd timer')); console.log(chalk_1.default.gray(' • Certificates are stored in /etc/letsencrypt/live/<subdomain>/')); console.log(chalk_1.default.yellow(' • Note: Cloudflare DNS management is handled via API for security')); } catch (error) { console.log(chalk_1.default.red(`Failed to setup SSL certificates: ${error}`)); // Provide helpful error guidance based on common issues const errorMessage = error instanceof Error ? error.message : String(error); if (errorMessage.includes('Timeout during connect')) { console.log(chalk_1.default.yellow('\nšŸ” Common SSL Certificate Issues:')); console.log(chalk_1.default.gray(' • Firewall blocking ports 80/443 (most common)')); console.log(chalk_1.default.gray(' • DNS not pointing to this server')); console.log(chalk_1.default.gray(' • Service not running on port 80')); console.log(chalk_1.default.gray(' • Rate limiting from Let\'s Encrypt')); console.log(); console.log(chalk_1.default.blue('šŸ’” Troubleshooting:')); console.log(chalk_1.default.gray(' 1. Run: forge infra --ssl (to recheck firewall)')); console.log(chalk_1.default.gray(' 2. Verify DNS: nslookup your-domain.com')); console.log(chalk_1.default.gray(' 3. Test port 80: curl -I http://your-domain.com')); console.log(chalk_1.default.gray(' 4. Check nginx: systemctl status nginx')); } throw error; } } async function installCertbot() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); try { // Check if certbot is already installed execSync('certbot --version', { stdio: 'pipe' }); console.log(chalk_1.default.gray('Certbot is already installed')); return; } catch { // Install certbot } console.log(chalk_1.default.gray('Installing Certbot...')); const installMethods = [ { name: 'apt', commands: [ 'apt-get update', 'apt-get install -y certbot python3-certbot-nginx' ] }, { name: 'snap', commands: [ 'snap install core; snap refresh core', 'snap install --classic certbot', 'ln -sf /snap/bin/certbot /usr/bin/certbot' ] }, { name: 'yum/dnf', commands: [ 'yum install -y certbot python3-certbot-nginx || dnf install -y certbot python3-certbot-nginx' ] } ]; let installSuccess = false; for (const method of installMethods) { try { console.log(chalk_1.default.gray(`Installing certbot via ${method.name}...`)); for (const cmd of method.commands) { execSync(cmd, { stdio: 'inherit' }); } // Verify installation execSync('certbot --version', { stdio: 'pipe' }); installSuccess = true; console.log(chalk_1.default.green(`Certbot installed successfully via ${method.name}`)); break; } catch { console.log(chalk_1.default.gray(`Failed to install via ${method.name}, trying next method...`)); continue; } } if (!installSuccess) { throw new Error('Could not install certbot automatically'); } } async function setupCertbotIntegration() { const fs = await Promise.resolve().then(() => __importStar(require('fs-extra'))); const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); // Create pre/post hooks for nginx integration const hooksDir = '/etc/letsencrypt/renewal-hooks'; await fs.ensureDir(`${hooksDir}/pre`); await fs.ensureDir(`${hooksDir}/post`); // Pre-hook: Test nginx config before renewal const preHook = `#!/bin/bash # Forge SSL Pre-renewal Hook echo "Testing nginx configuration before certificate renewal..." nginx -t if [ $? -ne 0 ]; then echo "Nginx configuration test failed. Skipping renewal." exit 1 fi `; await fs.writeFile(`${hooksDir}/pre/forge-nginx-test`, preHook); execSync(`chmod +x ${hooksDir}/pre/forge-nginx-test`, { stdio: 'pipe' }); // Post-hook: Reload nginx after successful renewal const postHook = `#!/bin/bash # Forge SSL Post-renewal Hook echo "Reloading nginx after certificate renewal..." systemctl reload nginx if [ $? -eq 0 ]; then echo "Nginx reloaded successfully" else echo "Failed to reload nginx" fi `; await fs.writeFile(`${hooksDir}/post/forge-nginx-reload`, postHook); execSync(`chmod +x ${hooksDir}/post/forge-nginx-reload`, { stdio: 'pipe' }); // Create SSL certificate setup script for deployments const sslSetupScript = `#!/bin/bash # Forge SSL Certificate Setup Script # This script is called automatically when deploying with SSL set -e DOMAIN="$1" PUBLIC_IP="$2" NGINX_CONFIG="$3" if [ -z "$DOMAIN" ] || [ -z "$PUBLIC_IP" ]; then echo "Usage: $0 <domain> <public-ip> [nginx-config-path]" exit 1 fi echo "Setting up SSL certificate for $DOMAIN..." # Check if certificate already exists if certbot certificates 2>/dev/null | grep -q "$DOMAIN"; then echo "Certificate for $DOMAIN already exists, checking if renewal is needed..." certbot renew --cert-name "$DOMAIN" --nginx --non-interactive echo "Certificate check completed for $DOMAIN" exit 0 fi # Enhanced firewall and accessibility check echo "Checking if ports 80 and 443 are accessible..." if ! timeout 10 bash -c "echo >/dev/tcp/$PUBLIC_IP/80" 2>/dev/null; then echo "āŒ ERROR: Port 80 is not accessible from the internet!" echo "This will prevent Let's Encrypt from issuing certificates." echo "" echo "šŸ”§ Firewall Configuration Required:" echo " • GCP: Enable 'Allow HTTP traffic' in Compute Engine firewall" echo " • AWS: Add HTTP (port 80) to Security Group inbound rules" echo " • Azure: Add HTTP (port 80) to Network Security Group" echo " • Other: Configure firewall to allow TCP port 80 from 0.0.0.0/0" echo "" echo "After configuring firewall, wait 2-3 minutes and retry deployment." exit 1 fi if ! timeout 10 bash -c "echo >/dev/tcp/$PUBLIC_IP/443" 2>/dev/null; then echo "āš ļø WARNING: Port 443 is not accessible (will be needed after SSL setup)" fi # Wait for DNS propagation if needed echo "Checking DNS resolution for $DOMAIN..." for i in {1..30}; do if nslookup "$DOMAIN" >/dev/null 2>&1; then resolved_ip=$(nslookup "$DOMAIN" | grep -A1 "Name:" | tail -n1 | awk '{print $2}' | head -1) if [ "$resolved_ip" = "$PUBLIC_IP" ]; then echo "āœ… DNS resolution successful for $DOMAIN -> $PUBLIC_IP" break else echo "āš ļø DNS resolves to $resolved_ip, expected $PUBLIC_IP" fi fi if [ $i -eq 30 ]; then echo "Warning: DNS resolution issues for $DOMAIN, but proceeding anyway..." echo "Make sure your DNS points to $PUBLIC_IP" fi sleep 2 done # Test nginx configuration before requesting certificate echo "Testing nginx configuration..." nginx -t if [ $? -ne 0 ]; then echo "Nginx configuration test failed. Please fix the configuration first." exit 1 fi # Request certificate using nginx plugin echo "Requesting SSL certificate from Let's Encrypt..." certbot --nginx -d "$DOMAIN" \\ --non-interactive \\ --agree-tos \\ --email "admin@$DOMAIN" \\ --redirect \\ --no-eff-email if [ $? -eq 0 ]; then echo "SSL certificate setup completed successfully for $DOMAIN" # Test the configuration again nginx -t && systemctl reload nginx echo "Nginx configuration updated and reloaded" else echo "SSL certificate setup failed for $DOMAIN" exit 1 fi `; const scriptPath = '/usr/local/bin/forge-ssl-setup'; await fs.writeFile(scriptPath, sslSetupScript); execSync(`chmod +x ${scriptPath}`, { stdio: 'pipe' }); console.log(chalk_1.default.gray(`Created SSL setup script: ${scriptPath}`)); } async function setupAutoRenewal() { const { execSync } = await Promise.resolve().then(() => __importStar(require('child_process'))); try { // Enable certbot renewal timer console.log(chalk_1.default.gray('Setting up automatic certificate renewal...')); execSync('systemctl enable certbot.timer', { stdio: 'pipe' }); execSync('systemctl start certbot.timer', { stdio: 'pipe' }); // Verify timer is active const timerStatus = execSync('systemctl is-active certbot.timer', { encoding: 'utf8', stdio: 'pipe' }).trim(); if (timerStatus === 'active') { console.log(chalk_1.default.green('āœ… Automatic certificate renewal enabled')); } else { throw new Error('Failed to enable automatic renewal timer'); } // Test renewal process console.log(chalk_1.default.gray('Testing certificate renewal process...')); try { execSync('certbot renew --dry-run --quiet', { stdio: 'pipe' }); console.log(chalk_1.default.green('āœ… Certificate renewal test passed')); } catch (error) { console.log(chalk_1.default.yellow('āš ļø Certificate renewal test failed, but continuing...')); } } catch (error) { console.log(chalk_1.default.yellow(`Warning: Could not enable automatic renewal: ${error}`)); console.log(chalk_1.default.gray('You may need to set this up manually')); } } //# sourceMappingURL=infra.js.map