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:
2026-06-07 05:50:50 +00:00
parent 716d292e77
commit e3b166803a
28 changed files with 2576 additions and 1 deletions
+108
View File
@@ -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");
}
}