fix: global exception handler (prevents 502), transaction rollback on account create, CORS for reverse proxy

- set_exception_handler in api/index.php prevents uncaught exceptions from crashing PHP-FPM
- AccountManager::create() wrapped in DB transaction with rollback + Linux user cleanup on failure
- CORS origin regex updated to allow requests from port 443 (NPM reverse proxy)
- index.html written via sudo tee instead of file_put_contents (www-data permission fix)
- chpasswd now called with sudo prefix

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LP9Q4kfCAYAjJnsbHBrViZ
This commit is contained in:
2026-06-20 05:23:42 +00:00
parent cbd20c5390
commit 39942929a7
2 changed files with 44 additions and 27 deletions
+10 -2
View File
@@ -9,14 +9,22 @@ define('NOVACPX_API', __DIR__);
define('NOVACPX_LIB', NOVACPX_ROOT . '/lib');
header('Content-Type: application/json');
// Global exception handler — prevents uncaught exceptions from crashing PHP-FPM (502)
set_exception_handler(function (Throwable $e) {
http_response_code(500);
echo json_encode(['success' => false, 'message' => $e->getMessage(), 'errors' => []]);
exit;
});
$_ver = file_get_contents(NOVACPX_ROOT . '/VERSION')
?: file_get_contents('/opt/novacpx-src/VERSION')
?: '1.0.0';
header('X-NovaCPX-Version: ' . trim($_ver));
// CORS for same-origin panel requests (ports 8880/8881/8882/8883)
// CORS for same-origin panel requests (ports 8880/8881/8882/8883 and HTTPS via reverse proxy on 443)
$origin = $_SERVER['HTTP_ORIGIN'] ?? '';
if (preg_match('#^https?://[^/]+:(888[0-3])$#', $origin)) {
if (preg_match('#^https?://[^/]+(:(888[0-3]))?$#', $origin)) {
header("Access-Control-Allow-Origin: $origin");
header('Access-Control-Allow-Credentials: true');
header('Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS');
+34 -25
View File
@@ -25,41 +25,50 @@ class AccountManager {
$docRoot = "{$homeDir}/public_html";
$password = $data['password'] ?? bin2hex(random_bytes(8));
// Create Linux user
// Create Linux user and home directory first
self::shell("useradd -m -d {$homeDir} -s /sbin/nologin -G www-data " . escapeshellarg($username));
self::shell("echo " . escapeshellarg("{$username}:{$password}") . " | chpasswd");
self::shell("echo " . escapeshellarg("{$username}:{$password}") . " | sudo chpasswd");
self::shell("sudo mkdir -p {$docRoot} {$homeDir}/logs {$homeDir}/tmp");
self::shell("sudo chown -R {$username}:www-data {$homeDir}");
self::shell("sudo chmod 750 {$homeDir}"); self::shell("sudo chmod 775 {$docRoot}");
self::shell("sudo chmod 750 {$homeDir}");
self::shell("sudo chmod 775 {$docRoot}");
// Default index page
file_put_contents("{$docRoot}/index.html",
"<html><body style='font-family:sans-serif;text-align:center;padding:4rem'><h1>Welcome to {$domain}</h1><p>Hosted by NovaCPX</p></body></html>"
);
// Default index page (write as root via sudo tee)
$html = "<html><body style='font-family:sans-serif;text-align:center;padding:4rem'><h1>Welcome to {$domain}</h1><p>Hosted by NovaCPX</p></body></html>";
self::shell("sudo tee " . escapeshellarg("{$docRoot}/index.html") . " > /dev/null << 'HTMLEOF'\n{$html}\nHTMLEOF");
// Save account to DB
$acctId = (int)$db->insert(
"INSERT INTO accounts (user_id, username, domain, home_dir, package_id, php_version, web_server) VALUES (?,?,?,?,?,?,?)",
[$userId, $username, $domain, $homeDir, $pkgId ?: null, $phpVer, $webSrv]
);
// Wrap all DB writes in a transaction so partial failures leave no orphans
$db->beginTransaction();
try {
$acctId = (int)$db->insert(
"INSERT INTO accounts (user_id, username, domain, home_dir, package_id, php_version, web_server) VALUES (?,?,?,?,?,?,?)",
[$userId, $username, $domain, $homeDir, $pkgId ?: null, $phpVer, $webSrv]
);
// Save domain
$db->insert(
"INSERT INTO domains (account_id, domain, type, document_root) VALUES (?,?,?,?)",
[$acctId, $domain, 'main', $docRoot]
);
$db->insert(
"INSERT INTO domains (account_id, domain, type, document_root) VALUES (?,?,?,?)",
[$acctId, $domain, 'main', $docRoot]
);
// Create web vhost
VhostManager::create($username, $domain, $docRoot, $phpVer);
// Create web vhost
VhostManager::create($username, $domain, $docRoot, $phpVer);
// Create DNS zone
DNSManager::createZone($acctId, $domain);
// Create DNS zone
DNSManager::createZone($acctId, $domain);
// Auto-provision SPF, DKIM, DMARC records
self::provisionEmailDNS($acctId, $domain);
// Auto-provision SPF, DKIM, DMARC records
self::provisionEmailDNS($acctId, $domain);
// Create PHP-FPM pool
PHPManager::createPool($username, $phpVer);
// Create PHP-FPM pool
PHPManager::createPool($username, $phpVer);
$db->commit();
} catch (Throwable $e) {
$db->rollBack();
// Clean up Linux user if DB failed
self::shell("userdel -r " . escapeshellarg($username) . " 2>/dev/null || true");
throw $e;
}
novacpx_log('info', "Account created: $username ($domain)");
return ['account_id' => $acctId, 'username' => $username, 'domain' => $domain, 'home_dir' => $homeDir];