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

942 lines
43 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, shell } 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 { mariaDbManagerService } from '../services/MariaDbManagerService'
import { nginxManagerService } from '../services/NginxManagerService'
import { apacheManagerService } from '../services/ApacheManagerService'
import { phpMyAdminService } from '../services/PhpMyAdminService'
import { portMappingService } from '../services/PortMappingService'
import path from 'path'
import { mariaDbOperationService } from '../services/MariaDbOperationService'
import { cliAliasService } from '../services/CliAliasService'
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 rebuildNginxConfig(forceProjects: boolean = false): Promise<{ binPath: string; nginxPrefix: string; configPath: string }> {
const settings = configService.getSettings()
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 isWindows = process.platform === 'win32';
const binPath = path.join(nginxPrefix, isWindows ? 'nginx.exe' : 'nginx')
const confDir = path.join(nginxPrefix, 'conf')
const logDir = path.join(configService.getBasePath(), 'logs')
const rootPath = path.join(app.getAppPath(), 'www')
// Ensure directories exist
const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects')
if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true })
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 })
}
// Generate project configs
const allProjects = await projectService.getProjects()
const projects = allProjects.filter(p => !p.serverType || p.serverType === 'nginx')
for (const p of projects) {
const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`)
let shouldGenerate = forceProjects || !fs.existsSync(projectConfPath)
// If file exists, check if it was manually edited
if (!shouldGenerate && fs.existsSync(projectConfPath)) {
const currentContent = fs.readFileSync(projectConfPath, 'utf8')
if (!currentContent.includes('# MANUAL_EDIT')) {
// Overwrite old, broken configs that don't have the marker
shouldGenerate = true
}
}
if (shouldGenerate) {
const phpPort = portMappingService.getPhpPort(p.phpVersion)
// Ensure path ends with a slash for alias compatibility
let normalizedPath = p.path.replace(/\\/g, '/')
if (!normalizedPath.endsWith('/')) {
normalizedPath += '/'
}
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,
LISTEN_ADDRESS: settings.allowRemoteAccess ? '0.0.0.0' : '127.0.0.1',
PHP_PORT: settings.phpPort,
ROOT: rootPath,
LOG_DIR: logDir.replace(/\\/g, '/'),
CONF_DIR: confDir.replace(/\\/g, '/'),
PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/'),
PROJECTS: projectsInclusion
})
return { binPath, nginxPrefix, configPath }
}
async function restartNginx(): Promise<{ success: boolean; message: string }> {
try {
const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false)
// Force kill Nginx to ensure it picks up new config on Windows
await processManager.forceKillAll('nginx')
await processManager.stopService('nginx')
// Give OS time to release the port
await new Promise(resolve => setTimeout(resolve, 1000))
// Start service
const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false)
if (success) {
return { success: true, message: 'Nginx restart successful' }
} else {
const errorLogs = await processManager.getLastErrorLogs('nginx')
return { success: false, message: `Nginx restart failed:\n${errorLogs.join('\n')}` }
}
} catch (error: any) {
return { success: false, message: `Restart hatası: ${error.message}` }
}
}
async function reloadNginx(): Promise<{ success: boolean; message: string }> {
try {
const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false)
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 {
// Fallback to full restart if reload fails (common on Windows)
return await restartNginx()
}
} catch (error: any) {
return { success: false, message: `Reload hatası: ${error.message}` }
}
}
async function rebuildApacheConfig(forceProjects: boolean = false): Promise<{ binPath: string; apacheDir: string; configPath: string }> {
const settings = configService.getSettings()
const binDir = path.join(configService.getBasePath(), 'bin')
const apacheDir = path.join(binDir, 'apache')
const isWindows = process.platform === 'win32';
const binPath = findExecutable(apacheDir, isWindows ? 'httpd.exe' : 'httpd')
if (!binPath) throw new Error('Apache executable bulunamadı.')
const apacheRoot = path.dirname(path.dirname(binPath)) // bin'in üstü
const logDir = path.join(configService.getBasePath(), 'logs')
const rootPath = path.join(app.getAppPath(), 'www')
// Ensure directories exist
const projectsConfDir = path.join(configService.getBasePath(), 'generated_configs', 'projects_apache')
if (!fs.existsSync(projectsConfDir)) fs.mkdirSync(projectsConfDir, { recursive: true })
// Generate project configs
const allProjects = await projectService.getProjects()
const projects = allProjects.filter(p => p.serverType === 'apache')
for (const p of projects) {
const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`)
let shouldGenerate = forceProjects || !fs.existsSync(projectConfPath)
if (shouldGenerate) {
const phpPort = portMappingService.getPhpPort(p.phpVersion)
let normalizedPath = p.path.replace(/\\/g, '/')
if (!normalizedPath.endsWith('/')) normalizedPath += '/'
await configService.generateConfig('apache_project.conf.template', `projects_apache/${p.host}.conf`, {
HOST: p.host,
PATH: normalizedPath,
PHP_PORT: phpPort.toString()
})
}
}
const projectsInclusion = projects.length > 0
? `Include "${projectsConfDir.replace(/\\/g, '/')}/*.conf"`
: ''
const configPath = await configService.generateConfig('httpd.conf.template', 'httpd.conf', {
PORT: settings.apachePort || '8080',
LISTEN_ADDRESS: settings.allowRemoteAccess ? '0.0.0.0' : '127.0.0.1',
PHP_PORT: settings.phpPort,
ROOT: rootPath.replace(/\\/g, '/'),
SRVROOT: apacheRoot.replace(/\\/g, '/'),
LOG_DIR: logDir.replace(/\\/g, '/'),
PHPMYADMIN_DIR: phpMyAdminService.getPath().replace(/\\/g, '/'),
PROJECTS: projectsInclusion
})
return { binPath, apacheDir, configPath }
}
async function restartApache(): Promise<{ success: boolean; message: string }> {
try {
const { binPath, configPath } = await rebuildApacheConfig(false)
await processManager.forceKillAll('apache')
await processManager.stopService('apache')
await new Promise(resolve => setTimeout(resolve, 1000))
const success = await processManager.startService('apache', binPath, ['-f', configPath], false)
if (success) {
return { success: true, message: 'Apache restart successful' }
} else {
const errorLogs = await processManager.getLastErrorLogs('apache')
return { success: false, message: `Apache restart failed:\n${errorLogs.join('\n')}` }
}
} catch (error: any) {
return { success: false, message: `Restart hatası: ${error.message}` }
}
}
export function registerIpcHandlers(): void {
// Service management
ipcMain.handle('services:reload', async (_event, serviceName: string) => {
if (serviceName === 'nginx') return await reloadNginx()
if (serviceName === 'apache') return await restartApache() // Apache doesn't have a simple reload here
return { success: false, message: 'Bu servis için reload desteklenmiyor.' }
})
ipcMain.handle('services:start', async (_event, serviceName: string) => {
try {
const settings = configService.getSettings()
const binDir = path.join(configService.getBasePath(), 'bin')
if (serviceName === 'nginx') {
const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false)
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 === 'apache') {
const { binPath, configPath } = await rebuildApacheConfig(false)
const success = await processManager.startService('apache', binPath, ['-f', configPath], false)
if (!success) {
const errorLogs = await processManager.getLastErrorLogs('apache')
return { success: false, message: `Apache başlatılamadı.\n\n${errorLogs.join('\n')}` }
}
return { success, message: success ? 'Apache başlatıldı.' : 'Apache 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 isWindows = process.platform === 'win32';
const phpDir = phpId ? path.join(binDir, phpId) : binDir
const phpBinary = isWindows ? 'php-cgi.exe' : 'php-cgi';
const binPath = findExecutable(phpDir, phpBinary)
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 sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms))
// --- 1. Get extensions from local php.ini ---
let extensionsList = ''
let errorReporting = 'E_ALL'
let displayErrors = 'On'
const localIniPath = path.join(phpDir, 'php.ini')
const mustHaves = ['curl', 'mbstring', 'openssl', 'mysqli', 'pdo_mysql', 'gd', 'fileinfo', 'exif', 'intl', 'bz2', 'gettext', 'soap', 'xsl']
if (fs.existsSync(localIniPath)) {
const localIniContent = fs.readFileSync(localIniPath, 'utf8')
// 1. Get extensions
const extMatches = localIniContent.match(/^\s*extension\s*=.*$/gm)
if (extMatches) {
extensionsList = extMatches.join('\n')
}
// 2. Get error settings
const erMatch = localIniContent.match(/^\s*error_reporting\s*=\s*(.+)$/m)
if (erMatch) errorReporting = erMatch[1].trim()
const deMatch = localIniContent.match(/^\s*display_errors\s*=\s*(.+)$/m)
if (deMatch) displayErrors = deMatch[1].trim()
} else {
// Fallback formatting based on version
const prefix = (phpVersion?.startsWith('5.') || phpVersion?.startsWith('7.')) ? 'php_' : ''
const suffix = (phpVersion?.startsWith('5.') || phpVersion?.startsWith('7.')) ? '.dll' : ''
extensionsList = mustHaves.map(m => `extension=${prefix}${m}${suffix}`).join('\n')
}
const configPath = await configService.generateConfig('php.ini.template', `php_${phpVersion || 'default'}.ini`, {
EXT_DIR: extDir,
GD_EXT: gdExtName,
SESSION_DIR: sessionDir,
TEMP_DIR: tempDir,
EXTENSIONS: extensionsList,
ERROR_REPORTING: errorReporting,
DISPLAY_ERRORS: displayErrors
})
// Add a small delay to ensure previous instance (if any) is fully killed
await sleep(1000)
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('mariadb') || serviceName.startsWith('mysql')) {
const versionMatch = serviceName.match(/^(?:mariadb|mysql):(.+)$/)
const dbVersion = versionMatch ? versionMatch[1] : null
const dbId = dbVersion ? `mariadb-${dbVersion}` : 'mariadb-default'
const dataDir = path.join(configService.getBasePath(), 'data', dbId)
const dbBinDir = dbVersion ? path.join(binDir, `mariadb-${dbVersion}`) : binDir
const isWindows = process.platform === 'win32';
const dbBinary = isWindows ? 'mariadbd.exe' : 'mariadbd';
const installBinary = isWindows ? 'mariadb-install-db.exe' : 'mariadb-install-db';
const mysqlInstallBinary = isWindows ? 'mysql_install_db.exe' : 'mysql_install_db';
let binPath = findExecutable(dbBinDir, dbBinary) || findExecutable(dbBinDir, isWindows ? 'mysqld.exe' : 'mysqld');
let installPath = findExecutable(dbBinDir, installBinary) || findExecutable(dbBinDir, mysqlInstallBinary);
if (!binPath) return { success: false, message: 'MariaDB executable bulunamadı. Lütfen indirin.' }
const dbVersions = await mariaDbManagerService.getVersions()
const dbInfo = dbVersions.find(v => v.id === `mariadb-${dbVersion}`)
const dbPort = dbInfo?.port || (dbVersion ? portMappingService.getMariaDbPort(dbVersion).toString() : settings.mariaDbPort)
// 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, dbPort)
// Initialize if data directory is empty or missing system tables
const systemDbDir = path.join(dataDir, 'mysql')
if (!fs.existsSync(dataDir) || fs.readdirSync(dataDir).length === 0 || !fs.existsSync(systemDbDir)) {
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true })
console.log(`Initializing MariaDB data directory for ${serviceName}...`)
try {
const normalizedDataDir = dataDir.replace(/\\/g, '/')
if (installPath) {
console.log(`Using install tool: ${installPath}`)
// MariaDB specific installation
await processManager.runOnce(installPath, [
`--datadir=${normalizedDataDir}`
])
} else {
console.log(`Using server init: ${binPath}`)
// MySQL 5.7+ / modern MariaDB fallback
await processManager.runOnce(binPath, ['--initialize-insecure', `--datadir=${normalizedDataDir}`])
}
} catch (err) {
console.log(`Initialization tool failed, trying template copy for ${serviceName}...`)
// Fallback: Copy default 'data' folder from binary dir if it exists
const templateDataDir = path.join(dbBinDir, 'data')
if (fs.existsSync(templateDataDir)) {
fs.cpSync(templateDataDir, dataDir, { recursive: true })
console.log(`Successfully copied template data for ${serviceName}`)
} else {
throw new Error(`MariaDB 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('mariadb.ini.template', `mariadb_${dbId || 'default'}.ini`, {
PORT: dbPort,
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
await new Promise(r => setTimeout(r, 1500))
if (!processManager.isServiceRunning(serviceName)) {
const errorLogs = await processManager.getLastErrorLogs(serviceName)
return { success: false, message: `${dbVersion || ''} MariaDB 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: `${dbVersion || ''} MariaDB başlatılamadı.\n\n${errorLogs.join('\n')}` }
}
return { success, message: success ? `${dbVersion || ''} MariaDB başlatıldı.` : `${dbVersion || ''} MariaDB 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')
// Regenerate aliases after install
await cliAliasService.generateAliases()
return { success: true, message: 'İndirme tamamlandı.' }
} catch (error: any) {
phpManagerService.updateProgress(id, 0, 'available')
return { success: false, message: `Hata: ${error.message}` }
}
})
ipcMain.handle('php:extensions:get', async (_event, version: string) => {
return await phpManagerService.getExtensions(version)
})
ipcMain.handle('php:extensions:toggle', async (_event, { version, extName, enabled }) => {
const result = await phpManagerService.toggleExtension(version, extName, enabled)
if (result.success) {
const serviceName = `php:${version}`
if (processManager.isServiceRunning(serviceName)) {
await processManager.stopService(serviceName)
result.message += " (Service stopped for restart)"
}
}
return result
})
ipcMain.handle('php:config:get', async (_event, version: string) => {
const phpDir = path.join(configService.getBasePath(), 'bin', `php-${version}`)
const iniPath = path.join(phpDir, 'php.ini')
if (fs.existsSync(iniPath)) {
return {
content: fs.readFileSync(iniPath, 'utf8'),
path: iniPath
}
}
return { content: '', path: iniPath }
})
ipcMain.handle('php:config:save', async (_event, { version, content }) => {
const phpDir = path.join(configService.getBasePath(), 'bin', `php-${version}`)
const iniPath = path.join(phpDir, 'php.ini')
fs.writeFileSync(iniPath, content)
const serviceName = `php:${version}`
if (processManager.isServiceRunning(serviceName)) {
await processManager.stopService(serviceName)
}
return { success: true, message: 'Configuration saved.' }
})
// MariaDB Versions
ipcMain.handle('mariadb:versions', async () => {
return await mariaDbManagerService.getVersions()
})
ipcMain.handle('mariadb:download', async (event, id: string) => {
const versions = await mariaDbManagerService.getVersions()
const version = versions.find(v => v.id === id)
if (!version) return { success: false, message: 'Geçersiz MariaDB sürümü.' }
try {
mariaDbManagerService.updateProgress(id, 0, 'downloading')
event.sender.send('mariadb:progress', { id, progress: 0 })
await downloadService.downloadAndExtract(version.url, id, (p) => {
mariaDbManagerService.updateProgress(id, p)
event.sender.send('mariadb:progress', { id, progress: p })
})
mariaDbManagerService.updateProgress(id, 100, 'installed')
// Regenerate aliases after install
await cliAliasService.generateAliases()
return { success: true, message: 'MariaDB indirme tamamlandı.' }
} catch (error: any) {
mariaDbManagerService.updateProgress(id, 0, 'available')
return { success: false, message: `MariaDB hatası: ${error.message}` }
}
})
// Apache Versions
ipcMain.handle('apache:versions', async () => {
return await apacheManagerService.getVersions()
})
ipcMain.handle('apache:download', async (event, id: string) => {
const versions = await apacheManagerService.getVersions()
const version = versions.find(v => v.id === id)
if (!version) return { success: false, message: 'Geçersiz Apache sürümü.' }
try {
apacheManagerService.updateProgress(id, 0, 'downloading')
event.sender.send('apache:progress', { id, progress: 0 })
await downloadService.downloadAndExtract(version.url, id, (p) => {
apacheManagerService.updateProgress(id, p)
event.sender.send('apache:progress', { id, progress: p })
})
apacheManagerService.updateProgress(id, 100, 'installed')
return { success: true, message: 'Apache indirme tamamlandı.' }
} catch (error: any) {
apacheManagerService.updateProgress(id, 0, 'available')
return { success: false, message: `Apache 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('dialog:save-file', async (_event, filters: any[]) => {
const result = await dialog.showSaveDialog({
filters: filters || [
{ name: 'SQL File', extensions: ['sql'] },
{ name: 'Compressed SQL', extensions: ['sql.gz'] }
]
})
if (result.canceled) return null
return result.filePath
})
ipcMain.handle('shell:open-path', async (_event, pathStr: string) => {
return await shell.openPath(pathStr)
})
ipcMain.handle('binaries:download', async (_event, { name, url }) => {
const result = await downloadService.downloadAndExtract(url, name, (p) => {
// TODO: Emit progress to renderer via webContents.send
console.log(`Download progress for ${name}: ${p}%`)
})
await cliAliasService.generateAliases()
return result
})
ipcMain.handle('cli:generate-aliases', async () => {
try {
await cliAliasService.generateAliases()
return {
success: true,
message: 'CLI aliasları başarıyla oluşturuldu.',
paths: {
powershell: configService.getGeneratedPath('aliases.ps1'),
bash: configService.getGeneratedPath('aliases.sh')
}
}
} catch (error: any) {
return { success: false, message: `Hata: ${error.message}` }
}
})
// 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 restart to generate initial config and apply
await restartNginx()
return result
})
ipcMain.handle('projects:remove', async (_event, id) => {
const result = projectService.removeProject(id)
// Trigger Nginx restart to remove config from glob inclusion indirectly
// Actually we might want to delete the .conf file too
const projects = await projectService.getProjects()
const projectToRemove = projects.find(p => p.id === id)
if (projectToRemove) {
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${projectToRemove.host}.conf`)
if (fs.existsSync(configPath)) fs.unlinkSync(configPath)
}
await restartNginx()
return result
})
ipcMain.handle('projects:update', async (_event, project) => {
// Clean up old config file if host changed
const projects = projectService.getProjects()
const oldProject = projects.find(p => p.id === project.id)
if (oldProject && oldProject.host !== project.host) {
const oldConfigPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${oldProject.host}.conf`)
if (fs.existsSync(oldConfigPath)) {
try { fs.unlinkSync(oldConfigPath) } catch (e) { console.error('Error deleting old config:', e) }
}
}
const result = projectService.updateProject(project)
// Trigger Nginx restart
await restartNginx()
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)
// Full restart after manual global save
return await restartNginx()
})
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`)
// Ensure manual edit marker is present
let finalContent = content
if (!finalContent.includes('# MANUAL_EDIT')) {
finalContent = `# MANUAL_EDIT\n${finalContent}`
}
fs.writeFileSync(configPath, finalContent)
return await restartNginx()
})
// phpMyAdmin
ipcMain.handle('pma:status', async () => {
return await phpMyAdminService.isInstalled()
})
ipcMain.handle('pma:setup', async (event, mariaDbPort: string) => {
return await phpMyAdminService.setup(mariaDbPort, (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(/^(?:mariadb|mysql):(.+)$/)
const dbVersion = versionMatch ? versionMatch[1] : null
const dbId = dbVersion ? `mariadb-${dbVersion}` : null
if (!dbId) return { success: false, message: 'Geçersiz servis adı.' }
const dataDir = path.join(configService.getBasePath(), 'data', dbId)
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}` }
}
})
// MariaDB Operations
ipcMain.handle('mariadb:import-wizard:check-db', async (_event, { versionId, dbName, rootPass }) => {
const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId)
if (!v) return { exists: false, error: 'MariaDB version not found' }
const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const isWindows = process.platform === 'win32'
const binPath = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!binPath) return { exists: false, error: 'mariadb/mysql executable not found' }
const exists = await mariaDbOperationService.checkDatabaseExists({
binPath,
host: '127.0.0.1',
port: v.port,
rootUser: 'root',
rootPass: rootPass || ''
}, dbName)
return { exists }
})
ipcMain.handle('mariadb:import-wizard:start', async (event, { versionId, filePath, dbName, user, pass, rootPass, overwrite }) => {
const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId)
if (!v) return { success: false, message: 'MariaDB version not found' }
const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const isWindows = process.platform === 'win32'
const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return { success: false, message: 'mariadb/mysql executable not found' }
const config = {
binPath: dbBin,
host: '127.0.0.1',
port: v.port,
rootUser: 'root',
rootPass: rootPass || ''
}
const log = (msg: string) => event.sender.send('mariadb:import:log', msg)
try {
if (overwrite) {
log(`Dropping existing database: ${dbName}`)
await mariaDbOperationService.dropDatabase(config, dbName)
}
log(`Creating database and user: ${dbName} / ${user}`)
const createRes = await mariaDbOperationService.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'), `mariadb_import_${Date.now()}`)
sqlFiles = await mariaDbOperationService.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 mariaDbOperationService.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}` }
}
})
ipcMain.handle('mariadb:export-wizard:list-dbs', async (_event, { versionId, rootPass }) => {
const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId)
if (!v) return []
const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const isWindows = process.platform === 'win32'
const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return []
return await mariaDbOperationService.listDatabases({
binPath: dbBin,
host: '127.0.0.1',
port: v.port,
rootUser: 'root',
rootPass: rootPass || ''
})
})
ipcMain.handle('mariadb:export-wizard:start', async (event, { versionId, dbName, savePath, compress, rootPass }) => {
const versions = await mariaDbManagerService.getVersions()
const v = versions.find(v => v.id === versionId)
if (!v) return { success: false, message: 'MariaDB version not found' }
const binDir = path.join(configService.getBasePath(), 'bin', versionId)
const isWindows = process.platform === 'win32'
const dbBin = findExecutable(binDir, isWindows ? 'mariadb.exe' : 'mariadb') || findExecutable(binDir, isWindows ? 'mysql.exe' : 'mysql')
if (!dbBin) return { success: false, message: 'mariadb/mysql executable not found' }
const log = (msg: string) => event.sender.send('mariadb:export:log', msg)
return await mariaDbOperationService.exportSql({
binPath: dbBin,
host: '127.0.0.1',
port: v.port,
rootUser: 'root',
rootPass: rootPass || ''
}, dbName, savePath, compress, (msg) => log(msg))
})
}