feat: implement Nginx and Apache configuration management with project-based virtual host generation in IPC layer

This commit is contained in:
Ümit Tunç
2026-04-02 11:19:38 +03:00
parent ab23b25a17
commit 6be62c6908
6 changed files with 101 additions and 25 deletions
+3
View File
@@ -7,6 +7,9 @@
"mariaDbPorts": {},
"allowRemoteAccess": false,
"language": "tr",
"cliEnvEnabled": true,
"defaultPhpVersion": "7.4.33",
"defaultMariaDbVersion": "mariadb-11.4.2",
"mysqlPort": "3306",
"mysqlPorts": {}
}
+2 -1
View File
@@ -1050,7 +1050,8 @@ export function registerIpcHandlers(): void {
enabled: settings.cliEnvEnabled !== false,
defaultPhp: settings.defaultPhpVersion,
defaultMariaDb: settings.defaultMariaDbVersion,
binDir: cliBinService.getBinDir()
binDir: cliBinService.getBinDir(),
aliases: cliBinService.getAllAliases()
}
})
+60 -19
View File
@@ -10,15 +10,20 @@ import { configService } from './ConfigService'
const execAsync = promisify(exec)
export class CliBinService {
private binDir: string
private _binDir: string | null = null
private aliases: { command: string, target: string }[] = []
constructor() {
// We store shims in the app's roaming/user path to avoid permission issues
this.binDir = path.join(app.getPath('userData'), 'cli-bin')
if (!fs.existsSync(this.binDir)) {
fs.mkdirSync(this.binDir, { recursive: true })
constructor() {}
private getBinDirInternal(): string {
if (!this._binDir) {
this._binDir = path.join(app.getPath('userData'), 'cli-bin')
if (!fs.existsSync(this._binDir)) {
fs.mkdirSync(this._binDir, { recursive: true })
}
}
return this._binDir
}
async sync() {
const phpVersions = await phpManagerService.getVersions()
@@ -27,11 +32,13 @@ export class CliBinService {
const installedPhp = phpVersions.filter(v => v.status === 'installed')
const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed')
this.aliases = []
// Clean current shims
const files = fs.readdirSync(this.binDir)
const binDir = this.getBinDirInternal()
const files = fs.readdirSync(binDir)
for (const file of files) {
fs.unlinkSync(path.join(this.binDir, file))
fs.unlinkSync(path.join(binDir, file))
}
// Generate PHP shims
@@ -113,25 +120,55 @@ export class CliBinService {
// Create a small .cmd proxy for each command
// Using "@%* " to pass all arguments correctly
const content = `@echo off\n"${targetPath}" %*\n`
fs.writeFileSync(path.join(this.binDir, `${name}.cmd`), content)
const binDir = this.getBinDirInternal()
fs.writeFileSync(path.join(binDir, `${name}.cmd`), content)
// Also create a .bat just in case
fs.writeFileSync(path.join(this.binDir, `${name}.bat`), content)
fs.writeFileSync(path.join(binDir, `${name}.bat`), content)
// Track alias
if (!this.aliases.some(a => a.command === name)) {
this.aliases.push({ command: name, target: targetPath })
}
}
async ensureInPath() {
if (process.platform !== 'win32') return
try {
// Get current User PATH
const { stdout } = await execAsync('powershell -Command "[Environment]::GetEnvironmentVariable(\'Path\', \'User\')"')
const currentPath = stdout.trim()
// Use PowerShell to manage PATH safely.
// 1. Get current User PATH
// 2. Add binDir if not exists
// 3. Set new PATH
// 4. Broadcast WM_SETTINGCHANGE to notify system
const binDir = this.getBinDirInternal()
if (!currentPath.includes(this.binDir)) {
const newPath = `${currentPath}${currentPath.endsWith(';') ? '' : ';'}${this.binDir}`
// Update User PATH safely using PowerShell
await execAsync(`powershell -Command "[Environment]::SetEnvironmentVariable('Path', '${newPath}', 'User')"`)
console.log(`Added Multi-PHP bin to User PATH: ${this.binDir}`)
const psScript = `
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
if ($currentPath -notlike '*${binDir}*') {
$newPath = $currentPath
if ($newPath -and -not $newPath.EndsWith(';')) { $newPath += ';' }
$newPath += '${binDir}'
[Environment]::SetEnvironmentVariable('Path', $newPath, 'User')
# Notify Windows of the change (WM_SETTINGCHANGE)
$signature = @'
[DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)]
public static extern IntPtr SendMessageTimeout(IntPtr hWnd, uint Msg, IntPtr wParam, string lParam, uint fuFlags, uint uTimeout, out IntPtr lpdwResult);
'@
$type = Add-Type -MemberDefinition $signature -Name "Win32" -Namespace "Native" -PassThru -ErrorAction SilentlyContinue
if ($type) {
$type::SendMessageTimeout(0xFFFF, 0x001A, [IntPtr]::Zero, "Environment", 0x0002, 1000, [ref][IntPtr]::Zero)
}
Write-Output "PATH_UPDATED"
} else {
Write-Output "ALREADY_IN_PATH"
}
`
const { stdout } = await execAsync(`powershell -Command "${psScript.replace(/\n/g, ' ')}"`)
if (stdout.includes('PATH_UPDATED')) {
console.log(`Successfully added Multi-PHP bin to User PATH: ${binDir}`)
}
} catch (e) {
console.error('Failed to update PATH:', e)
@@ -139,7 +176,11 @@ export class CliBinService {
}
getBinDir() {
return this.binDir
return this.getBinDirInternal()
}
getAllAliases() {
return this.aliases
}
}
+30 -1
View File
@@ -107,6 +107,9 @@ interface AppSettings {
mariaDbPorts?: Record<string, string>
allowRemoteAccess: boolean
language: string
cliEnvEnabled: boolean
defaultPhpVersion: string
defaultMariaDbVersion: string
}
function App(): JSX.Element {
@@ -137,7 +140,10 @@ function App(): JSX.Element {
phpPort: '9001',
mariaDbPort: '3306',
allowRemoteAccess: false,
language: 'tr'
language: 'tr',
cliEnvEnabled: true,
defaultPhpVersion: '',
defaultMariaDbVersion: ''
})
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
open: false,
@@ -1317,11 +1323,34 @@ function App(): JSX.Element {
onClick={async () => {
await window.api.invoke('cli:sync')
setNotification({ open: true, message: t('settings.cli_sync_success'), severity: 'success' })
fetchCliConfig()
}}
>
{t('settings.cli_sync_btn')}
</Button>
</Box>
{cliConfig.aliases && cliConfig.aliases.length > 0 && (
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{ mb: 1, fontWeight: 600, color: 'primary.main' }}>
{t('settings.cli_aliases_title')}
</Typography>
<Paper variant="outlined" sx={{ bgcolor: 'rgba(0,0,0,0.1)', borderColor: 'rgba(255,255,255,0.05)', maxHeight: 200, overflow: 'auto' }}>
<List dense disablePadding>
{cliConfig.aliases.map((a: any) => (
<ListItem key={a.command} divider sx={{ py: 0.5, borderColor: 'rgba(255,255,255,0.05)' }}>
<ListItemText
primary={<code>{a.command}</code>}
secondary={a.target}
primaryTypographyProps={{ variant: 'body2', fontWeight: 'bold' }}
secondaryTypographyProps={{ variant: 'caption', sx: { opacity: 0.5 } }}
/>
</ListItem>
))}
</List>
</Paper>
</Box>
)}
</Stack>
</Paper>
</Box>
+3 -2
View File
@@ -109,10 +109,11 @@
"cli_default_php_desc": "Choose which version will be used when you run the 'php' command.",
"cli_default_mariadb": "Default MariaDB Version (mysql)",
"cli_default_mariadb_desc": "Default version for 'mysql' or 'mariadb' commands.",
"cli_sync_btn": "SYNC SETTINGS",
"cli_sync_btn": "Sync Commands",
"cli_path_found": "PATH Found:",
"cli_path_not_found": "PATH Not Set Yet",
"cli_sync_success": "CLI settings and PATH integration updated successfully.",
"cli_sync_success": "CLI commands updated successfully.",
"cli_aliases_title": "Generated Commands (Aliases)",
"server_settings": "Server Settings"
},
"server": {
+3 -2
View File
@@ -110,10 +110,11 @@
"cli_default_php_desc": "'php' komutunu çalıştırdığınızda hangi sürümün kullanılacağını seçin.",
"cli_default_mariadb": "Varsayılan MariaDB Sürümü (mysql)",
"cli_default_mariadb_desc": "'mysql' veya 'mariadb' komutu için varsayılan sürüm.",
"cli_sync_btn": "AYARLARI SENKRONİZE ET",
"cli_sync_btn": "Komutları Eşitle",
"cli_path_found": "PATH Ayarı Bulundu:",
"cli_path_not_found": "PATH Ayarı Henüz Yapılmamış",
"cli_sync_success": "CLI ayarları ve PATH entegrasyonu başarıyla güncellendi."
"cli_sync_success": "CLI komutları başarıyla güncellendi.",
"cli_aliases_title": "Oluşturulan Komutlar (Takma Adlar)"
},
"server": {
"nginx": {