feat: implement Nginx and Apache configuration management with project-based virtual host generation in IPC layer
This commit is contained in:
@@ -7,6 +7,9 @@
|
|||||||
"mariaDbPorts": {},
|
"mariaDbPorts": {},
|
||||||
"allowRemoteAccess": false,
|
"allowRemoteAccess": false,
|
||||||
"language": "tr",
|
"language": "tr",
|
||||||
|
"cliEnvEnabled": true,
|
||||||
|
"defaultPhpVersion": "7.4.33",
|
||||||
|
"defaultMariaDbVersion": "mariadb-11.4.2",
|
||||||
"mysqlPort": "3306",
|
"mysqlPort": "3306",
|
||||||
"mysqlPorts": {}
|
"mysqlPorts": {}
|
||||||
}
|
}
|
||||||
@@ -1050,7 +1050,8 @@ export function registerIpcHandlers(): void {
|
|||||||
enabled: settings.cliEnvEnabled !== false,
|
enabled: settings.cliEnvEnabled !== false,
|
||||||
defaultPhp: settings.defaultPhpVersion,
|
defaultPhp: settings.defaultPhpVersion,
|
||||||
defaultMariaDb: settings.defaultMariaDbVersion,
|
defaultMariaDb: settings.defaultMariaDbVersion,
|
||||||
binDir: cliBinService.getBinDir()
|
binDir: cliBinService.getBinDir(),
|
||||||
|
aliases: cliBinService.getAllAliases()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -10,15 +10,20 @@ import { configService } from './ConfigService'
|
|||||||
const execAsync = promisify(exec)
|
const execAsync = promisify(exec)
|
||||||
|
|
||||||
export class CliBinService {
|
export class CliBinService {
|
||||||
private binDir: string
|
private _binDir: string | null = null
|
||||||
|
private aliases: { command: string, target: string }[] = []
|
||||||
|
|
||||||
constructor() {
|
constructor() {}
|
||||||
// We store shims in the app's roaming/user path to avoid permission issues
|
|
||||||
this.binDir = path.join(app.getPath('userData'), 'cli-bin')
|
private getBinDirInternal(): string {
|
||||||
if (!fs.existsSync(this.binDir)) {
|
if (!this._binDir) {
|
||||||
fs.mkdirSync(this.binDir, { recursive: true })
|
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() {
|
async sync() {
|
||||||
const phpVersions = await phpManagerService.getVersions()
|
const phpVersions = await phpManagerService.getVersions()
|
||||||
@@ -27,11 +32,13 @@ export class CliBinService {
|
|||||||
|
|
||||||
const installedPhp = phpVersions.filter(v => v.status === 'installed')
|
const installedPhp = phpVersions.filter(v => v.status === 'installed')
|
||||||
const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed')
|
const installedMariaDb = mariaDbVersions.filter(v => v.status === 'installed')
|
||||||
|
this.aliases = []
|
||||||
|
|
||||||
// Clean current shims
|
// Clean current shims
|
||||||
const files = fs.readdirSync(this.binDir)
|
const binDir = this.getBinDirInternal()
|
||||||
|
const files = fs.readdirSync(binDir)
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
fs.unlinkSync(path.join(this.binDir, file))
|
fs.unlinkSync(path.join(binDir, file))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate PHP shims
|
// Generate PHP shims
|
||||||
@@ -113,25 +120,55 @@ export class CliBinService {
|
|||||||
// Create a small .cmd proxy for each command
|
// Create a small .cmd proxy for each command
|
||||||
// Using "@%* " to pass all arguments correctly
|
// Using "@%* " to pass all arguments correctly
|
||||||
const content = `@echo off\n"${targetPath}" %*\n`
|
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
|
// 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() {
|
async ensureInPath() {
|
||||||
if (process.platform !== 'win32') return
|
if (process.platform !== 'win32') return
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Get current User PATH
|
// Use PowerShell to manage PATH safely.
|
||||||
const { stdout } = await execAsync('powershell -Command "[Environment]::GetEnvironmentVariable(\'Path\', \'User\')"')
|
// 1. Get current User PATH
|
||||||
const currentPath = stdout.trim()
|
// 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 psScript = `
|
||||||
const newPath = `${currentPath}${currentPath.endsWith(';') ? '' : ';'}${this.binDir}`
|
$currentPath = [Environment]::GetEnvironmentVariable('Path', 'User')
|
||||||
// Update User PATH safely using PowerShell
|
if ($currentPath -notlike '*${binDir}*') {
|
||||||
await execAsync(`powershell -Command "[Environment]::SetEnvironmentVariable('Path', '${newPath}', 'User')"`)
|
$newPath = $currentPath
|
||||||
console.log(`Added Multi-PHP bin to User PATH: ${this.binDir}`)
|
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) {
|
} catch (e) {
|
||||||
console.error('Failed to update PATH:', e)
|
console.error('Failed to update PATH:', e)
|
||||||
@@ -139,7 +176,11 @@ export class CliBinService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getBinDir() {
|
getBinDir() {
|
||||||
return this.binDir
|
return this.getBinDirInternal()
|
||||||
|
}
|
||||||
|
|
||||||
|
getAllAliases() {
|
||||||
|
return this.aliases
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -107,6 +107,9 @@ interface AppSettings {
|
|||||||
mariaDbPorts?: Record<string, string>
|
mariaDbPorts?: Record<string, string>
|
||||||
allowRemoteAccess: boolean
|
allowRemoteAccess: boolean
|
||||||
language: string
|
language: string
|
||||||
|
cliEnvEnabled: boolean
|
||||||
|
defaultPhpVersion: string
|
||||||
|
defaultMariaDbVersion: string
|
||||||
}
|
}
|
||||||
|
|
||||||
function App(): JSX.Element {
|
function App(): JSX.Element {
|
||||||
@@ -137,7 +140,10 @@ function App(): JSX.Element {
|
|||||||
phpPort: '9001',
|
phpPort: '9001',
|
||||||
mariaDbPort: '3306',
|
mariaDbPort: '3306',
|
||||||
allowRemoteAccess: false,
|
allowRemoteAccess: false,
|
||||||
language: 'tr'
|
language: 'tr',
|
||||||
|
cliEnvEnabled: true,
|
||||||
|
defaultPhpVersion: '',
|
||||||
|
defaultMariaDbVersion: ''
|
||||||
})
|
})
|
||||||
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
|
const [notification, setNotification] = useState<{ open: boolean, message: string, severity: 'success' | 'error' }>({
|
||||||
open: false,
|
open: false,
|
||||||
@@ -1317,11 +1323,34 @@ function App(): JSX.Element {
|
|||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
await window.api.invoke('cli:sync')
|
await window.api.invoke('cli:sync')
|
||||||
setNotification({ open: true, message: t('settings.cli_sync_success'), severity: 'success' })
|
setNotification({ open: true, message: t('settings.cli_sync_success'), severity: 'success' })
|
||||||
|
fetchCliConfig()
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{t('settings.cli_sync_btn')}
|
{t('settings.cli_sync_btn')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</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>
|
</Stack>
|
||||||
</Paper>
|
</Paper>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
@@ -109,10 +109,11 @@
|
|||||||
"cli_default_php_desc": "Choose which version will be used when you run the 'php' command.",
|
"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": "Default MariaDB Version (mysql)",
|
||||||
"cli_default_mariadb_desc": "Default version for 'mysql' or 'mariadb' commands.",
|
"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_found": "PATH Found:",
|
||||||
"cli_path_not_found": "PATH Not Set Yet",
|
"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_settings": "Server Settings"
|
||||||
},
|
},
|
||||||
"server": {
|
"server": {
|
||||||
|
|||||||
@@ -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_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": "Varsayılan MariaDB Sürümü (mysql)",
|
||||||
"cli_default_mariadb_desc": "'mysql' veya 'mariadb' komutu için varsayılan sürüm.",
|
"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_found": "PATH Ayarı Bulundu:",
|
||||||
"cli_path_not_found": "PATH Ayarı Henüz Yapılmamış",
|
"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": {
|
"server": {
|
||||||
"nginx": {
|
"nginx": {
|
||||||
|
|||||||
Reference in New Issue
Block a user