import { ipcMain, app, dialog } from 'electron' import fs from 'fs' import { processManager } from '../services/ProcessManager' import { configService } from '../services/ConfigService' import { downloadService } from '../services/DownloadService' import { projectService } from '../services/ProjectService' import { phpManagerService } from '../services/PhpManagerService' import { mySqlManagerService } from '../services/MySqlManagerService' import { nginxManagerService } from '../services/NginxManagerService' import { phpMyAdminService } from '../services/PhpMyAdminService' import { portMappingService } from '../services/PortMappingService' import path from 'path' import { mySqlOperationService } from '../services/MySqlOperationService' function findExecutable(dir: string, name: string): string | null { if (!fs.existsSync(dir)) return null const files = fs.readdirSync(dir) for (const file of files) { const fullPath = path.join(dir, file) const stat = fs.statSync(fullPath) if (stat.isDirectory()) { const found = findExecutable(fullPath, name) if (found) return found } else if (file.toLowerCase() === name.toLowerCase()) { return fullPath } } return null } async function reloadNginx(): Promise<{ success: boolean; message: string }> { try { const binDir = path.join(configService.getBasePath(), 'bin') const nginxDir = path.join(binDir, 'nginx') const nginxVersions = fs.readdirSync(nginxDir).filter(f => fs.statSync(path.join(nginxDir, f)).isDirectory()) const activeVersion = nginxVersions[0] if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.') const nginxPrefix = path.join(nginxDir, activeVersion) const binPath = path.join(nginxPrefix, 'nginx.exe') const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf') const result = await processManager.runOnce(binPath, ['-p', nginxPrefix, '-c', configPath, '-s', 'reload'], 'nginx') if (result.success) { console.log('Nginx reload successful') return { success: true, message: 'Nginx reloaded' } } else { console.error('Nginx reload failed:', result.output) return { success: false, message: result.output || 'Nginx reload failed' } } } catch (error: any) { return { success: false, message: `Reload hatası: ${error.message}` } } } export function registerIpcHandlers(): void { // Service management ipcMain.handle('services:reload', async (_event, serviceName: string) => { if (serviceName !== 'nginx') return { success: false, message: 'Yalnızca Nginx için reload desteklenmektedir.' } return await reloadNginx() }) ipcMain.handle('services:start', async (_event, serviceName: string) => { try { const settings = configService.getSettings() const binDir = path.join(configService.getBasePath(), 'bin') if (serviceName === 'nginx') { const rootPath = path.join(app.getAppPath(), 'www') const nginxDir = path.join(binDir, 'nginx') const binPath = findExecutable(nginxDir, 'nginx.exe') const logDir = path.join(configService.getBasePath(), 'logs') if (!binPath) return { success: false, message: 'Nginx executable bulunamadı. Lütfen indirin.' } // Nginx prefix = directory containing nginx.exe const nginxPrefix = path.dirname(binPath) const confDir = path.join(nginxPrefix, 'conf') // Ensure temp directories exist (Nginx needs these) const tempDirs = ['temp', 'temp/client_body_temp', 'temp/proxy_temp', 'temp/fastcgi_temp', 'temp/uwsgi_temp', 'temp/scgi_temp', 'logs'] for (const dir of tempDirs) { const fullDir = path.join(nginxPrefix, dir) if (!fs.existsSync(fullDir)) { fs.mkdirSync(fullDir, { recursive: true }) } } const projects = await projectService.getProjects() const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects') if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true }) // Clean up old project configs const existingFiles = fs.readdirSync(projectsConfDir) for (const file of existingFiles) { fs.unlinkSync(path.join(projectsConfDir, file)) } for (const p of projects) { const phpPort = portMappingService.getPhpPort(p.phpVersion) const normalizedPath = p.path.replace(/\\/g, '/') await configService.generateConfig('nginx_project.conf.template', `projects/${p.host}.conf`, { HOST: p.host, PATH: normalizedPath, PHP_PORT: phpPort.toString(), CONF_DIR: confDir.replace(/\\/g, '/') }) } const projectsInclusion = projects.length > 0 ? `include "${projectsConfDir.replace(/\\/g, '/')}/*.conf";` : '' const configPath = await configService.generateConfig('nginx.conf.template', 'nginx.conf', { PORT: settings.nginxPort, PHP_PORT: settings.phpPort, ROOT: rootPath, LOG_DIR: logDir.replace(/\\/g, '/'), CONF_DIR: confDir.replace(/\\/g, '/'), PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/'), PROJECTS: projectsInclusion }) // Test config first with prefix const testResult = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx') if (!testResult.success) { console.error('Nginx config test failed before reload:', testResult.output) return { success: false, message: `Nginx config error: ${testResult.output}` } } const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false) if (!success) { const errorLogs = await processManager.getLastErrorLogs('nginx') return { success: false, message: `Nginx başlatılamadı.\n\n${errorLogs.join('\n')}` } } return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx devreye alınamadı.' } } if (serviceName.startsWith('php')) { const versionMatch = serviceName.match(/^php:(.+)$/) const phpVersion = versionMatch ? versionMatch[1] : null const phpId = phpVersion ? `php-${phpVersion}` : null const phpDir = phpId ? path.join(binDir, phpId) : binDir const binPath = findExecutable(phpDir, 'php-cgi.exe') if (!binPath) return { success: false, message: `${phpVersion || ''} PHP executable bulunamadı. Lütfen indirin.` } const phpPort = phpVersion ? portMappingService.getPhpPort(phpVersion) : settings.phpPort const extDir = path.join(path.dirname(binPath), 'ext').replace(/\\/g, '/') const gdExtName = fs.existsSync(path.join(extDir, 'php_gd2.dll')) ? 'gd2' : 'gd' const sessionDir = path.join(configService.getBasePath(), 'data', 'sessions').replace(/\\/g, '/') const tempDir = path.join(configService.getBasePath(), 'data', 'temp').replace(/\\/g, '/') if (!fs.existsSync(sessionDir)) fs.mkdirSync(sessionDir, { recursive: true }) if (!fs.existsSync(tempDir)) fs.mkdirSync(tempDir, { recursive: true }) const configPath = await configService.generateConfig('php.ini.template', `php_${phpVersion || 'default'}.ini`, { EXT_DIR: extDir, GD_EXT: gdExtName, SESSION_DIR: sessionDir, TEMP_DIR: tempDir }) const success = await processManager.startService(serviceName, binPath, ['-b', `127.0.0.1:${phpPort}`, '-c', configPath], false) if (!success) { const errorLogs = await processManager.getLastErrorLogs(serviceName) return { success: false, message: `${phpVersion || ''} PHP başlatılamadı.\n\n${errorLogs.join('\n')}` } } return { success, message: success ? `${phpVersion || ''} PHP başlatıldı.` : `${phpVersion || ''} PHP devreye alınamadı.` } } if (serviceName.startsWith('mysql')) { const versionMatch = serviceName.match(/^mysql:(.+)$/) const mysqlVersion = versionMatch ? versionMatch[1] : null const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null const dataDir = mysqlId ? path.join(configService.getBasePath(), 'data', mysqlId) : path.join(configService.getBasePath(), 'data', 'default') const mysqlDir = mysqlId ? path.join(binDir, mysqlId) : binDir const binPath = findExecutable(mysqlDir, 'mysqld.exe') if (!binPath) return { success: false, message: 'MySQL executable bulunamadı. Lütfen indirin.' } const mysqlVersions = await mySqlManagerService.getVersions() const mysqlInfo = mysqlVersions.find(v => v.id === mysqlId) const mysqlPort = mysqlInfo?.port || (mysqlId ? '3306' : settings.mysqlPort) // Force kill this specific version if running or generic mysql await processManager.forceKillAll(serviceName) // Also kill anything else listening on the same port await processManager.killByPort(serviceName, mysqlPort) // Initialize if data directory is empty if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0) { if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true }) console.log(`Initializing MySQL data directory for ${serviceName}...`) try { // Try modern initialization first (5.7+) await processManager.runOnce(binPath, ['--initialize-insecure', `--datadir=${dataDir.replace(/\\/g, '/')}`]) } catch (err) { console.log(`Modern init failed, trying template copy for ${serviceName}...`) // Fallback for 5.6: Copy default 'data' folder from binary dir if it exists const templateDataDir = path.join(mysqlDir, 'data') if (fs.existsSync(templateDataDir)) { // Recursively copy template data (mysql, performance_schema, etc.) fs.cpSync(templateDataDir, dataDir, { recursive: true }) console.log(`Successfully copied template data for ${serviceName}`) } else { throw new Error(`MySQL initialization failed and no template data found at ${templateDataDir}`) } } } const sanitizedServiceName = serviceName.replace(/:/g, '_') const logFile = path.join(configService.getBasePath(), 'logs', `${sanitizedServiceName}_error.log`) const configPath = await configService.generateConfig('my.ini.template', `my_${mysqlId || 'default'}.ini`, { PORT: mysqlPort, DATADIR: dataDir.replace(/\\/g, '/'), SOCKET: `/tmp/${sanitizedServiceName}.sock`, LOG_FILE: logFile.replace(/\\/g, '/') }) const success = await processManager.startService(serviceName, binPath, ['--defaults-file=' + configPath], false) if (success) { // Quick health check for MySQL await new Promise(r => setTimeout(r, 1500)) if (!processManager.isServiceRunning(serviceName)) { const errorLogs = await processManager.getLastErrorLogs(serviceName) return { success: false, message: `${mysqlVersion || ''} MySQL başlatıldı ancak çalışma hatası verdi (ExitCode: 1).\n\n${errorLogs.length > 0 ? 'Önemli Hata Logları:\n' + errorLogs.join('\n') : 'Lütfen log sekmesini kontrol edin.'}` } } } else { const errorLogs = await processManager.getLastErrorLogs(serviceName) return { success: false, message: `${mysqlVersion || ''} MySQL başlatılamadı.\n\n${errorLogs.join('\n')}` } } return { success, message: success ? `${mysqlVersion || ''} MySQL başlatıldı.` : `${mysqlVersion || ''} MySQL devreye alınamadı.` } } return { success: false, message: 'Bilinmeyen servis.' } } catch (error: any) { console.error('Service start error:', error) return { success: false, message: `Hata: ${error.message}` } } }) ipcMain.handle('services:stop', async (_event, serviceName: string) => { const success = await processManager.stopService(serviceName) return { success, message: success ? `${serviceName} durduruldu.` : `${serviceName} zaten durmuş.` } }) ipcMain.handle('services:status', async () => { return processManager.getStatuses() }) ipcMain.handle('services:logs', async (_event, serviceName: string) => { return processManager.getLogs(serviceName) }) // Binary management ipcMain.handle('binaries:list-php', async () => { return phpManagerService.getVersions() }) ipcMain.handle('binaries:download-php', async (event, id: string) => { const versions = await phpManagerService.getVersions() const version = versions.find(v => v.id === id) if (!version) return { success: false, message: 'Geçersiz sürüm.' } try { // Signal immediate start phpManagerService.updateProgress(id, 0, 'downloading') event.sender.send('binaries:progress', { id, progress: 0 }) await downloadService.downloadAndExtract(version.url, id, (p) => { phpManagerService.updateProgress(id, p) event.sender.send('binaries:progress', { id, progress: p }) }) phpManagerService.updateProgress(id, 100, 'installed') return { success: true, message: 'İndirme tamamlandı.' } } catch (error: any) { phpManagerService.updateProgress(id, 0, 'available') return { success: false, message: `Hata: ${error.message}` } } }) // MySQL Versions ipcMain.handle('mysql:versions', async () => { return await mySqlManagerService.getVersions() }) ipcMain.handle('mysql:download', async (event, id: string) => { const versions = await mySqlManagerService.getVersions() const version = versions.find(v => v.id === id) if (!version) return { success: false, message: 'Geçersiz MySQL sürümü.' } try { mySqlManagerService.updateProgress(id, 0, 'downloading') event.sender.send('mysql:progress', { id, progress: 0 }) await downloadService.downloadAndExtract(version.url, id, (p) => { mySqlManagerService.updateProgress(id, p) event.sender.send('mysql:progress', { id, progress: p }) }) mySqlManagerService.updateProgress(id, 100, 'installed') return { success: true, message: 'MySQL indirme tamamlandı.' } } catch (error: any) { mySqlManagerService.updateProgress(id, 0, 'available') return { success: false, message: `MySQL hatası: ${error.message}` } } }) // Nginx Versions ipcMain.handle('nginx:versions', async () => { return await nginxManagerService.getVersions() }) ipcMain.handle('nginx:download', async (event, id: string) => { const versions = await nginxManagerService.getVersions() const version = versions.find(v => v.id === id) if (!version) return { success: false, message: 'Geçersiz Nginx sürümü.' } try { nginxManagerService.updateProgress(id, 0, 'downloading') event.sender.send('nginx:progress', { id, progress: 0 }) await downloadService.downloadAndExtract(version.url, id, (p) => { nginxManagerService.updateProgress(id, p) event.sender.send('nginx:progress', { id, progress: p }) }) nginxManagerService.updateProgress(id, 100, 'installed') return { success: true, message: 'Nginx indirme tamamlandı.' } } catch (error: any) { nginxManagerService.updateProgress(id, 0, 'available') return { success: false, message: `Nginx hatası: ${error.message}` } } }) ipcMain.handle('dialog:select-directory', async () => { const result = await dialog.showOpenDialog({ properties: ['openDirectory'] }) if (result.canceled) return null return result.filePaths[0] }) ipcMain.handle('dialog:select-file', async (_event, filters: any[]) => { const result = await dialog.showOpenDialog({ properties: ['openFile'], filters: filters || [ { name: 'SQL & Archives', extensions: ['sql', 'zip', 'gz', 'tar'] } ] }) if (result.canceled) return null return result.filePaths[0] }) ipcMain.handle('binaries:download', async (_event, { name, url }) => { return downloadService.downloadAndExtract(url, name, (p) => { // TODO: Emit progress to renderer via webContents.send console.log(`Download progress for ${name}: ${p}%`) }) }) // Project management ipcMain.handle('projects:list', async () => { return projectService.getProjects() }) ipcMain.handle('projects:add', async (_event, project) => { const result = await projectService.addProject(project) // Trigger Nginx reload await ipcMain.emit('services:reload', null, 'nginx') return result }) ipcMain.handle('projects:remove', async (_event, id) => { const result = projectService.removeProject(id) // Trigger Nginx reload await ipcMain.emit('services:reload', null, 'nginx') return result }) ipcMain.handle('projects:update', async (_event, project) => { const result = projectService.updateProject(project) // Trigger Nginx reload await ipcMain.emit('services:reload', null, 'nginx') return result }) // Config management ipcMain.handle('config:get', async () => { return configService.getSettings() }) ipcMain.handle('config:save', async (_event, config: any) => { const settings = configService.updateSettings(config) return { success: true, settings } }) // Nginx Config Management ipcMain.handle('nginx:config:get', async () => { const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf') if (fs.existsSync(configPath)) { return fs.readFileSync(configPath, 'utf8') } return '' }) ipcMain.handle('nginx:config:save', async (_event, content) => { const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf') fs.writeFileSync(configPath, content) // Reload Nginx after save return await reloadNginx() }) ipcMain.handle('nginx:config:test', async () => { try { const binDir = path.join(configService.getBasePath(), 'bin', 'nginx') const nginxVersions = fs.readdirSync(binDir).filter(f => fs.statSync(path.join(binDir, f)).isDirectory()) const activeVersion = nginxVersions[0] if (!activeVersion) throw new Error('Nginx not found') const nginxPrefix = path.join(binDir, activeVersion) const binPath = path.join(nginxPrefix, 'nginx.exe') const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf') const result = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx') return { success: result.success, message: result.success ? 'Configuration test successful' : result.output } } catch (error: any) { return { success: false, message: error.message } } }) // Project Nginx Config ipcMain.handle('project:nginx:get', async (_event, host) => { const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`) if (fs.existsSync(configPath)) { return fs.readFileSync(configPath, 'utf8') } return '' }) ipcMain.handle('project:nginx:test', async (_event, host, content) => { const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`) let originalContent = '' if (fs.existsSync(configPath)) { originalContent = fs.readFileSync(configPath, 'utf8') } try { // 1. Save new content temporarily fs.writeFileSync(configPath, content) // 2. Run global test const binDir = path.join(configService.getBasePath(), 'bin', 'nginx') const nginxVersions = fs.readdirSync(binDir).filter(f => fs.statSync(path.join(binDir, f)).isDirectory()) const activeVersion = nginxVersions[0] if (!activeVersion) throw new Error('Nginx sürümü bulunamadı.') const nginxPrefix = path.join(binDir, activeVersion) const binPath = path.join(nginxPrefix, 'nginx.exe') const globalConfigPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf') const result = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', globalConfigPath], 'nginx') // 3. Restore original content regardless of test result // (The user must click "Save" to keep it) if (originalContent) { fs.writeFileSync(configPath, originalContent) } else { fs.unlinkSync(configPath) } return { success: result.success, message: result.success ? 'Configuration test successful' : result.output } } catch (error: any) { // Restore on error too if (originalContent) fs.writeFileSync(configPath, originalContent) return { success: false, message: error.message } } }) ipcMain.handle('project:nginx:save', async (_event, host, content) => { const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`) fs.writeFileSync(configPath, content) // Reload Nginx after save return await reloadNginx() }) // phpMyAdmin ipcMain.handle('pma:status', async () => { return await phpMyAdminService.isInstalled() }) ipcMain.handle('pma:setup', async (event, mysqlPort: string) => { return await phpMyAdminService.setup(mysqlPort, (p) => { event.sender.send('pma:progress', p) }) }) ipcMain.handle('services:reset-data', async (_event, serviceName: string) => { try { await processManager.forceKillAll(serviceName) const versionMatch = serviceName.match(/^mysql:(.+)$/) const mysqlVersion = versionMatch ? versionMatch[1] : null const mysqlId = mysqlVersion ? `mysql-${mysqlVersion}` : null if (!mysqlId) return { success: false, message: 'Geçersiz servis adı.' } const dataDir = path.join(configService.getBasePath(), 'data', mysqlId) if (fs.existsSync(dataDir)) { fs.rmSync(dataDir, { recursive: true, force: true }) } return { success: true, message: `${serviceName} verileri sıfırlandı. Tekrar başlatmayı deneyebilirsiniz.` } } catch (error: any) { return { success: false, message: `Sıfırlama hatası: ${error.message}` } } }) ipcMain.handle('mysql:import-wizard:check-db', async (_event, { versionId, dbName, rootPass }) => { const versions = await mySqlManagerService.getVersions() const v = versions.find(v => v.id === versionId) if (!v) return { exists: false, error: 'MySQL version not found' } const binDir = path.join(configService.getBasePath(), 'bin', versionId) const binPath = findExecutable(binDir, 'mysql.exe') if (!binPath) return { exists: false, error: 'mysql.exe not found' } const exists = await mySqlOperationService.checkDatabaseExists({ binPath, host: '127.0.0.1', port: v.port, rootUser: 'root', rootPass: rootPass || '' }, dbName) return { exists } }) ipcMain.handle('mysql:import-wizard:start', async (event, { versionId, filePath, dbName, user, pass, rootPass, overwrite }) => { const versions = await mySqlManagerService.getVersions() const v = versions.find(v => v.id === versionId) if (!v) return { success: false, message: 'MySQL version not found' } const binDir = path.join(configService.getBasePath(), 'bin', versionId) const mysqlBin = findExecutable(binDir, 'mysql.exe') if (!mysqlBin) return { success: false, message: 'mysql.exe not found' } const config = { binPath: mysqlBin, host: '127.0.0.1', port: v.port, rootUser: 'root', rootPass: rootPass || '' } const log = (msg: string) => event.sender.send('mysql:import:log', msg) try { if (overwrite) { log(`Dropping existing database: ${dbName}`) await mySqlOperationService.dropDatabase(config, dbName) } log(`Creating database and user: ${dbName} / ${user}`) const createRes = await mySqlOperationService.createDatabaseAndUser(config, dbName, user, pass) if (!createRes.success) return createRes let sqlFiles: string[] = [] if (filePath.toLowerCase().endsWith('.sql')) { sqlFiles = [filePath] } else { log(`Extracting archive: ${path.basename(filePath)}`) const extractDir = path.join(app.getPath('temp'), `mysql_import_${Date.now()}`) sqlFiles = await mySqlOperationService.extractArchive(filePath, extractDir) if (sqlFiles.length === 0) return { success: false, message: 'No .sql files found in archive.' } } for (const sqlFile of sqlFiles) { log(`Importing: ${path.basename(sqlFile)}`) const importRes = await mySqlOperationService.importSql(config, dbName, sqlFile, (msg) => log(msg)) if (!importRes.success) return importRes } return { success: true, message: 'Import completed successfully.' } } catch (error: any) { return { success: false, message: `Import error: ${error.message}` } } }) }