mirror of
https://github.com/myronblair/novacpx
synced 2026-06-30 17:50:41 -05:00
e3b166803a
- 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>
151 lines
6.6 KiB
PHP
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");
|
|
}
|
|
}
|
|
}
|