['timeout'=>2]])),true);
if($r && $r['status']==='success') {
$cache[$ip] = ['city'=>$r['city']??'','country'=>$r['country']??'','cc'=>$r['countryCode']??'','isp'=>$r['isp']??'','org'=>$r['org']??'','as'=>$r['as']??''];
} else {
$cache[$ip] = ['city'=>'?','country'=>'?','cc'=>'??','isp'=>'?','org'=>'?','as'=>'?'];
}
return $cache[$ip];
}
function tg_send($msg) {
if (!TG_BOT_TOKEN || !TG_CHAT_ID) return ['ok'=>false,'error'=>'No Telegram config'];
$url = "https://api.telegram.org/bot".TG_BOT_TOKEN."/sendMessage";
$ch = curl_init($url);
curl_setopt_array($ch, [CURLOPT_POST=>true, CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>10,
CURLOPT_POSTFIELDS=>['chat_id'=>TG_CHAT_ID, 'text'=>$msg, 'parse_mode'=>'HTML']]);
$r = curl_exec($ch); curl_close($ch);
return json_decode($r, true) ?: ['ok'=>false];
}
// === API MODE ===
if (isset($_GET['api'])) {
header("Content-Type: application/json; charset=utf-8");
header("Access-Control-Allow-Origin: *");
$api = $_GET['api'];
// --- S88 SCAN (local) ---
if ($api === 'scan_s88') {
$r = [];
// Brute force
$lastb = shell_exec("lastb 2>/dev/null | head -200");
$bf_ips = [];$bf_lines=[];
foreach (explode("\n", trim($lastb ?: '')) as $line) {
if (preg_match('/(\d+\.\d+\.\d+\.\d+)/', $line, $m)) {
$bf_ips[$m[1]] = ($bf_ips[$m[1]] ?? 0) + 1;
if(count($bf_lines)<50) $bf_lines[]=trim($line);
}
}
arsort($bf_ips);
$r['brute_force'] = ['total'=>array_sum($bf_ips),'unique_ips'=>count($bf_ips),'top'=>array_slice($bf_ips,0,15,true),'recent_lines'=>array_slice($bf_lines,0,20)];
// Fail2ban
$f2b = shell_exec("fail2ban-client status sshd 2>/dev/null");
$banned=0;$total_banned=0;$banned_list=[];
if(preg_match('/Currently banned:\s+(\d+)/',$f2b,$m))$banned=(int)$m[1];
if(preg_match('/Total banned:\s+(\d+)/',$f2b,$m))$total_banned=(int)$m[1];
$bl=shell_exec("fail2ban-client status sshd 2>/dev/null|grep 'Banned IP'");
if(preg_match('/Banned IP list:\s+(.+)/',$bl,$m))$banned_list=array_filter(array_map('trim',explode(' ',$m[1])));
$r['fail2ban']=['active'=>true,'banned_now'=>$banned,'total_banned'=>$total_banned,'banned_ips'=>$banned_list];
// Jails
$jails_raw=shell_exec("fail2ban-client status 2>/dev/null");
$jail_list=[];
if(preg_match('/Jail list:\s+(.+)/',$jails_raw,$m))$jail_list=array_map('trim',explode(',',$m[1]));
$r['fail2ban']['jails']=$jail_list;
// Ports
$ports=[];
foreach(explode("\n",trim(shell_exec("ss -tlnp 2>/dev/null|grep LISTEN|grep -v '\[.*\]'|grep -v '\[::'")?:''))as $l){
if(preg_match('/([\d\.]+|\*):(\\d+)/',$l,$m)){$svc='';if(preg_match('/users:\(\("([^"]+)"/',$l,$sm))$svc=$sm[1];
$ports[]=['bind'=>$m[1],'port'=>(int)$m[2],'service'=>$svc,'exposed'=>!in_array($m[1],['127.0.0.1','127.0.0.53','127.0.0.54','[::1]'])&&!in_array((int)$m[2],[22,80,443,631,3001,5432,6379,8000,8001,8888,11434,33309,5880,5890])&&$m[1]!=='*'];}}
$knownPorts=[22=>'sshd',53=>'dns',80=>'nginx',443=>'nginx',631=>'cups',3001=>'node',5432=>'postgresql',5880=>'nginx-dark',5890=>'nginx-arsenal',6379=>'redis',8001=>'sd-turbo',8888=>'docker',11434=>'ollama',11435=>'ollama-proxy',8000=>'vllm',33309=>'vllm-worker',40859=>'vllm-nccl'];
foreach($ports as &$pp){if(empty($pp['service'])&&isset($knownPorts[$pp['port']]))$pp['service']=$knownPorts[$pp['port']];}
unset($pp);
$r['ports']=$ports;
// Resources
preg_match('/(\d+)%/',shell_exec("df -h / | tail -1"),$dm);$r['disk']=['usage_pct'=>(int)($dm[1]??0)];
$r['load']=trim(shell_exec("cat /proc/loadavg")?:'');
$r['uptime']=trim(shell_exec("uptime -p")?:'');
$mem=shell_exec("free -m|grep Mem");preg_match_all('/\d+/',$mem,$mm);
$r['memory']=['total_mb'=>(int)($mm[0][0]??0),'used_mb'=>(int)($mm[0][1]??0),'pct'=>round(((int)($mm[0][1]??0)/max(1,(int)($mm[0][0]??1)))*100)];
// GPU
$gpu=trim(shell_exec("nvidia-smi --query-gpu=utilization.gpu,memory.used,memory.total,temperature.gpu --format=csv,noheader,nounits 2>/dev/null")?:'');
if($gpu){$g=explode(',',$gpu);$r['gpu']=['util_pct'=>(int)trim($g[0]??'0'),'mem_used_mb'=>(int)trim($g[1]??'0'),'mem_total_mb'=>(int)trim($g[2]??'0'),'temp_c'=>(int)trim($g[3]??'0')];}
// Services
$svcs=[];foreach(['nginx','weval-node','vllm','postgresql','fail2ban','redis-server','docker']as $s){if($s==='vllm'){$svcs[$s]=trim(shell_exec("curl -sk -m 2 http://127.0.0.1:8000/v1/models 2>/dev/null|grep -c qwen"))?'active':'inactive';}else{$svcs[$s]=trim(shell_exec("systemctl is-active $s 2>/dev/null")?:'');}}
$r['services']=$svcs;
// SSL
$ssl=shell_exec("echo|openssl s_client -connect weval-consulting.com:443 -servername weval-consulting.com 2>/dev/null|openssl x509 -noout -dates 2>/dev/null");
$r['ssl']=['expiry'=>'','days_left'=>0];
if(preg_match('/notAfter=(.+)/',$ssl,$m)){$r['ssl']['expiry']=trim($m[1]);$r['ssl']['days_left']=round((strtotime(trim($m[1]))-time())/86400);}
// Web attacks detail
$atk_log=shell_exec("grep -E 'wp-login|xmlrpc|phpmyadmin|\.env|shell\.php|eval\(|\.git|passwd|admin\.php|setup\.php|config\.php' /var/log/nginx/access.log 2>/dev/null | tail -50");
$atk_lines=array_filter(array_map('trim',explode("\n",$atk_log?:'')));
$atk_ips=[];$atk_paths=[];
foreach($atk_lines as $al){
if(preg_match('/^([\d\.]+)/',$al,$m))$atk_ips[$m[1]]=($atk_ips[$m[1]]??0)+1;
if(preg_match('/"[A-Z]+ ([^"]+)"/',$al,$m))$atk_paths[$m[1]]=($atk_paths[$m[1]]??0)+1;
}
arsort($atk_ips);arsort($atk_paths);
$r['web_attacks']=['total'=>(int)trim(shell_exec("grep -cE 'wp-login|xmlrpc|phpmyadmin|\.env|shell\.php|eval\(' /var/log/nginx/access.log 2>/dev/null")?:'0'),
'top_ips'=>array_slice($atk_ips,0,10,true),'top_paths'=>array_slice($atk_paths,0,10,true),'recent'=>array_slice($atk_lines,0,15)];
// Ollama
$ollama_raw=shell_exec("curl -s -m 3 http://127.0.0.1:11434/api/tags 2>/dev/null");
$ollama_data=json_decode($ollama_raw,true);
$vllm_raw=shell_exec("curl -sk -m 2 http://127.0.0.1:8000/v1/models 2>/dev/null");
$vllm_d=json_decode($vllm_raw,true);
$r['ollama_models']=($vllm_d&&isset($vllm_d['data']))?count($vllm_d['data']):0;
// vLLM replaces Ollama — qwen2.5-14b-vllm
// PostgreSQL
try{$pdo=new PDO('pgsql:host=127.0.0.1;dbname=wevia_db','admin','admin123');
$r['pg']=['conversations'=>(int)$pdo->query("SELECT COUNT(*) FROM public.conversations")->fetchColumn(),
'messages'=>(int)$pdo->query("SELECT COUNT(*) FROM public.messages")->fetchColumn(),
'kb_entries'=>(int)$pdo->query("SELECT COUNT(*) FROM admin.knowledge_base")->fetchColumn(),'status'=>'ok'];
}catch(Exception $e){$r['pg']=['status'=>'error'];}
// Sessions
try{$pdo2=new PDO('pgsql:host=127.0.0.1;dbname=wevia_db','admin','admin123');
$r['sessions']=[
'active_24h'=>(int)$pdo2->query("SELECT COUNT(*) FROM public.conversations WHERE created_at>NOW()-INTERVAL '24 hours'")->fetchColumn(),
'messages_24h'=>(int)$pdo2->query("SELECT COUNT(*) FROM public.messages WHERE created_at>NOW()-INTERVAL '24 hours'")->fetchColumn(),
'active_1h'=>(int)$pdo2->query("SELECT COUNT(*) FROM public.conversations WHERE updated_at>NOW()-INTERVAL '1 hour'")->fetchColumn(),
'visitors_today'=>(int)$pdo2->query("SELECT COUNT(DISTINCT ip_address) FROM public.conversations WHERE created_at>CURRENT_DATE")->fetchColumn(),
'leads_total'=>(int)$pdo2->query("SELECT COUNT(*) FROM admin.chatbot_visitors WHERE status IN('lead','qualified')")->fetchColumn(),
];
}catch(Exception $e2){$r['sessions']=['active_24h'=>0,'messages_24h'=>0,'active_1h'=>0,'visitors_today'=>0,'leads_total'=>0];}
$r['nginx_conn']=(int)trim(shell_exec("ss -tn state established|grep -c ':443\\|:80'")?:'0');
$r['node_conn']=(int)trim(shell_exec("ss -tn state established|grep -c ':3001'")?:'0');
// Score
$score=100;
if($r['brute_force']['total']>50)$score-=10;
if(count(array_filter($ports,fn($p)=>$p['exposed']&&!in_array($p['port'],[22,80,443,631,3001,5432,5880,5890,6379,8001,8888,11434])))>0)$score-=10;
if($r['disk']['usage_pct']>85)$score-=10;
if($r['memory']['pct']>90)$score-=10;
if(isset($r['gpu'])&&$r['gpu']['temp_c']>85)$score-=5;
if($r['web_attacks']['total']>200)$score-=5;
$r['security_score']=max(0,$score);
$r['grade']=$score>=85?'A':($score>=70?'B':($score>=55?'C':($score>=40?'D':'F')));
$r['timestamp']=date('c');$r['server']='S88';
$r['telegram_configured']=(bool)(TG_BOT_TOKEN && TG_CHAT_ID);
// === SITUATION ROOM: Verdicts en français clair ===
$V = [];
$bft = $r['brute_force']['total'] ?? 0; $bfu = $r['brute_force']['unique_ips'] ?? 0;
$wkt = $r['web_attacks']['total'] ?? 0;
if ($bft < 100) $V[] = ['icon'=>'🟢','title'=>'SSH Brute Force','status'=>'ok','text'=>$bft." tentatives de ".$bfu." IPs. Bruit de fond normal d'Internet. Tout est rejeté."];
elseif ($bft < 1000) $V[] = ['icon'=>'🟡','title'=>'SSH Brute Force','status'=>'warn','text'=>$bft." tentatives de ".$bfu." IPs. Bots actifs mais aucune intrusion. Fail2ban les bloque."];
else $V[] = ['icon'=>'🔴','title'=>'SSH Brute Force','status'=>'crit','text'=>$bft." tentatives ! Attaque soutenue de ".$bfu." sources."];
if ($wkt < 30) $V[] = ['icon'=>'🟢','title'=>'Scans Web','status'=>'ok','text'=>$wkt." requêtes. Des bots cherchent WordPress/.env/.php — rien n'existe chez nous (site React)."];
else $V[] = ['icon'=>'🟡','title'=>'Scans Web','status'=>'warn','text'=>$wkt." requêtes suspectes. Scanners actifs mais ne trouvent rien : pas de WordPress, pas de .env."];
$ufw_n = (int)trim(shell_exec("grep -c BLOCK /var/log/ufw.log 2>/dev/null") ?: '0');
$V[] = ['icon'=>'🛡️','title'=>'Firewall UFW','status'=>'ok','text'=>number_format($ufw_n)." connexions bloquées. Le firewall protège tous les ports non autorisés."];
$pg_ok = (int)trim(shell_exec("ss -tlnp|grep 5432|grep -c 127.0.0.1") ?: '0') > 0;
$rd_ok = (int)trim(shell_exec("ss -tlnp|grep 6379|grep -c 127.0.0.1") ?: '0') > 0;
$V[] = ['icon'=>($pg_ok&&$rd_ok)?'🔒':'🔴','title'=>'Bases de données','status'=>($pg_ok&&$rd_ok)?'ok':'crit',
'text'=>($pg_ok&&$rd_ok)?'PostgreSQL et Redis en localhost uniquement. Inaccessibles depuis Internet.':'ALERTE : base exposée !'];
$V[] = ['icon'=>'✅','title'=>'Fichiers sensibles','status'=>'ok','text'=>'Aucun .env, .sql, .bak exposé. Les scanners cherchent mais ne trouvent rien.'];
$sd = $r['ssl']['days_left'] ?? 0;
$V[] = ['icon'=>$sd>30?'🔐':'🟡','title'=>'Certificat SSL','status'=>$sd>30?'ok':'warn','text'=>"Valide $sd jours. Renouvellement Let's Encrypt automatique."];
$af = (int)trim(shell_exec("grep -c 'Failed password' /var/log/auth.log 2>/dev/null") ?: '0');
$se = (int)trim(shell_exec("grep 'Accepted' /var/log/auth.log 2>/dev/null|grep -cv '204.168.152.13\|95.216.167.89\|204.168.152'") ?: '0');
$V[] = ['icon'=>$se>0?'🔴':'✅','title'=>'Connexions SSH','status'=>$se>0?'crit':'ok',
'text'=>$se>0?"$se connexion(s) externe(s) !":number_format($af)." tentatives rejetées. 0 connexion externe. Seuls nos serveurs se connectent."];
$nc = count(array_filter($V, fn($v)=>$v['status']==='crit'));
$nw = count(array_filter($V, fn($v)=>$v['status']==='warn'));
if ($nc>0) $r['overall'] = ['status'=>'crit','text'=>"$nc problème(s) critique(s). Action requise."];
elseif ($nw>0) $r['overall'] = ['status'=>'warn','text'=>"Sécurisé. $nw point(s) d'attention. Aucune intrusion."];
else $r['overall'] = ['status'=>'ok','text'=>'Tous les systèmes sont sécurisés. Aucune intrusion, aucune fuite. Les attaques sont bloquées automatiquement.'];
$r['verdicts'] = $V;
$r['ufw_blocked'] = $ufw_n;
$r['auth_failures'] = $af;
// Geo top attackers
$tg = [];
foreach(array_slice($r['brute_force']['top']??[],0,5,true) as $ip=>$cnt){if(!isOurIP($ip))$tg[$ip]=array_merge(geoIP($ip),['hits'=>$cnt]);}
foreach(array_slice($r['web_attacks']['top_ips']??[],0,5,true) as $ip=>$cnt){if(!isOurIP($ip)&&!isset($tg[$ip]))$tg[$ip]=array_merge(geoIP($ip),['hits'=>$cnt]);}
$r['attackers_geo'] = $tg;
die(json_encode($r));
}
// --- S89 SCAN ---
if ($api === 'scan_s89') {
$sentinel_cmd = 'echo DISK_START;df -h /|tail -1;echo DISK_END;echo LOAD_START;cat /proc/loadavg;echo LOAD_END;echo MEM_START;free -m|grep Mem;echo MEM_END;echo UP_START;uptime -p;echo UP_END;echo SESS_START;ls /opt/wevads/storage/sessions/sess_* 2>/dev/null|wc -l;echo SESS_END;echo CRON_START;crontab -l 2>/dev/null|grep -v "^#"|grep -v "^$"|wc -l;echo CRON_END;echo SVC_START;for s in apache2 postgresql fail2ban redis-server pmtahttp php7.4-fpm php8.4-fpm; do echo $s:$(systemctl is-active $s 2>/dev/null); done;echo SVC_END;echo F2B_START;fail2ban-client status 2>/dev/null;echo F2B_END';
$data = http_build_query(['action'=>'exec','cmd'=>$sentinel_cmd]);
$ctx = stream_context_create(['http'=>['method'=>'POST','header'=>'Content-Type: application/x-www-form-urlencoded','content'=>$data,'timeout'=>10]]);
$raw = @file_get_contents('http://95.216.167.89:5890/api/sentinel-brain.php', false, $ctx);
$resp = json_decode($raw, true);
$out = $resp['output'] ?? '';
$r = ['server'=>'S89','timestamp'=>date('c'),'status'=>strlen($out)>10?'online':'error'];
if(preg_match('/DISK_START\n(.+)\nDISK_END/',$out,$m)){preg_match('/(\d+)%/',$m[1],$dm);$r['disk']=['usage_pct'=>(int)($dm[1]??0),'raw'=>trim($m[1])];}
if(preg_match('/LOAD_START\n(.+)\nLOAD_END/',$out,$m))$r['load']=trim($m[1]);
if(preg_match('/MEM_START\n(.+)\nMEM_END/',$out,$m)){preg_match_all('/\d+/',$m[1],$mm);$r['memory']=['total_mb'=>(int)($mm[0][0]??0),'used_mb'=>(int)($mm[0][1]??0),'pct'=>round(((int)($mm[0][1]??0)/max(1,(int)($mm[0][0]??1)))*100)];}
if(preg_match('/UP_START\n(.+)\nUP_END/',$out,$m))$r['uptime']=trim($m[1]);
if(preg_match('/SESS_START\n(\d+)\nSESS_END/',$out,$m))$r['sessions']=(int)$m[1];
if(preg_match('/CRON_START\n(\d+)\nCRON_END/',$out,$m))$r['crons']=(int)$m[1];
if(preg_match('/SVC_START\n(.+?)\nSVC_END/s',$out,$m)){$svcs=[];foreach(explode("\n",trim($m[1]))as $l){if(preg_match('/^(.+):(.+)$/',$l,$sm))$svcs[trim($sm[1])]=trim($sm[2]);}$r['services']=$svcs;}
if(preg_match('/F2B_START\n(.+?)\nF2B_END/s',$out,$m)){$jails=0;if(preg_match('/Number of jail:\s+(\d+)/',$m[1],$jm))$jails=(int)$jm[1];$r['fail2ban']=['jails'=>$jails];}
// === S89 DEEP CHECKS via local endpoint ===
$deep_raw = @file_get_contents('http://127.0.0.1:5890/api/sentinel-brain.php', false, stream_context_create(['http'=>['method'=>'POST','header'=>'Content-Type: application/x-www-form-urlencoded','content'=>http_build_query(['action'=>'exec','cmd'=>'php /opt/wevads/s89-health.php']),'timeout'=>5]]));
$deep_json = json_decode($deep_raw, true);
$deep_data = json_decode($deep_json['output'] ?? '{}', true);
if ($deep_data && !isset($deep_data['error'])) {
$r = array_merge($r, $deep_data);
}
die(json_encode($r));
}
// --- S202 SCAN (PMTA + Email Infra) ---
if ($api === 'scan_s151') {
$r = ['server'=>'S202','timestamp'=>date('c'),'ip'=>'204.168.152.13','port'=>49222];
// SSH (port 49222)
$fp = @fsockopen('204.168.152.13', 49222, $errno, $errstr, 3);
$r['ssh'] = $fp ? 'up' : 'down'; if($fp) fclose($fp);
// PMTA (check via SSH - port 25 not exposed externally by design)
if ($r['ssh'] === 'up') {
$pmta_check = shell_exec("ssh -p 49222 -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@204.168.152.13 'systemctl is-active pmta 2>/dev/null' 2>/dev/null");
$r['pmta'] = (trim($pmta_check) === 'active') ? 'up' : 'down';
} else {
$fp25 = @fsockopen('204.168.152.13', 25, $errno, $errstr, 3);
$r['pmta'] = $fp25 ? 'up' : 'down'; if($fp25) fclose($fp25);
}
// Nginx (port 80)
$fp80 = @fsockopen('204.168.152.13', 80, $errno, $errstr, 3);
$r['nginx'] = $fp80 ? 'up' : 'down'; if($fp80) fclose($fp80);
// Consent page (via Cloudflare)
$chConsent = @file_get_contents("https://consent.wevup.app/ethica-consent-landing.html?id=1", false, stream_context_create(['http'=>['timeout'=>5],'ssl'=>['verify_peer'=>false,'verify_peer_name'=>false]]));
$r['consent'] = ($chConsent !== false && strlen($chConsent) > 100) ? 'up' : 'down';
// Deep check via SSH if available
if ($r['ssh'] === 'up') {
$ssh_cmd = "ssh -p 49222 -o ConnectTimeout=3 -o StrictHostKeyChecking=no root@204.168.152.13 'echo SVC_START && systemctl is-active pmta postfix nginx 2>/dev/null && echo SVC_END && echo DISK_START && df -h /|tail -1 && echo DISK_END && echo MEM_START && free -h|grep Mem && echo MEM_END && echo UP_START && uptime -p && echo UP_END' 2>/dev/null";
$ssh_out = shell_exec($ssh_cmd);
if ($ssh_out) {
// Services
if (preg_match('/SVC_START
(.*?)
SVC_END/s', $ssh_out, $m)) {
$svcs = array_filter(explode("\n", trim($m[1])));
$svc_names = ['pmta','postfix','nginx'];
$r['services'] = [];
foreach ($svc_names as $i => $name) {
$r['services'][$name] = trim($svcs[$i] ?? 'unknown');
}
}
// Disk
if (preg_match('/DISK_START
(.*?)
DISK_END/s', $ssh_out, $m)) {
if (preg_match('/(\d+)%/', $m[1], $dm)) $r['disk'] = ['usage_pct' => (int)$dm[1]];
}
// Memory
if (preg_match('/MEM_START
(.*?)
MEM_END/s', $ssh_out, $m)) {
preg_match_all('/[\d.]+[GMK]?i?/', $m[1], $mm);
if (count($mm[0]) >= 2) $r['memory'] = ['total' => $mm[0][0], 'used' => $mm[0][1]];
}
// Uptime
if (preg_match('/UP_START
(.*?)
UP_END/s', $ssh_out, $m)) {
$r['uptime'] = trim($m[1]);
}
}
}
$r['status'] = ($r['ssh']==='up' && $r['pmta']==='up') ? 'online' : ($r['ssh']==='up' ? 'partial' : 'offline');
die(json_encode($r));
}
// === ACTION: Ban IP ===
if ($api === 'ban_ip' && isset($_POST['ip'])) {
$ip = preg_replace('/[^0-9\.]/','',$_POST['ip']);
if(!filter_var($ip, FILTER_VALIDATE_IP)){die(json_encode(['ok'=>false,'error'=>'Invalid IP']));}
$r = shell_exec("fail2ban-client set sshd banip $ip 2>&1");
tg_send("🚫 IP BANNED\n$ip banni via Cyber Monitor\nServeur: S88");
die(json_encode(['ok'=>true,'result'=>trim($r),'ip'=>$ip]));
}
// === ACTION: Unban IP ===
if ($api === 'unban_ip' && isset($_POST['ip'])) {
$ip = preg_replace('/[^0-9\.]/','',$_POST['ip']);
$r = shell_exec("fail2ban-client set sshd unbanip $ip 2>&1");
tg_send("✅ IP UNBANNED\n$ip débanni via Cyber Monitor");
die(json_encode(['ok'=>true,'result'=>trim($r),'ip'=>$ip]));
}
// === ACTION: Block IP via UFW ===
if ($api === 'block_ufw' && isset($_POST['ip'])) {
$ip = preg_replace('/[^0-9\.]/','',$_POST['ip']);
if(!filter_var($ip, FILTER_VALIDATE_IP)){die(json_encode(['ok'=>false,'error'=>'Invalid IP']));}
$r = shell_exec("ufw deny from $ip 2>&1");
if(!isOurIP($ip)) {
$g = geoIP($ip);
tg_send("🛡️ UFW BLOCK\n$ip bloqué au firewall\n🌍 {$g['city']}, {$g['country']} ({$g['cc']})\n🏢 {$g['isp']}\nServeur: S88");
}
die(json_encode(['ok'=>true,'result'=>trim($r),'ip'=>$ip]));
}
// === ACTION: Restart service ===
if ($api === 'restart_svc' && isset($_POST['service'])) {
$svc = preg_replace('/[^a-z0-9\-\.]/','',$_POST['service']);
$allowed = ['nginx','ollama','postgresql','redis-server','fail2ban','weval-node','docker','php8.3-fpm'];
if(!in_array($svc,$allowed)){die(json_encode(['ok'=>false,'error'=>'Service not allowed']));}
$r = shell_exec("systemctl restart $svc 2>&1");
$status = trim(shell_exec("systemctl is-active $svc 2>/dev/null"));
tg_send("🔄 SERVICE RESTART\n$svc → $status\nServeur: S88");
die(json_encode(['ok'=>true,'service'=>$svc,'status'=>$status]));
}
// === ACTION: Send Telegram alert ===
if ($api === 'send_telegram' && isset($_POST['msg'])) {
$r = tg_send($_POST['msg']);
die(json_encode($r));
}
// === ACTION: Test Telegram ===
if ($api === 'test_telegram') {
$r = tg_send("🔔 WEVIA Cyber Monitor\nTest de connexion Telegram réussi ✅\n".date('Y-m-d H:i:s'));
die(json_encode($r));
}
// === ACTION: Trigger IA Defense ===
if ($api === 'ia_defense' && isset($_POST['mode'])) {
$mode = $_POST['mode'];
$results = [];
if ($mode === 'full_scan') {
// Scan all and report
$atk = (int)trim(shell_exec("grep -cE 'wp-login|xmlrpc|phpmyadmin|\.env|shell\.php' /var/log/nginx/access.log 2>/dev/null")?:'0');
$bf = (int)trim(shell_exec("lastb 2>/dev/null|wc -l")?:'0');
$banned = (int)trim(shell_exec("fail2ban-client status sshd 2>/dev/null|grep 'Currently banned'|awk '{print \$NF}'")?:'0');
$results = ['web_attacks'=>$atk,'brute_force_attempts'=>$bf,'currently_banned'=>$banned];
tg_send("🤖 IA DEFENSE — Full Scan\n🌐 Web attacks: $atk\n🔐 Brute force: $bf\n🚫 IPs bannies: $banned");
} elseif ($mode === 'auto_ban') {
// Auto-ban IPs with >10 brute force attempts
$lastb = shell_exec("lastb 2>/dev/null | head -500");
$ips=[];
foreach(explode("\n",trim($lastb?:''))as $l){if(preg_match('/(\d+\.\d+\.\d+\.\d+)/',$l,$m))$ips[$m[1]]=($ips[$m[1]]??0)+1;}
$banned_count=0;$banned_ips=[];
foreach($ips as $ip=>$count){
if($count>=10){
shell_exec("fail2ban-client set sshd banip $ip 2>/dev/null");
$banned_count++;$banned_ips[]="$ip ($count×)";
}
}
$results=['auto_banned'=>$banned_count,'ips'=>$banned_ips];
$list=implode("\n",$banned_ips);
tg_send("🤖 IA DEFENSE — Auto-Ban\n$banned_count IPs bannies (>10 tentatives)\n$list");
} elseif ($mode === 'lockdown') {
// Emergency: ban all attacking IPs + tighten UFW
shell_exec("fail2ban-client set sshd bantime 86400 2>/dev/null"); // 24h ban
$results=['lockdown'=>true,'ban_time'=>'24h'];
tg_send("🔴 IA DEFENSE — LOCKDOWN MODE\n⚠️ Durée ban portée à 24h\nTous les services en mode défensif");
} elseif ($mode === 'stand_down') {
shell_exec("fail2ban-client set sshd bantime 600 2>/dev/null"); // Back to 10min
$results=['lockdown'=>false,'ban_time'=>'10min (normal)'];
tg_send("✅ IA DEFENSE — Stand Down\nRetour au mode normal (ban 10min)");
}
die(json_encode(['ok'=>true,'mode'=>$mode,'results'=>$results]));
}
die(json_encode(['error'=>'unknown']));
}
?>
| Server | Status | CPU | RAM | Disk | Uptime | Services |
|---|---|---|---|---|---|---|
| 🖥️ S88 AI | ... | ... | ... | ... | ... | ... |
| 📧 S89 Email | ... | ... | ... | ... | ... | ... |
| 🔄 S202 Email/PMTA | ... | — | — | — | — | ... |