const fs = require('fs'); const path = require('path'); const { execSync, exec } = require('child_process'); const config = require('../config'); class NginxManager { constructor() { this.configDir = path.resolve(config.nginxConfigDir); this.templatePath = path.resolve(config.nginxTemplatePath); this.backupFileName = 'auto-deploy.conf.backup'; this.mainConfigFile = path.join(this.configDir, 'auto-deploy.conf'); console.log('[NginxManager] Initialized with:'); console.log(` - configDir: ${this.configDir}`); console.log(` - templatePath: ${this.templatePath}`); console.log(` - mainConfigFile: ${this.mainConfigFile}`); } renderTemplate(templateName, variables) { const templateFile = path.join(this.templatePath, templateName); console.log(`[NginxManager] Rendering template: ${templateFile}`); if (!fs.existsSync(templateFile)) { console.error(`[NginxManager] Template file not found: ${templateFile}`); throw new Error(`Template file not found: ${templateName}`); } let content = fs.readFileSync(templateFile, 'utf8'); Object.keys(variables).forEach(key => { const regex = new RegExp(`{{${key}}}`, 'g'); content = content.replace(regex, variables[key] || ''); }); console.log(`[NginxManager] Template rendered successfully`); return content; } generateMainConfig() { try { console.log('[NginxManager] Generating main config...'); const projects = this._getRunningProjects(); console.log(`[NginxManager] Found ${projects.length} running projects:`, projects.map(p => ({ id: p.id, name: p.name, port: p.port }))); const projectLocations = projects.map(project => { console.log(`[NginxManager] Generating location for project ${project.name} (ID: ${project.id}, Port: ${project.port})`); return this.renderTemplate('location.conf.tpl', { PROJECT_NAME: project.name, PROJECT_ID: project.id, PROJECT_PORT: project.port }); }).join('\n'); console.log(`[NginxManager] Project locations generated: ${projectLocations.length} chars`); const serverName = config.baseDomain || 'localhost'; const port = config.isProduction() ? 80 : config.port; console.log(`[NginxManager] Server config: serverName=${serverName}, port=${port}`); const mainConfig = this.renderTemplate('main.conf.tpl', { PORT: port, SERVER_NAME: serverName, MANAGER_PORT: config.port, PROJECT_LOCATIONS: projectLocations }); console.log(`[NginxManager] Main config generated: ${mainConfig.length} chars`); return mainConfig; } catch (error) { console.error(`[NginxManager] Failed to generate main config: ${error.message}`); throw new Error(`Failed to generate main config: ${error.message}`); } } addProjectLocation(project) { console.log(`[NginxManager] addProjectLocation called for project:`, project); try { this._backupConfig(); console.log(`[NginxManager] Checking config directory: ${this.configDir}`); if (!fs.existsSync(this.configDir)) { console.log(`[NginxManager] Creating config directory: ${this.configDir}`); fs.mkdirSync(this.configDir, { recursive: true }); } const serverName = config.baseDomain || 'localhost'; const port = config.isProduction() ? 80 : config.port; const projectLocation = this.renderTemplate('location.conf.tpl', { PROJECT_NAME: project.name, PROJECT_ID: project.id, PROJECT_PORT: project.port }); const mainConfig = this.renderTemplate('main.conf.tpl', { PORT: port, SERVER_NAME: serverName, MANAGER_PORT: config.port, PROJECT_LOCATIONS: projectLocation }); console.log(`[NginxManager] Writing config to: ${this.mainConfigFile}`); fs.writeFileSync(this.mainConfigFile, mainConfig); console.log(`[NginxManager] Config written successfully`); console.log(`[NginxManager] Config content preview:\n${mainConfig.substring(0, 500)}...`); return { success: true, message: `Added location for project ${project.name}` }; } catch (error) { console.error(`[NginxManager] addProjectLocation failed: ${error.message}`); console.error(error.stack); return { success: false, message: error.message }; } } removeProjectLocation(projectId) { console.log(`[NginxManager] removeProjectLocation called for project: ${projectId}`); try { this._backupConfig(); const mainConfig = this.generateMainConfig(); if (!fs.existsSync(this.configDir)) { fs.mkdirSync(this.configDir, { recursive: true }); } fs.writeFileSync(this.mainConfigFile, mainConfig); return { success: true, message: `Removed location for project ${projectId}` }; } catch (error) { console.error(`[NginxManager] removeProjectLocation failed: ${error.message}`); return { success: false, message: error.message }; } } testConfig() { try { const testCmd = config.nginxTestCmd || 'nginx -t'; console.log(`[NginxManager] Testing config: ${testCmd}`); execSync(testCmd, { stdio: 'pipe' }); console.log(`[NginxManager] Config test passed`); return { success: true, message: 'Nginx configuration is valid' }; } catch (error) { const errorMessage = error.stderr ? error.stderr.toString() : error.message; console.error(`[NginxManager] Config test failed: ${errorMessage}`); return { success: false, message: `Configuration test failed: ${errorMessage}` }; } } reload() { try { const reloadCmd = config.nginxReloadCmd || 'nginx -s reload'; console.log(`[NginxManager] Reloading Nginx: ${reloadCmd}`); execSync(reloadCmd, { stdio: 'pipe' }); console.log(`[NginxManager] Nginx reloaded successfully`); return { success: true, message: 'Nginx reloaded successfully' }; } catch (error) { const errorMessage = error.stderr ? error.stderr.toString() : error.message; console.error(`[NginxManager] Nginx reload failed: ${errorMessage}`); return { success: false, message: `Nginx reload failed: ${errorMessage}` }; } } rollback() { try { const backupFile = path.join(this.configDir, this.backupFileName); if (!fs.existsSync(backupFile)) { return { success: false, message: 'No backup file found to rollback' }; } fs.copyFileSync(backupFile, this.mainConfigFile); return { success: true, message: 'Configuration rolled back successfully' }; } catch (error) { return { success: false, message: `Rollback failed: ${error.message}` }; } } checkNginxAvailable() { try { execSync('nginx -v', { stdio: 'pipe' }); } catch (error) { return { available: false, reason: 'Nginx command is not available. Please install nginx first.' }; } if (!fs.existsSync(this.configDir)) { try { fs.mkdirSync(this.configDir, { recursive: true }); } catch (error) { return { available: false, reason: `Cannot create config directory: ${error.message}` }; } } try { const testFile = path.join(this.configDir, '.write_test'); fs.writeFileSync(testFile, 'test'); fs.unlinkSync(testFile); } catch (error) { return { available: false, reason: `Config directory is not writable: ${error.message}` }; } return { available: true, reason: 'Nginx is available and ready to use' }; } initConfig() { console.log('[NginxManager] initConfig called'); try { if (!fs.existsSync(this.configDir)) { console.log(`[NginxManager] Creating config directory: ${this.configDir}`); fs.mkdirSync(this.configDir, { recursive: true }); } const mainConfig = this.generateMainConfig(); console.log(`[NginxManager] Writing initial config to: ${this.mainConfigFile}`); fs.writeFileSync(this.mainConfigFile, mainConfig); return { success: true, message: 'Nginx configuration initialized successfully' }; } catch (error) { console.error(`[NginxManager] initConfig failed: ${error.message}`); return { success: false, message: `Failed to initialize config: ${error.message}` }; } } _backupConfig() { if (fs.existsSync(this.mainConfigFile)) { const backupFile = path.join(this.configDir, this.backupFileName); fs.copyFileSync(this.mainConfigFile, backupFile); console.log(`[NginxManager] Config backed up to: ${backupFile}`); } } _getRunningProjects() { // Use absolute path for container environment const projectsFile = '/app/data/projects.json'; console.log(`[NginxManager] Reading projects from: ${projectsFile}`); if (!fs.existsSync(projectsFile)) { console.log(`[NginxManager] Projects file not found: ${projectsFile}`); return []; } try { const content = fs.readFileSync(projectsFile, 'utf8'); const projects = JSON.parse(content); console.log(`[NginxManager] Total projects: ${projects.length}`); const running = projects.filter(p => p.status === 'running' && p.port); console.log(`[NginxManager] Running projects with port: ${running.length}`); return running; } catch (error) { console.error('[NginxManager] Failed to read projects.json:', error.message); return []; } } async safeReload() { console.log('[NginxManager] safeReload called'); const testResult = this.testConfig(); if (!testResult.success) { console.error('[NginxManager] Config test failed, aborting reload'); return testResult; } const reloadResult = this.reload(); if (!reloadResult.success) { console.error('[NginxManager] Reload failed, rolling back'); this.rollback(); return { success: false, message: `Reload failed, configuration rolled back. Error: ${reloadResult.message}` }; } console.log('[NginxManager] safeReload completed successfully'); return reloadResult; } } module.exports = new NginxManager();