Files
wevads-platform/scripts/api_server-supervisor.php
2026-02-26 04:53:11 +01:00

511 lines
21 KiB
PHP
Executable File

<?php
/**
* SERVER SUPERVISOR
* Auto-detect burned IPs, idle servers, auto-terminate to save costs
*/
header('Content-Type: application/json');
$pdo = new PDO("pgsql:host=localhost;dbname=adx_system", "admin", "admin123", [PDO::ATTR_ERRMODE => 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' => "<b>$title</b>\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']
]);
}