PDO::ERRMODE_EXCEPTION]); $pdo->exec(" CREATE TABLE IF NOT EXISTS admin.server_monitor ( id SERIAL PRIMARY KEY, server_id INTEGER, ip_address VARCHAR(50), provider VARCHAR(50), cloud_instance_id VARCHAR(255), status VARCHAR(50) DEFAULT 'active', last_output TIMESTAMP, emails_last_hour INTEGER DEFAULT 0, emails_last_check INTEGER DEFAULT 0, bounce_rate FLOAT DEFAULT 0, blacklist_count INTEGER DEFAULT 0, blacklisted_on TEXT, idle_minutes INTEGER DEFAULT 0, cost_per_hour FLOAT DEFAULT 0.05, total_cost FLOAT DEFAULT 0, auto_action VARCHAR(50), action_reason TEXT, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, terminated_at TIMESTAMP ); CREATE TABLE IF NOT EXISTS admin.supervisor_rules ( id SERIAL PRIMARY KEY, rule_name VARCHAR(100) UNIQUE, condition_type VARCHAR(50), threshold_value FLOAT, action VARCHAR(50), is_active BOOLEAN DEFAULT true, priority INTEGER DEFAULT 5 ); CREATE TABLE IF NOT EXISTS admin.supervisor_logs ( id SERIAL PRIMARY KEY, server_id INTEGER, event_type VARCHAR(100), details TEXT, action_taken VARCHAR(100), cost_saved FLOAT DEFAULT 0, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); CREATE TABLE IF NOT EXISTS admin.blacklist_checks ( id SERIAL PRIMARY KEY, ip_address VARCHAR(50), rbl_name VARCHAR(100), listed BOOLEAN DEFAULT false, checked_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); "); class ServerSupervisor { private $pdo; private $rbls = [ 'zen.spamhaus.org', 'bl.spamcop.net', 'b.barracudacentral.org', 'dnsbl.sorbs.net', 'spam.dnsbl.sorbs.net', 'cbl.abuseat.org', 'dnsbl-1.uceprotect.net', 'pbl.spamhaus.org', 'sbl.spamhaus.org', 'xbl.spamhaus.org' ]; private $huaweiApi = 'http://localhost:5821/api/huawei.php'; private $scalewayApi = 'http://localhost:5821/api/scaleway.php'; public function __construct($pdo) { $this->pdo = $pdo; $this->initRules(); } private function initRules() { $rules = [ ['idle_30min', 'idle_minutes', 30, 'warn', true, 1], ['idle_60min', 'idle_minutes', 60, 'stop', true, 2], ['idle_120min', 'idle_minutes', 120, 'terminate', true, 3], ['blacklist_1', 'blacklist_count', 1, 'warn', true, 4], ['blacklist_3', 'blacklist_count', 3, 'stop', true, 5], ['blacklist_5', 'blacklist_count', 5, 'terminate', true, 6], ['bounce_10pct', 'bounce_rate', 10, 'warn', true, 7], ['bounce_25pct', 'bounce_rate', 25, 'stop', true, 8], ['bounce_50pct', 'bounce_rate', 50, 'terminate', true, 9], ['no_output_15min', 'no_output_minutes', 15, 'warn', true, 10], ['no_output_30min', 'no_output_minutes', 30, 'stop', true, 11], ['cost_limit_10', 'total_cost', 10, 'warn', true, 12], ['cost_limit_50', 'total_cost', 50, 'terminate', true, 13] ]; foreach ($rules as $r) { $this->pdo->prepare("INSERT INTO admin.supervisor_rules (rule_name, condition_type, threshold_value, action, is_active, priority) VALUES (?, ?, ?, ?, ?, ?) ON CONFLICT (rule_name) DO NOTHING") ->execute($r); } } // ============================================ // MONITORING // ============================================ public function checkAllServers() { $servers = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE status != 'terminated'")->fetchAll(PDO::FETCH_ASSOC); $results = []; foreach ($servers as $server) { $results[] = $this->checkServer($server['id']); } return ['checked' => count($servers), 'results' => $results]; } public function checkServer($monitorId) { $server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC); if (!$server) return ['error' => 'Server not found']; $issues = []; $actions = []; // 1. Check PMTA output $pmtaStatus = $this->checkPMTAOutput($server['ip_address']); if ($pmtaStatus['idle']) { $idleMinutes = $pmtaStatus['idle_minutes']; $this->pdo->exec("UPDATE admin.server_monitor SET idle_minutes = $idleMinutes, emails_last_hour = {$pmtaStatus['emails_hour']} WHERE id = $monitorId"); $issues[] = "Idle for {$idleMinutes} minutes"; } else { $this->pdo->exec("UPDATE admin.server_monitor SET idle_minutes = 0, last_output = NOW(), emails_last_hour = {$pmtaStatus['emails_hour']} WHERE id = $monitorId"); } // 2. Check blacklists $blacklistResult = $this->checkBlacklists($server['ip_address']); if ($blacklistResult['listed_count'] > 0) { $listedOn = implode(',', $blacklistResult['listed_on']); $this->pdo->exec("UPDATE admin.server_monitor SET blacklist_count = {$blacklistResult['listed_count']}, blacklisted_on = '$listedOn' WHERE id = $monitorId"); $issues[] = "Blacklisted on {$blacklistResult['listed_count']} RBLs: $listedOn"; } // 3. Check bounce rate $bounceRate = $this->getBounceRate($server['ip_address']); $this->pdo->exec("UPDATE admin.server_monitor SET bounce_rate = $bounceRate WHERE id = $monitorId"); if ($bounceRate > 10) { $issues[] = "High bounce rate: {$bounceRate}%"; } // 4. Update cost $this->updateCost($monitorId, $server['cost_per_hour']); // 5. Apply rules $server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC); $action = $this->applyRules($server); if ($action) { $actions[] = $action; $this->executeAction($monitorId, $action['action'], $action['reason']); } return [ 'server_id' => $monitorId, 'ip' => $server['ip_address'], 'status' => $server['status'], 'issues' => $issues, 'actions' => $actions, 'metrics' => [ 'idle_minutes' => $server['idle_minutes'], 'blacklist_count' => $server['blacklist_count'], 'bounce_rate' => $bounceRate, 'emails_last_hour' => $pmtaStatus['emails_hour'], 'total_cost' => $server['total_cost'] ] ]; } private function checkPMTAOutput($ip) { // Try to connect to PMTA web interface $ch = curl_init("http://$ip:8080/status"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 5, CURLOPT_USERPWD => 'admin:admin' ]); $response = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch); if ($code != 200) { return ['idle' => true, 'idle_minutes' => 999, 'emails_hour' => 0, 'error' => 'PMTA not responding']; } // Parse PMTA status preg_match('/delivered:\s*(\d+)/i', $response, $delivered); preg_match('/queued:\s*(\d+)/i', $response, $queued); $emailsHour = intval($delivered[1] ?? 0); $queuedCount = intval($queued[1] ?? 0); // If no deliveries and no queue, server is idle $isIdle = ($emailsHour == 0 && $queuedCount == 0); return [ 'idle' => $isIdle, 'idle_minutes' => $isIdle ? 15 : 0, // Will accumulate over checks 'emails_hour' => $emailsHour, 'queued' => $queuedCount ]; } public function checkBlacklists($ip) { $reversed = implode('.', array_reverse(explode('.', $ip))); $listedOn = []; foreach ($this->rbls as $rbl) { $lookup = $reversed . '.' . $rbl; $result = @dns_get_record($lookup, DNS_A); $listed = !empty($result); // Log check $this->pdo->prepare("INSERT INTO admin.blacklist_checks (ip_address, rbl_name, listed) VALUES (?, ?, ?)") ->execute([$ip, $rbl, $listed]); if ($listed) { $listedOn[] = $rbl; } } return [ 'ip' => $ip, 'listed_count' => count($listedOn), 'listed_on' => $listedOn, 'clean' => empty($listedOn) ]; } private function getBounceRate($ip) { // Get bounce rate from tracking data $result = $this->pdo->query("SELECT COUNT(*) FILTER (WHERE bounce_type = 'hard') as hard_bounces, COUNT(*) as total FROM admin.bounces WHERE received_at > NOW() - INTERVAL '1 hour'")->fetch(PDO::FETCH_ASSOC); if ($result['total'] > 0) { return round(($result['hard_bounces'] / $result['total']) * 100, 2); } return 0; } private function updateCost($monitorId, $costPerHour) { // Add hourly cost (called every check, typically every 5 min = 1/12 of hour) $costIncrement = $costPerHour / 12; $this->pdo->exec("UPDATE admin.server_monitor SET total_cost = total_cost + $costIncrement WHERE id = $monitorId"); } // ============================================ // RULES ENGINE // ============================================ private function applyRules($server) { $rules = $this->pdo->query("SELECT * FROM admin.supervisor_rules WHERE is_active = true ORDER BY priority ASC")->fetchAll(PDO::FETCH_ASSOC); foreach ($rules as $rule) { $triggered = false; $currentValue = 0; switch ($rule['condition_type']) { case 'idle_minutes': $currentValue = $server['idle_minutes']; $triggered = $currentValue >= $rule['threshold_value']; break; case 'blacklist_count': $currentValue = $server['blacklist_count']; $triggered = $currentValue >= $rule['threshold_value']; break; case 'bounce_rate': $currentValue = $server['bounce_rate']; $triggered = $currentValue >= $rule['threshold_value']; break; case 'total_cost': $currentValue = $server['total_cost']; $triggered = $currentValue >= $rule['threshold_value']; break; case 'no_output_minutes': $lastOutput = strtotime($server['last_output'] ?? 'now'); $currentValue = (time() - $lastOutput) / 60; $triggered = $currentValue >= $rule['threshold_value']; break; } if ($triggered) { return [ 'rule' => $rule['rule_name'], 'action' => $rule['action'], 'reason' => "{$rule['condition_type']} = $currentValue (threshold: {$rule['threshold_value']})" ]; } } return null; } // ============================================ // ACTIONS // ============================================ private function executeAction($monitorId, $action, $reason) { $server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC); switch ($action) { case 'warn': $this->logEvent($monitorId, 'warning', $reason, 'alert_sent'); $this->sendAlert("⚠️ Server Warning", "Server {$server['ip_address']}: $reason"); break; case 'stop': $this->pdo->exec("UPDATE admin.server_monitor SET status = 'stopped', auto_action = 'stop', action_reason = '$reason' WHERE id = $monitorId"); $this->stopServer($server); $this->logEvent($monitorId, 'stop', $reason, 'server_stopped'); $this->sendAlert("🛑 Server Stopped", "Server {$server['ip_address']} stopped: $reason"); break; case 'terminate': $costSaved = $this->calculateCostSaved($server); $this->pdo->exec("UPDATE admin.server_monitor SET status = 'terminated', auto_action = 'terminate', action_reason = '$reason', terminated_at = NOW() WHERE id = $monitorId"); $this->terminateServer($server); $this->logEvent($monitorId, 'terminate', $reason, 'server_terminated', $costSaved); $this->sendAlert("🔴 Server Terminated", "Server {$server['ip_address']} terminated: $reason\nCost saved: \${$costSaved}"); break; } } private function stopServer($server) { $provider = strtolower($server['provider']); $instanceId = $server['cloud_instance_id']; if ($provider == 'huawei') { $this->callApi($this->huaweiApi, [ 'action' => 'server_action', 'server_id' => $instanceId, 'server_action' => 'stop' ]); } elseif ($provider == 'scaleway') { $this->callApi($this->scalewayApi, [ 'action' => 'server_action', 'server_id' => $instanceId, 'server_action' => 'poweroff' ]); } } private function terminateServer($server) { $provider = strtolower($server['provider']); $instanceId = $server['cloud_instance_id']; if ($provider == 'huawei') { $this->callApi($this->huaweiApi, [ 'action' => 'delete_server', 'server_id' => $instanceId ]); } elseif ($provider == 'scaleway') { $this->callApi($this->scalewayApi, [ 'action' => 'delete_server', 'server_id' => $instanceId ]); } // Also update auto_servers table $this->pdo->exec("UPDATE admin.auto_servers SET status = 'terminated' WHERE ip_address = '{$server['ip_address']}'"); } private function callApi($url, $data) { $ch = curl_init($url); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => http_build_query($data), CURLOPT_TIMEOUT => 30 ]); $response = curl_exec($ch); curl_close($ch); return json_decode($response, true); } private function calculateCostSaved($server) { // Estimate cost saved by terminating (remaining hours in typical campaign) $hoursRemaining = 24; // Assume would run 24 more hours return round($server['cost_per_hour'] * $hoursRemaining, 2); } private function logEvent($serverId, $eventType, $details, $actionTaken, $costSaved = 0) { $this->pdo->prepare("INSERT INTO admin.supervisor_logs (server_id, event_type, details, action_taken, cost_saved) VALUES (?, ?, ?, ?, ?)") ->execute([$serverId, $eventType, $details, $actionTaken, $costSaved]); } private function sendAlert($title, $message) { // Send to Telegram $config = $this->pdo->query("SELECT * FROM admin.telegram_config WHERE is_active = true LIMIT 1")->fetch(PDO::FETCH_ASSOC); if ($config) { $ch = curl_init("https://api.telegram.org/bot{$config['bot_token']}/sendMessage"); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => [ 'chat_id' => $config['chat_id'], 'text' => "$title\n\n$message", 'parse_mode' => 'HTML' ] ]); curl_exec($ch); curl_close($ch); } } // ============================================ // REGISTRATION & MANAGEMENT // ============================================ public function registerServer($data) { $this->pdo->prepare("INSERT INTO admin.server_monitor (server_id, ip_address, provider, cloud_instance_id, cost_per_hour) VALUES (?, ?, ?, ?, ?)") ->execute([ $data['server_id'] ?? null, $data['ip_address'], $data['provider'] ?? 'huawei', $data['cloud_instance_id'] ?? '', $data['cost_per_hour'] ?? 0.05 ]); return ['success' => true, 'monitor_id' => $this->pdo->lastInsertId()]; } public function forceTerminate($monitorId) { $server = $this->pdo->query("SELECT * FROM admin.server_monitor WHERE id = $monitorId")->fetch(PDO::FETCH_ASSOC); if ($server) { $this->terminateServer($server); $this->pdo->exec("UPDATE admin.server_monitor SET status = 'terminated', auto_action = 'manual_terminate', terminated_at = NOW() WHERE id = $monitorId"); return ['success' => true, 'message' => 'Server terminated']; } return ['success' => false, 'error' => 'Server not found']; } public function getStats() { return [ 'active_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE status = 'active'")->fetchColumn(), 'stopped_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE status = 'stopped'")->fetchColumn(), 'terminated_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE status = 'terminated'")->fetchColumn(), 'total_cost_today' => $this->pdo->query("SELECT COALESCE(SUM(total_cost), 0) FROM admin.server_monitor WHERE DATE(created_at) = CURRENT_DATE")->fetchColumn(), 'cost_saved_today' => $this->pdo->query("SELECT COALESCE(SUM(cost_saved), 0) FROM admin.supervisor_logs WHERE DATE(created_at) = CURRENT_DATE")->fetchColumn(), 'blacklisted_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE blacklist_count > 0 AND status = 'active'")->fetchColumn(), 'idle_servers' => $this->pdo->query("SELECT COUNT(*) FROM admin.server_monitor WHERE idle_minutes > 30 AND status = 'active'")->fetchColumn(), 'by_provider' => $this->pdo->query("SELECT provider, COUNT(*) as count, SUM(total_cost) as cost FROM admin.server_monitor GROUP BY provider")->fetchAll(PDO::FETCH_ASSOC) ]; } public function getRecentActions() { return $this->pdo->query("SELECT sl.*, sm.ip_address, sm.provider FROM admin.supervisor_logs sl LEFT JOIN admin.server_monitor sm ON sl.server_id = sm.id ORDER BY sl.created_at DESC LIMIT 50")->fetchAll(PDO::FETCH_ASSOC); } } $supervisor = new ServerSupervisor($pdo); $action = $_POST['action'] ?? $_GET['action'] ?? ''; switch ($action) { case 'check_all': echo json_encode($supervisor->checkAllServers()); break; case 'check': echo json_encode($supervisor->checkServer($_GET['monitor_id'] ?? $_POST['monitor_id'])); break; case 'check_blacklist': echo json_encode($supervisor->checkBlacklists($_GET['ip'] ?? $_POST['ip'])); break; case 'register': echo json_encode($supervisor->registerServer($_POST)); break; case 'terminate': echo json_encode($supervisor->forceTerminate($_POST['monitor_id'])); break; case 'servers': echo json_encode(['servers' => $pdo->query("SELECT * FROM admin.server_monitor ORDER BY created_at DESC")->fetchAll(PDO::FETCH_ASSOC)]); break; case 'rules': echo json_encode(['rules' => $pdo->query("SELECT * FROM admin.supervisor_rules ORDER BY priority")->fetchAll(PDO::FETCH_ASSOC)]); break; case 'update_rule': $pdo->prepare("UPDATE admin.supervisor_rules SET threshold_value = ?, is_active = ? WHERE id = ?") ->execute([$_POST['threshold'], $_POST['is_active'] ?? true, $_POST['rule_id']]); echo json_encode(['success' => true]); break; case 'logs': echo json_encode(['logs' => $supervisor->getRecentActions()]); break; case 'stats': echo json_encode($supervisor->getStats()); break; default: echo json_encode([ 'name' => 'Server Supervisor', 'description' => 'Auto-detect burned IPs, idle servers, auto-terminate to save costs', 'actions' => ['check_all','check','check_blacklist','register','terminate','servers','rules','update_rule','logs','stats'] ]); }