Files
multiphp/src/main/ipc/index.ts
T

369 lines
17 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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}` }
}
})
}