mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
Add full API endpoint suite, lib managers, webmail (Roundcube :8883), and NovaCPX icon/branding assets
- 14 API endpoints: accounts, packages, domains, dns, email, databases, ftp, ssl, cron, php, files, stats, webmail, server_setup - 8 lib managers: AccountManager, VhostManager, DNSManager, EmailManager, DatabaseManager, PHPManager, FTPManager, SSLManager - Roundcube webmail on dedicated port 8883 (sequenced after 8880/8881/8882) - Custom NovaCPX SVG icon sprite (30+ unique icons), logo, mark, favicon - PORT_WEBMAIL=8883 wired into Core.php, install.sh, UFW, Fail2Ban, credentials file Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,108 @@
|
||||
<?php
|
||||
/**
|
||||
* PHPManager — per-account PHP-FPM pools + version switching
|
||||
*/
|
||||
class PHPManager {
|
||||
|
||||
private static string $poolDir = '/etc/php/{ver}/fpm/pool.d';
|
||||
|
||||
public static function createPool(string $username, string $phpVer): void {
|
||||
$poolFile = str_replace('{ver}', $phpVer, self::$poolDir) . "/{$username}.conf";
|
||||
$homeDir = "/home/{$username}";
|
||||
$sock = "/run/php/php{$phpVer}-fpm-{$username}.sock";
|
||||
|
||||
file_put_contents($poolFile, "[{$username}]
|
||||
user = {$username}
|
||||
group = www-data
|
||||
listen = {$sock}
|
||||
listen.owner = www-data
|
||||
listen.group = www-data
|
||||
listen.mode = 0660
|
||||
|
||||
pm = ondemand
|
||||
pm.max_children = 5
|
||||
pm.process_idle_timeout = 10s
|
||||
pm.max_requests = 500
|
||||
|
||||
php_admin_value[error_log] = {$homeDir}/logs/php.log
|
||||
php_admin_value[open_basedir] = {$homeDir}/:/tmp/
|
||||
php_admin_flag[log_errors] = on
|
||||
php_admin_value[disable_functions] = exec,passthru,shell_exec,system,proc_open,popen
|
||||
php_value[upload_max_filesize] = 64M
|
||||
php_value[post_max_size] = 64M
|
||||
php_value[memory_limit] = 256M
|
||||
php_value[max_execution_time] = 30
|
||||
");
|
||||
self::reloadFPM($phpVer);
|
||||
}
|
||||
|
||||
public static function removePool(string $username): void {
|
||||
foreach (['7.4','8.1','8.2','8.3'] as $ver) {
|
||||
$file = str_replace('{ver}', $ver, self::$poolDir) . "/{$username}.conf";
|
||||
if (file_exists($file)) { unlink($file); self::reloadFPM($ver); }
|
||||
}
|
||||
}
|
||||
|
||||
public static function switchVersion(int $accountId, string $newVer): void {
|
||||
$db = DB::getInstance();
|
||||
$acct = $db->fetchOne("SELECT * FROM accounts WHERE id = ?", [$accountId]);
|
||||
if (!$acct) throw new RuntimeException("Account not found");
|
||||
|
||||
$oldVer = $acct['php_version'];
|
||||
if ($oldVer === $newVer) return;
|
||||
|
||||
// Remove old pool, create new one
|
||||
$oldPool = str_replace('{ver}', $oldVer, self::$poolDir) . "/{$acct['username']}.conf";
|
||||
if (file_exists($oldPool)) { unlink($oldPool); self::reloadFPM($oldVer); }
|
||||
|
||||
self::createPool($acct['username'], $newVer);
|
||||
|
||||
// Update vhost to use new socket
|
||||
VhostManager::create($acct['username'], $acct['domain'], $acct['home_dir'] . '/public_html', $newVer);
|
||||
|
||||
$db->execute("UPDATE accounts SET php_version = ? WHERE id = ?", [$newVer, $accountId]);
|
||||
$db->execute("UPDATE php_configs SET php_version = ?, updated_at = NOW() WHERE account_id = ?", [$newVer, $accountId]);
|
||||
}
|
||||
|
||||
public static function updateConfig(int $accountId, array $cfg): void {
|
||||
$db = DB::getInstance();
|
||||
$acct = $db->fetchOne("SELECT username, php_version FROM accounts WHERE id = ?", [$accountId]);
|
||||
if (!$acct) throw new RuntimeException("Account not found");
|
||||
|
||||
$poolFile = str_replace('{ver}', $acct['php_version'], self::$poolDir) . "/{$acct['username']}.conf";
|
||||
if (!file_exists($poolFile)) throw new RuntimeException("PHP-FPM pool not found");
|
||||
|
||||
$content = file_get_contents($poolFile);
|
||||
$map = [
|
||||
'memory_limit' => 'php_value[memory_limit]',
|
||||
'max_execution_time' => 'php_value[max_execution_time]',
|
||||
'upload_max_filesize' => 'php_value[upload_max_filesize]',
|
||||
'post_max_size' => 'php_value[post_max_size]',
|
||||
];
|
||||
foreach ($map as $key => $iniKey) {
|
||||
if (isset($cfg[$key])) {
|
||||
$content = preg_replace("/{$iniKey}\s*=.*/", "{$iniKey} = {$cfg[$key]}", $content);
|
||||
}
|
||||
}
|
||||
file_put_contents($poolFile, $content);
|
||||
self::reloadFPM($acct['php_version']);
|
||||
|
||||
$db->execute(
|
||||
"INSERT INTO php_configs (account_id, php_version, memory_limit, max_execution_time, upload_max_filesize, post_max_size)
|
||||
VALUES (?,?,?,?,?,?)
|
||||
ON DUPLICATE KEY UPDATE memory_limit=VALUES(memory_limit), max_execution_time=VALUES(max_execution_time),
|
||||
upload_max_filesize=VALUES(upload_max_filesize), post_max_size=VALUES(post_max_size), updated_at=NOW()",
|
||||
[$accountId, $acct['php_version'], $cfg['memory_limit'] ?? '256M', $cfg['max_execution_time'] ?? 30,
|
||||
$cfg['upload_max_filesize'] ?? '64M', $cfg['post_max_size'] ?? '64M']
|
||||
);
|
||||
}
|
||||
|
||||
public static function listExtensions(string $phpVer): array {
|
||||
$out = shell_exec("php{$phpVer} -m 2>/dev/null") ?: '';
|
||||
return array_values(array_filter(explode("\n", $out), fn($l) => $l && !str_starts_with($l, '[')));
|
||||
}
|
||||
|
||||
private static function reloadFPM(string $ver): void {
|
||||
shell_exec("systemctl reload php{$ver}-fpm 2>/dev/null || true");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user