前言
又造了个轮子....用来给SHPC用的,后续开通新的SHPC时不用再调用API操作Caddy了,直接Nginx搞定。
简单描述一下流程:
1. 支付回调确认付款成功,系统自动开通SHPC容器,这时候需要做一个反向代理,将客户的rstudio和jupyterhub暴露给公网访问。
2. 系统将需要反代的信息写入MySQL数据库。
3. 定时任务每5分钟执行一次PHP脚本,脚本读取MySQL中的配置并生成对应Nginx的conf文件。
4. 确认生成成功后Nginx重载配置(Reload)
代码
首先是MySQL部分:
CREATE TABLE `reverse_proxies` ( `id` int UNSIGNED NOT NULL, `hostname` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `listen_port` smallint UNSIGNED NOT NULL DEFAULT '80', `protocol` enum('HTTP','HTTPS') COLLATE utf8mb4_unicode_ci NOT NULL DEFAULT 'HTTP', `proxy_address` varchar(45) COLLATE utf8mb4_unicode_ci NOT NULL, `proxy_hostname` varchar(255) COLLATE utf8mb4_unicode_ci NOT NULL, `enable_websocket` tinyint(1) NOT NULL DEFAULT '0', `metadata` text COLLATE utf8mb4_unicode_ci NOT NULL, `created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP, `updated_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci; ALTER TABLE `reverse_proxies` ADD PRIMARY KEY (`id`), ADD UNIQUE KEY `unique_hostname_port` (`hostname`,`listen_port`); ALTER TABLE `reverse_proxies` MODIFY `id` int UNSIGNED NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=3; COMMIT;
然后是PHP部分
<?php /** * Script to generate/update Nginx reverse proxy configuration based on MySQL table entries. * * Usage: php update_nginx_config.php */ // Database configuration $dbHost = 'localhost'; // Database host $dbName = 'reverse_proxies'; // Database name $dbUser = 'reverse_proxies'; // Database username $dbPass = 'XXXXXXXXXXX'; // Database password // Nginx configuration file path $nginxConfigPath = './0.auto-reverse-proxy.conf'; // Nginx configuration header $nginxConfigHeader = <<<EOL # Auto-generated reverse proxy configuration # Generated on: {date} EOL; // Function to escape variables for Nginx function escape_nginx($string) { return addcslashes($string, '\\"'); } try { // Establish a PDO connection $dsn = "mysql:host=$dbHost;dbname=$dbName;charset=utf8mb4"; $options = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, // Enable exceptions PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, // Fetch associative arrays PDO::ATTR_EMULATE_PREPARES => false, // Disable emulation ]; $pdo = new PDO($dsn, $dbUser, $dbPass, $options); // Fetch all reverse proxy entries $stmt = $pdo->query("SELECT hostname, listen_port, protocol, proxy_address, proxy_hostname, enable_websocket FROM reverse_proxies"); $reverseProxies = $stmt->fetchAll(); // Initialize the configuration content $configContent = $nginxConfigHeader; $configContent = str_replace('{date}', date('Y-m-d H:i:s'), $configContent); // Generate Nginx server blocks foreach ($reverseProxies as $proxy) { // Sanitize and escape variables $hostname = escape_nginx($proxy['hostname']); $listenPort = (int)$proxy['listen_port']; $protocol = strtoupper($proxy['protocol']); $proxyAddress = escape_nginx($proxy['proxy_address']); $proxyHostname = escape_nginx($proxy['proxy_hostname']); $enableWebsocket = (bool)$proxy['enable_websocket']; // Initialize listen directives $listenDirectives = ''; $sslConfig = ''; $websocketConfig = ''; if ($protocol === 'HTTPS') { // For HTTPS, include both HTTP and HTTPS listen directives $listenDirectives = "listen 80;\n listen [::]:80;\n listen 443 ssl;\n listen [::]:443 ssl;"; // Paths to SSL certificate and key files // Ensure these paths are correct and certificates exist $sslCertPath = "/www/server/panel/vhost/cert/cloudraft.cn/fullchain.pem"; $sslKeyPath = "/www/server/panel/vhost/cert/cloudraft.cn/privkey.pem"; $sslConfig = <<<EOL ssl_certificate {$sslCertPath}; ssl_certificate_key {$sslKeyPath}; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on; EOL; } else { // HTTP // For HTTP, only include the HTTP listen directive $listenDirectives = "listen {$listenPort};\n listen [::]:{$listenPort};"; } // WebSocket Configuration (if enabled) if ($enableWebsocket) { $websocketConfig = <<<EOL # WebSocket support proxy_http_version 1.1; proxy_set_header Upgrade \$http_upgrade; proxy_set_header Connection "upgrade"; EOL; } // Construct the server block $serverBlock = <<<EOL server { {$listenDirectives} server_name {$hostname}; root /www/wwwroot/router-sh-bgp.cloudraft.cn/reverse-proxy; {$sslConfig} location / { proxy_pass {$proxyAddress}; proxy_set_header Host {$proxyHostname}; proxy_set_header X-Real-IP \$remote_addr; proxy_set_header X-Forwarded-For \$proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto \$scheme; } } EOL; // Append the server block to the configuration content $configContent .= $serverBlock; } // Write the configuration to the file if (file_put_contents($nginxConfigPath, $configContent) === false) { throw new Exception("Failed to write to Nginx configuration file: {$nginxConfigPath}"); } echo "Nginx configuration successfully updated.\n"; // Optionally, reload Nginx to apply changes // Uncomment the following lines if you want the script to reload Nginx automatically /* $reloadOutput = []; $reloadStatus = 0; exec('sudo systemctl reload nginx', $reloadOutput, $reloadStatus); if ($reloadStatus !== 0) { throw new Exception("Failed to reload Nginx. Output: " . implode("\n", $reloadOutput)); } echo "Nginx reloaded successfully.\n"; */ } catch (PDOException $e) { // Handle database connection errors error_log("Database error: " . $e->getMessage()); exit("Database error occurred. Check logs for details.\n"); } catch (Exception $e) { // Handle general errors error_log("Error: " . $e->getMessage()); exit("An error occurred. Check logs for details.\n"); } ?>
最后是定时任务:
sudo -u root bash -c 'curl https://router-sh-bgp.cloudraft.cn/reverse-proxy/update.php && /etc/init.d/nginx reload'