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
JavaScript
;
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