369 lines
17 KiB
TypeScript
369 lines
17 KiB
TypeScript
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'
|
||
|
||
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
|
||
}
|
||
|
||
export function registerIpcHandlers(): void {
|
||
// Service management
|
||
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 projectLocations = projects.map(p => {
|
||
const phpPort = portMappingService.getPhpPort(p.phpVersion)
|
||
const normalizedPath = p.path.replace(/\\/g, '/')
|
||
return `
|
||
location /${p.host}/ {
|
||
alias "${normalizedPath}/";
|
||
index index.php index.html;
|
||
try_files $uri $uri/ /${p.host}/index.php?$query_string;
|
||
|
||
# Sadece .php değil, URI'nin tamamını eşleştirip dosya adını $1 içine alıyoruz
|
||
location ~ ^/${p.host}/(.*\.php)$ {
|
||
fastcgi_pass 127.0.0.1:${phpPort};
|
||
fastcgi_index index.php;
|
||
|
||
# Hatalı olan $request_filename yerine manuel path veriyoruz
|
||
fastcgi_param SCRIPT_FILENAME "${normalizedPath}/$1";
|
||
|
||
include "${confDir.replace(/\\/g, '/')}/fastcgi_params";
|
||
}
|
||
}`
|
||
}).join('\n')
|
||
|
||
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: projectLocations
|
||
})
|
||
|
||
// Test config first with prefix
|
||
const testResult = await processManager.runOnce(binPath, ['-t', '-p', nginxPrefix, '-c', configPath], 'nginx')
|
||
if (!testResult) {
|
||
return { success: false, message: 'Nginx konfigürasyon hatası! Log sekmesini kontrol edin.' }
|
||
}
|
||
|
||
const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false)
|
||
return { success, message: success ? 'Nginx başlatıldı.' : 'Nginx devreye alınamadı (Hata loglarını kontrol edin).' }
|
||
}
|
||
|
||
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)
|
||
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.' }
|
||
|
||
// Force kill this specific version if running or generic mysql
|
||
// Actually processManager handles by serviceName
|
||
await processManager.forceKillAll(serviceName)
|
||
|
||
// 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 mysqlPort = mysqlId ? (settings.mysqlPorts?.[mysqlId] || '3306') : settings.mysqlPort
|
||
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)) {
|
||
return { success: false, message: `${mysqlVersion || ''} MySQL başlatıldı ancak çalışma hatası verdi. Lütfen logları ve port çakışmalarını kontrol edin.` }
|
||
}
|
||
}
|
||
|
||
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('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) => {
|
||
return projectService.addProject(project)
|
||
})
|
||
|
||
ipcMain.handle('projects:remove', async (_event, id) => {
|
||
return projectService.removeProject(id)
|
||
})
|
||
ipcMain.handle('projects:update', async (_event, project) => {
|
||
return projectService.updateProject(project)
|
||
})
|
||
|
||
// Config management
|
||
ipcMain.handle('config:get', async () => {
|
||
return configService.getSettings()
|
||
})
|
||
|
||
ipcMain.handle('config:save', async (_event, config: any) => {
|
||
return { success: true, settings: configService.updateSettings(config) }
|
||
})
|
||
|
||
// 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}` }
|
||
}
|
||
})
|
||
}
|