feat: implement Nginx service management and dynamic configuration generation via IPC handlers
This commit is contained in:
@@ -35,10 +35,11 @@ http {
|
|||||||
}
|
}
|
||||||
|
|
||||||
location ~ \.php$ {
|
location ~ \.php$ {
|
||||||
|
# Use $request_filename for consistent behavior across global and project configs
|
||||||
|
include "{{CONF_DIR}}/fastcgi_params";
|
||||||
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
|
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||||
include "{{CONF_DIR}}/fastcgi_params";
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,12 @@ location ^~ /{{HOST}}/ {
|
|||||||
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
fastcgi_pass 127.0.0.1:{{PHP_PORT}};
|
||||||
fastcgi_index index.php;
|
fastcgi_index index.php;
|
||||||
|
|
||||||
# Correctly split path to get the relative script path
|
# Correctly use $request_filename for alias compatibility
|
||||||
fastcgi_split_path_info ^/{{HOST}}(.+\.php)(.*)$;
|
|
||||||
fastcgi_param SCRIPT_FILENAME "{{PATH}}$fastcgi_script_name";
|
|
||||||
fastcgi_param DOCUMENT_ROOT "{{PATH}}";
|
|
||||||
fastcgi_param PATH_INFO $fastcgi_path_info;
|
|
||||||
|
|
||||||
include "{{CONF_DIR}}/fastcgi_params";
|
include "{{CONF_DIR}}/fastcgi_params";
|
||||||
|
fastcgi_param SCRIPT_FILENAME $request_filename;
|
||||||
|
fastcgi_param DOCUMENT_ROOT $document_root;
|
||||||
|
|
||||||
|
# Optional parameters for frameworks
|
||||||
|
fastcgi_param PATH_INFO $fastcgi_path_info;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+98
-83
@@ -28,25 +28,93 @@ function findExecutable(dir: string, name: string): string | null {
|
|||||||
return null
|
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 binPath = path.join(nginxPrefix, 'nginx.exe')
|
||||||
|
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 projects = await projectService.getProjects()
|
||||||
|
for (const p of projects) {
|
||||||
|
const projectConfPath = path.join(projectsConfDir, `${p.host}.conf`)
|
||||||
|
if (forceProjects || !fs.existsSync(projectConfPath)) {
|
||||||
|
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
|
||||||
|
})
|
||||||
|
|
||||||
|
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')
|
||||||
|
|
||||||
|
// 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 }> {
|
async function reloadNginx(): Promise<{ success: boolean; message: string }> {
|
||||||
try {
|
try {
|
||||||
const binDir = path.join(configService.getBasePath(), 'bin')
|
const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false)
|
||||||
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')
|
const result = await processManager.runOnce(binPath, ['-p', nginxPrefix, '-c', configPath, '-s', 'reload'], 'nginx')
|
||||||
if (result.success) {
|
if (result.success) {
|
||||||
console.log('Nginx reload successful')
|
console.log('Nginx reload successful')
|
||||||
return { success: true, message: 'Nginx reloaded' }
|
return { success: true, message: 'Nginx reloaded' }
|
||||||
} else {
|
} else {
|
||||||
console.error('Nginx reload failed:', result.output)
|
// Fallback to full restart if reload fails (common on Windows)
|
||||||
return { success: false, message: result.output || 'Nginx reload failed' }
|
return await restartNginx()
|
||||||
}
|
}
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
return { success: false, message: `Reload hatası: ${error.message}` }
|
return { success: false, message: `Reload hatası: ${error.message}` }
|
||||||
@@ -66,67 +134,7 @@ export function registerIpcHandlers(): void {
|
|||||||
const binDir = path.join(configService.getBasePath(), 'bin')
|
const binDir = path.join(configService.getBasePath(), 'bin')
|
||||||
|
|
||||||
if (serviceName === 'nginx') {
|
if (serviceName === 'nginx') {
|
||||||
const rootPath = path.join(app.getAppPath(), 'www')
|
const { binPath, nginxPrefix, configPath } = await rebuildNginxConfig(false)
|
||||||
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)
|
const success = await processManager.startService('nginx', binPath, ['-p', nginxPrefix, '-c', configPath], false)
|
||||||
|
|
||||||
@@ -383,21 +391,28 @@ export function registerIpcHandlers(): void {
|
|||||||
|
|
||||||
ipcMain.handle('projects:add', async (_event, project) => {
|
ipcMain.handle('projects:add', async (_event, project) => {
|
||||||
const result = await projectService.addProject(project)
|
const result = await projectService.addProject(project)
|
||||||
// Trigger Nginx reload
|
// Trigger Nginx restart to generate initial config and apply
|
||||||
await ipcMain.emit('services:reload', null, 'nginx')
|
await restartNginx()
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('projects:remove', async (_event, id) => {
|
ipcMain.handle('projects:remove', async (_event, id) => {
|
||||||
const result = projectService.removeProject(id)
|
const result = projectService.removeProject(id)
|
||||||
// Trigger Nginx reload
|
// Trigger Nginx restart to remove config from glob inclusion indirectly
|
||||||
await ipcMain.emit('services:reload', null, 'nginx')
|
// 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
|
return result
|
||||||
})
|
})
|
||||||
ipcMain.handle('projects:update', async (_event, project) => {
|
ipcMain.handle('projects:update', async (_event, project) => {
|
||||||
const result = projectService.updateProject(project)
|
const result = projectService.updateProject(project)
|
||||||
// Trigger Nginx reload
|
// Trigger Nginx restart
|
||||||
await ipcMain.emit('services:reload', null, 'nginx')
|
await restartNginx()
|
||||||
return result
|
return result
|
||||||
})
|
})
|
||||||
|
|
||||||
@@ -423,8 +438,8 @@ export function registerIpcHandlers(): void {
|
|||||||
ipcMain.handle('nginx:config:save', async (_event, content) => {
|
ipcMain.handle('nginx:config:save', async (_event, content) => {
|
||||||
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'nginx.conf')
|
||||||
fs.writeFileSync(configPath, content)
|
fs.writeFileSync(configPath, content)
|
||||||
// Reload Nginx after save
|
// Full restart after manual global save
|
||||||
return await reloadNginx()
|
return await restartNginx()
|
||||||
})
|
})
|
||||||
|
|
||||||
ipcMain.handle('nginx:config:test', async () => {
|
ipcMain.handle('nginx:config:test', async () => {
|
||||||
@@ -496,8 +511,8 @@ export function registerIpcHandlers(): void {
|
|||||||
ipcMain.handle('project:nginx:save', async (_event, host, content) => {
|
ipcMain.handle('project:nginx:save', async (_event, host, content) => {
|
||||||
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
|
const configPath = path.join(configService.getBasePath(), 'generated_configs', 'projects', `${host}.conf`)
|
||||||
fs.writeFileSync(configPath, content)
|
fs.writeFileSync(configPath, content)
|
||||||
// Reload Nginx after save
|
// Full restart after manual project save
|
||||||
return await reloadNginx()
|
return await restartNginx()
|
||||||
})
|
})
|
||||||
|
|
||||||
// phpMyAdmin
|
// phpMyAdmin
|
||||||
|
|||||||
Reference in New Issue
Block a user