Files
novacpx/panel/lib/VhostManager.php
T
myron e3b166803a 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>
2026-06-07 05:50:50 +00:00

151 lines
6.6 KiB
PHP

<?php
/**
* VhostManager — creates/removes Apache2 and nginx virtual host configs
*/
class VhostManager {
public static function create(string $username, string $domain, string $docRoot, string $phpVer): void {
$logDir = "/home/{$username}/logs";
if (WEB_SERVER === 'nginx') {
self::writeNginx($username, $domain, $docRoot, $phpVer, $logDir);
} else {
self::writeApache($username, $domain, $docRoot, $phpVer, $logDir);
}
self::reload();
}
public static function createSubdomain(string $username, string $subdomain, string $docRoot, string $phpVer): void {
self::create($username, $subdomain, $docRoot, $phpVer);
}
public static function suspend(string $username, string $domain): void {
$suspendedRoot = "/var/novacpx/suspended";
@mkdir($suspendedRoot, 0755, true);
$suspendPage = "{$suspendedRoot}/{$domain}.html";
file_put_contents($suspendPage,
"<html><body style='font-family:sans-serif;text-align:center;padding:4rem;background:#0d0f17;color:#e2e4f0'>"
. "<h1 style='color:#ef4444'>Account Suspended</h1>"
. "<p>This account has been suspended. Please contact support.</p></body></html>"
);
// Rewrite vhost to serve suspension page
if (WEB_SERVER === 'nginx') {
$conf = "/etc/nginx/sites-available/novacpx-{$username}.conf";
if (file_exists($conf)) {
$content = file_get_contents($conf);
$content = preg_replace('/root\s+[^;]+;/', "root {$suspendedRoot};", $content);
file_put_contents($conf, $content);
}
} else {
$conf = "/etc/apache2/sites-available/novacpx-{$username}.conf";
if (file_exists($conf)) {
$content = file_get_contents($conf);
$content = preg_replace('/DocumentRoot\s+\S+/', "DocumentRoot {$suspendedRoot}", $content);
file_put_contents($conf, $content);
}
}
self::reload();
}
public static function unsuspend(string $username, string $domain): void {
// Re-create from DB
$db = DB::getInstance();
$acct = $db->fetchOne("SELECT * FROM accounts WHERE username = ?", [$username]);
if ($acct) {
self::create($username, $domain, $acct['home_dir'] . '/public_html', $acct['php_version']);
}
}
public static function remove(string $username, string $domain): void {
if (WEB_SERVER === 'nginx') {
$conf = "/etc/nginx/sites-available/novacpx-{$username}.conf";
$link = "/etc/nginx/sites-enabled/novacpx-{$username}.conf";
@unlink($conf); @unlink($link);
} else {
$conf = "/etc/apache2/sites-available/novacpx-{$username}.conf";
shell_exec("a2dissite novacpx-{$username} 2>/dev/null");
@unlink($conf);
}
self::reload();
}
public static function enableSSL(string $username, string $domain, string $cert, string $key, string $chain = ''): void {
$certDir = "/etc/novacpx/ssl/accounts/{$username}";
@mkdir($certDir, 0700, true);
file_put_contents("{$certDir}/cert.pem", $cert);
file_put_contents("{$certDir}/key.pem", $key);
if ($chain) file_put_contents("{$certDir}/chain.pem", $chain);
if (WEB_SERVER === 'nginx') {
$conf = file_get_contents("/etc/nginx/sites-available/novacpx-{$username}.conf") ?: '';
if (!str_contains($conf, 'ssl_certificate')) {
$conf = str_replace('listen 80;', "listen 443 ssl http2;\n listen 80;\n ssl_certificate {$certDir}/cert.pem;\n ssl_certificate_key {$certDir}/key.pem;", $conf);
file_put_contents("/etc/nginx/sites-available/novacpx-{$username}.conf", $conf);
}
} else {
$conf = file_get_contents("/etc/apache2/sites-available/novacpx-{$username}.conf") ?: '';
if (!str_contains($conf, 'SSLEngine')) {
$conf = str_replace('<VirtualHost *:80>', "<VirtualHost *:443>\n SSLEngine on\n SSLCertificateFile {$certDir}/cert.pem\n SSLCertificateKeyFile {$certDir}/key.pem", $conf);
$conf .= "\n<VirtualHost *:80>\n ServerName {$domain}\n Redirect permanent / https://{$domain}/\n</VirtualHost>";
file_put_contents("/etc/apache2/sites-available/novacpx-{$username}.conf", $conf);
}
}
self::reload();
}
private static function writeNginx(string $username, string $domain, string $docRoot, string $phpVer, string $logDir): void {
$sock = "/run/php/php{$phpVer}-fpm-{$username}.sock";
$conf = "/etc/nginx/sites-available/novacpx-{$username}.conf";
file_put_contents($conf, "server {
listen 80;
server_name {$domain} www.{$domain};
root {$docRoot};
index index.php index.html index.htm;
access_log {$logDir}/access.log;
error_log {$logDir}/error.log;
location / { try_files \$uri \$uri/ /index.php?\$query_string; }
location ~ \.php$ {
fastcgi_pass unix:{$sock};
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME \$document_root\$fastcgi_script_name;
fastcgi_param PHP_VALUE \"error_log={$logDir}/php.log\";
}
location ~ /\\.ht { deny all; }
location ~* \\.(jpg|jpeg|png|gif|ico|css|js|svg|woff2)$ { expires 30d; add_header Cache-Control public; }
}
");
@symlink($conf, "/etc/nginx/sites-enabled/novacpx-{$username}.conf");
}
private static function writeApache(string $username, string $domain, string $docRoot, string $phpVer, string $logDir): void {
$sock = "/run/php/php{$phpVer}-fpm-{$username}.sock";
$conf = "/etc/apache2/sites-available/novacpx-{$username}.conf";
file_put_contents($conf, "<VirtualHost *:80>
ServerName {$domain}
ServerAlias www.{$domain}
DocumentRoot {$docRoot}
ErrorLog {$logDir}/error.log
CustomLog {$logDir}/access.log combined
<Directory {$docRoot}>
Options -Indexes +FollowSymLinks +MultiViews
AllowOverride All
Require all granted
</Directory>
<FilesMatch \\.php$>
SetHandler \"proxy:unix:{$sock}|fcgi://localhost/\"
</FilesMatch>
</VirtualHost>
");
shell_exec("a2ensite novacpx-{$username} 2>/dev/null");
}
private static function reload(): void {
if (WEB_SERVER === 'nginx') {
shell_exec("nginx -t 2>/dev/null && systemctl reload nginx 2>/dev/null");
} else {
shell_exec("apache2ctl configtest 2>/dev/null && systemctl reload apache2 2>/dev/null");
}
}
}