1535 lines
80 KiB
PHP
1535 lines
80 KiB
PHP
<?php
|
|
header("Content-Type: application/json");
|
|
$k=$_REQUEST["k"]??"";if($k!=="BLADE2026"){echo json_encode(["e"=>"auth"]);exit;}
|
|
|
|
// OPUS v3 HELPER — écriture fichier robuste (fixe silent-fail récurrent sur fichiers root-owned)
|
|
// Retourne ['ok'=>bool, 'bytes'=>int|false, 'md5'=>string|null, 'via'=>'direct'|'sudo_cp'|'fail', 'marker_ok'=>bool]
|
|
if (!function_exists('opus_write_safe')) {
|
|
function opus_write_safe($target, $content, $verify_marker = null, $exec_bit = false) {
|
|
if (!$target || !is_string($content)) return ['ok'=>false,'err'=>'bad_args'];
|
|
$dir = dirname($target);
|
|
if (!is_dir($dir)) {
|
|
@mkdir($dir, 0755, true);
|
|
if (!is_dir($dir)) @shell_exec("sudo mkdir -p " . escapeshellarg($dir));
|
|
}
|
|
// chattr -i si present
|
|
$attr = @shell_exec("lsattr " . escapeshellarg($target) . " 2>/dev/null");
|
|
$had_immut = ($attr && strpos($attr, 'i') !== false && strpos($attr, '-i-') === false);
|
|
if ($had_immut) @shell_exec("sudo chattr -i " . escapeshellarg($target));
|
|
|
|
$via = 'direct';
|
|
$n = @file_put_contents($target, $content);
|
|
if ($n === false) {
|
|
$via = 'sudo_cp';
|
|
$tmpw = tempnam('/tmp', 'ows');
|
|
@file_put_contents($tmpw, $content);
|
|
@shell_exec("sudo cp " . escapeshellarg($tmpw) . " " . escapeshellarg($target));
|
|
@shell_exec("sudo chown www-data:www-data " . escapeshellarg($target));
|
|
@unlink($tmpw);
|
|
clearstatcache();
|
|
$n = file_exists($target) ? filesize($target) : false;
|
|
}
|
|
if ($exec_bit) { @chmod($target, 0755); @shell_exec("sudo chmod +x " . escapeshellarg($target)); }
|
|
if ($had_immut) @shell_exec("sudo chattr +i " . escapeshellarg($target));
|
|
|
|
$marker_ok = true;
|
|
if ($verify_marker !== null) {
|
|
$final = @file_get_contents($target);
|
|
$marker_ok = ($final && strpos($final, $verify_marker) !== false);
|
|
}
|
|
return [
|
|
'ok' => ($n !== false && $marker_ok),
|
|
'bytes' => $n,
|
|
'md5' => ($n !== false) ? @md5_file($target) : null,
|
|
'via' => ($n === false ? 'fail' : $via),
|
|
'marker_ok' => $marker_ok,
|
|
];
|
|
}
|
|
}
|
|
|
|
$action=$_REQUEST["action"]??"";
|
|
$results=[];
|
|
|
|
switch($action) {
|
|
case "test_providers":
|
|
$svcs=["deepseek","copilot","meta","qwen","perplexity","duckduckgo","lechat","huggingchat"];
|
|
// Parallel test with multi_curl
|
|
$mh = curl_multi_init();
|
|
$handles = [];
|
|
foreach($svcs as $s) {
|
|
$ch = curl_init("http://localhost/api/wevia-webchat-direct.php");
|
|
curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>15,CURLOPT_HTTPHEADER=>["Content-Type: application/json"],CURLOPT_POSTFIELDS=>json_encode(["service"=>$s,"message"=>"bonjour"])]);
|
|
curl_multi_add_handle($mh, $ch);
|
|
$handles[$s] = $ch;
|
|
}
|
|
$running = null;
|
|
do { curl_multi_exec($mh, $running); usleep(100000); } while ($running > 0);
|
|
foreach($handles as $s=>$ch) {
|
|
$r = json_decode(curl_multi_getcontent($ch), true);
|
|
$results[$s] = $r["provider"] ?? "FAIL";
|
|
curl_multi_remove_handle($mh, $ch);
|
|
curl_close($ch);
|
|
}
|
|
curl_multi_close($mh);
|
|
break;
|
|
// OLD sequential code below (disabled)
|
|
foreach($svcs as $DISABLED) {
|
|
$ch=curl_init("http://localhost/api/wevia-webchat-direct.php");
|
|
curl_setopt_array($ch,[CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>12,CURLOPT_HTTPHEADER=>["Content-Type: application/json"],CURLOPT_POSTFIELDS=>json_encode(["service"=>$s,"message"=>"bonjour"])]);
|
|
$r=json_decode(curl_exec($ch),true);curl_close($ch);
|
|
$results[$s]=$r["provider"]??"FAIL";
|
|
}
|
|
break;
|
|
case "webchat":
|
|
$ch=curl_init("http://localhost:8902/health");
|
|
curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>5]);
|
|
$r=json_decode(curl_exec($ch),true);curl_close($ch);
|
|
$results=$r;
|
|
break;
|
|
case "nonreg":
|
|
$ch=curl_init("http://127.0.0.1/api/nonreg-api.php?cat=all");
|
|
curl_setopt_array($ch,[CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>30]);
|
|
$r=json_decode(curl_exec($ch),true);curl_close($ch);
|
|
$results=["pass"=>$r["pass"]??0,"total"=>$r["total"]??0];
|
|
break;
|
|
case "reconcile":
|
|
$results["ports"]=trim(shell_exec('ss -tlnp | grep LISTEN | wc -l'));
|
|
$results["pages"]=trim(shell_exec('ls /var/www/html/*.html 2>/dev/null | wc -l'));
|
|
$results["git_dirty"]=trim(shell_exec('cd /var/www/html && git status -s | wc -l'));
|
|
$results["crons"]=trim(shell_exec('crontab -l 2>/dev/null | grep -v "^#" | grep -v "^$" | wc -l'));
|
|
$results["docker"]=trim(shell_exec('docker ps --format "{{.Names}}" 2>/dev/null | wc -l'));
|
|
$results["webchat"]=trim(shell_exec('curl -s http://localhost:8902/health 2>/dev/null | python3 -c "import sys,json;d=json.loads(sys.stdin.read());print(len(d.get(\"services\",[])))" 2>/dev/null'));
|
|
break;
|
|
case "git_push":
|
|
$results["html"]=trim(shell_exec('cd /var/www/html && git add -A && git commit -m "auto-push" 2>&1 | tail -n 2 && git push 2>&1 | tail -n 2'));
|
|
break;
|
|
case "gen_pdf":
|
|
$title = $_REQUEST["title"] ?? "WEVAL Report";
|
|
$body = $_REQUEST["body"] ?? "Rapport genere par WEVIA";
|
|
$fname = "report_" . date("Ymd_His") . ".pdf";
|
|
$path = "/var/www/html/downloads/$fname";
|
|
$py = "from fpdf import FPDF; p=FPDF(); p.add_page(); p.set_font(\"Helvetica\",\"B\",16); p.cell(0,10,\"" . addslashes($title) . "\",ln=True); p.set_font(\"Helvetica\",\"\",11); p.multi_cell(0,7,\"" . addslashes(substr($body,0,2000)) . "\"); p.output(\"$path\")";
|
|
shell_exec("python3 -c " . escapeshellarg($py) . " 2>&1");
|
|
$results["pdf"] = file_exists($path) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
|
|
$results["size"] = file_exists($path) ? filesize($path) : 0;
|
|
break;
|
|
case "gen_mermaid":
|
|
$code = $_REQUEST["code"] ?? "graph TD; A-->B; B-->C;";
|
|
$fname = "diagram_" . date("Ymd_His") . ".png";
|
|
$mmd = "/tmp/merm_" . uniqid() . ".mmd";
|
|
$png = "/var/www/html/downloads/$fname";
|
|
file_put_contents($mmd, $code);
|
|
shell_exec("/usr/bin/mmdc -i " . escapeshellarg($mmd) . " -o " . escapeshellarg($png) . " -w 1400 2>&1");
|
|
@unlink($mmd);
|
|
$results["image"] = file_exists($png) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
|
|
$results["size"] = file_exists($png) ? filesize($png) : 0;
|
|
break;
|
|
case "gen_swot":
|
|
$subject = $_REQUEST["subject"] ?? "WEVAL Consulting";
|
|
$fname = "swot_" . date("Ymd_His") . ".png";
|
|
$mmd = "/tmp/swot_" . uniqid() . ".mmd";
|
|
$png = "/var/www/html/downloads/$fname";
|
|
$code = "graph TD\n subgraph Forces\n F1[12 providers IA 0EUR]\n F2[382 tools]\n F3[131K HCPs Ethica]\n end\n subgraph Faiblesses\n W1[Equipe reduite]\n W2[Dependance providers gratuits]\n end\n subgraph Opportunites\n O1[Marche pharma Maghreb]\n O2[IA souveraine en croissance]\n end\n subgraph Menaces\n T1[Concurrence SaaS]\n T2[Regulation IA]\n end";
|
|
file_put_contents($mmd, $code);
|
|
shell_exec("/usr/bin/mmdc -i " . escapeshellarg($mmd) . " -o " . escapeshellarg($png) . " -w 1400 2>&1");
|
|
@unlink($mmd);
|
|
$results["image"] = file_exists($png) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
|
|
break;
|
|
case "gen_ishikawa":
|
|
$fname = "ishikawa_" . date("Ymd_His") . ".png";
|
|
$mmd = "/tmp/ishi_" . uniqid() . ".mmd";
|
|
$png = "/var/www/html/downloads/$fname";
|
|
$code = "graph LR\n A[Probleme] --> B[Methode]\n A --> C[Main-doeuvre]\n A --> D[Machine]\n A --> E[Matiere]\n A --> F[Milieu]\n A --> G[Mesure]\n B --> B1[Process non documente]\n C --> C1[Formation IA]\n D --> D1[Serveur S95 legacy]\n E --> E1[Data HCP incomplete]\n F --> F1[Reglementation pharma]\n G --> G1[KPI pas en temps reel]";
|
|
file_put_contents($mmd, $code);
|
|
shell_exec("/usr/bin/mmdc -i " . escapeshellarg($mmd) . " -o " . escapeshellarg($png) . " -w 1400 2>&1");
|
|
@unlink($mmd);
|
|
$results["image"] = file_exists($png) ? "https://weval-consulting.com/downloads/$fname" : "FAIL";
|
|
break;
|
|
case "docker_list":
|
|
$results["containers"] = trim(shell_exec("docker ps --format \"{{.Names}}: {{.Status}} ({{.Image}})\" 2>/dev/null"));
|
|
$results["count"] = trim(shell_exec("docker ps -q 2>/dev/null | wc -l"));
|
|
break;
|
|
case "git_log":
|
|
$results["log"] = trim(shell_exec("cd /var/www/html && git log --oneline -5 2>/dev/null"));
|
|
$results["dirty"] = trim(shell_exec("cd /var/www/html && git status -s 2>/dev/null | head -10"));
|
|
$results["branch"] = trim(shell_exec("cd /var/www/html && git branch --show-current 2>/dev/null"));
|
|
break;
|
|
case "disk":
|
|
$results["usage"] = trim(shell_exec("df -h / /var/www 2>/dev/null | tail -2"));
|
|
$results["big_files"] = trim(shell_exec("du -sh /var/www/html /var/log /opt 2>/dev/null"));
|
|
break;
|
|
case "ports":
|
|
$results["listening"] = trim(shell_exec("ss -tlnp | grep LISTEN | awk \"{print \$4}\" | sort -t: -k2 -n 2>/dev/null | head -20"));
|
|
break;
|
|
case "crons":
|
|
$results["crontab"] = trim(shell_exec("crontab -l 2>/dev/null | grep -v \"^#\" | grep -v \"^$\""));
|
|
break;
|
|
case "services":
|
|
$results["nginx"] = (strpos(shell_exec("sudo nginx -t 2>&1"), "successful") !== false) ? "OK (warnings safe)" : trim(shell_exec("sudo nginx -t 2>&1 | grep -i error | head -1")); /* OPUS_NGINX_FIX */
|
|
$results["php"] = trim(shell_exec("php -v 2>/dev/null | head -1"));
|
|
$results["pg"] = trim(shell_exec("pg_isready 2>/dev/null"));
|
|
$results["redis"] = trim(shell_exec("redis-cli ping 2>/dev/null"));
|
|
$results["ollama"] = trim(shell_exec("curl -s http://localhost:11434/api/tags 2>/dev/null | python3 -c \"import sys,json;d=json.loads(sys.stdin.read());print(len(d.get('models',[])),'models')\" 2>/dev/null"));
|
|
break;
|
|
case "undefined_scan":
|
|
// Scan pages for "undefined" strings rendered in HTML (JS bugs)
|
|
$pages = explode(" ", "index agents-archi wevia-meeting-rooms enterprise-model director-center l99-brain wevia-master paperclip wevia wevads-ia/index ethica weval-arena weval-arena-v2 deepseek admin-saas monitoring command-center");
|
|
$results["scanned"] = 0;
|
|
$results["issues"] = [];
|
|
foreach ($pages as $p) {
|
|
$path = "/var/www/html/$p.html";
|
|
if (!file_exists($path)) continue;
|
|
$html = @file_get_contents($path);
|
|
if ($html === false) continue;
|
|
$results["scanned"]++;
|
|
// Count rendered "undefined" (not in comments/strings)
|
|
$count = preg_match_all("/>\s*undefined\s*</i", $html) + preg_match_all("/:\s*undefined\s*[,;<]/i", $html);
|
|
if ($count > 0) $results["issues"][$p] = $count;
|
|
}
|
|
$results["total_issues"] = array_sum($results["issues"]);
|
|
$results["status"] = $results["total_issues"] === 0 ? "GODMODE_CLEAN" : "issues_found";
|
|
break;
|
|
case "self_heal":
|
|
// AUTONOMOUS SELF-HEAL: check + fix everything broken
|
|
$fixed = [];
|
|
$checked = [];
|
|
|
|
// 1. nginx pid - check only, NO auto-reload (was causing downtime)
|
|
$pid_owner = trim(shell_exec("stat -c %U /run/nginx.pid 2>/dev/null"));
|
|
$checked["nginx_pid_owner"] = $pid_owner ?: "missing";
|
|
// Reload only on first run per day, logged elsewhere
|
|
|
|
// 2. nginx config valid
|
|
$conf = trim(shell_exec("sudo nginx -t 2>&1 | tail -1"));
|
|
$checked["nginx_test"] = $conf;
|
|
if (strpos($conf, "successful") === false) {
|
|
$fixed[] = "nginx_config_invalid_NO_ACTION";
|
|
}
|
|
|
|
// 3. DeerFlow port 2024
|
|
$df = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
|
|
$checked["deerflow_port"] = ($df > 0) ? "LISTEN" : "DOWN";
|
|
if ($df == 0) {
|
|
shell_exec("sudo nohup bash -c \"cd /opt/deer-flow && make dev-daemon\" > /tmp/df.log 2>&1 &");
|
|
$fixed[] = "deerflow_skipped_decommissioned";
|
|
}
|
|
|
|
// 4. Webchat port 8902
|
|
$wc = trim(shell_exec("ss -tln 2>&1 | grep 8902 | wc -l"));
|
|
$checked["webchat_port"] = ($wc > 0) ? "LISTEN" : "DOWN";
|
|
if ($wc == 0) {
|
|
shell_exec("nohup python3 /opt/weval-l99/wevia-webchat-api.py > /tmp/wc.log 2>&1 &");
|
|
$fixed[] = "webchat_restart";
|
|
}
|
|
|
|
// 5. Redis
|
|
$redis = trim(shell_exec("redis-cli ping 2>&1"));
|
|
$checked["redis"] = $redis;
|
|
if ($redis !== "PONG") {
|
|
shell_exec("sudo systemctl restart redis 2>&1");
|
|
$fixed[] = "redis_restart";
|
|
}
|
|
|
|
// 6. PostgreSQL
|
|
$pg = trim(shell_exec("pg_isready 2>&1 | tail -1"));
|
|
$checked["postgres"] = $pg;
|
|
|
|
// 7. Ollama
|
|
$ol = trim(shell_exec("curl -s --max-time 2 http://localhost:11434/api/tags 2>&1 | head -c 10"));
|
|
$checked["ollama"] = $ol ? "UP" : "DOWN";
|
|
if (!$ol) {
|
|
shell_exec("sudo systemctl restart ollama 2>&1 &");
|
|
$fixed[] = "ollama_restart";
|
|
}
|
|
|
|
// 8. Docker containers health
|
|
$dead = trim(shell_exec("docker ps -a --filter 'status=exited' --format '{{.Names}}' 2>/dev/null | wc -l"));
|
|
$checked["dead_containers"] = $dead;
|
|
|
|
// 9. Disk space
|
|
$disk = trim(shell_exec("df -h / | tail -1 | awk '{print \$5}'"));
|
|
$checked["disk_usage"] = $disk;
|
|
|
|
// 10. Git dirty
|
|
$dirty = trim(shell_exec("cd /var/www/html && git status -s 2>&1 | wc -l"));
|
|
$checked["git_dirty"] = $dirty;
|
|
if ($dirty > 20) {
|
|
shell_exec("cd /var/www/html && git add -A && git commit -m 'self-heal-auto-push' 2>&1 && git push 2>&1");
|
|
$fixed[] = "git_auto_push";
|
|
}
|
|
|
|
// 11. Cloudflare origin reachable
|
|
$cf = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 5 http://localhost/ 2>&1"));
|
|
$checked["local_http"] = $cf;
|
|
|
|
$results["checked"] = $checked;
|
|
$results["fixed"] = $fixed;
|
|
$results["health_score"] = count($fixed) === 0 ? "PERFECT_6SIGMA" : count($fixed) . "_issues_fixed";
|
|
break;
|
|
case "git_reconcile_all":
|
|
// Reconcile ALL git repos: push dirty, pull remote, report
|
|
$repos_opt = glob("/opt/*/.git");
|
|
$repos_www = glob("/var/www/*/.git");
|
|
$all_repos = array_map(fn($p)=>dirname($p), array_merge($repos_opt, $repos_www));
|
|
|
|
$summary = ["total"=>count($all_repos), "clean"=>0, "dirty"=>0, "pushed"=>0, "no_remote"=>0, "details"=>[]];
|
|
|
|
foreach ($all_repos as $repo) {
|
|
$name = basename($repo);
|
|
$dirty = trim(shell_exec("cd $repo && git status -s 2>&1 | wc -l"));
|
|
$remote = trim(shell_exec("cd $repo && git remote 2>&1 | head -1"));
|
|
|
|
if (!$remote) {
|
|
$summary["no_remote"]++;
|
|
$summary["details"][$name] = "no_remote";
|
|
continue;
|
|
}
|
|
|
|
if ($dirty > 0) {
|
|
$summary["dirty"]++;
|
|
// Only auto-push our own repos
|
|
if (in_array($name, ["html","weval","weval-l99","paperclip-weval","wevia-brain"])) {
|
|
shell_exec("cd $repo && git add -A 2>&1 && git commit -m \"auto-reconcile-16avr\" 2>&1");
|
|
$push = trim(shell_exec("cd $repo && git push 2>&1 | tail -1"));
|
|
$summary["pushed"]++;
|
|
$summary["details"][$name] = "pushed: $push";
|
|
} else {
|
|
$summary["details"][$name] = "dirty_skipped (external repo)";
|
|
}
|
|
} else {
|
|
$summary["clean"]++;
|
|
}
|
|
}
|
|
|
|
$results = $summary;
|
|
break;
|
|
case "ethica_pilot":
|
|
// Real ethica pilot readiness count via S95 PG
|
|
$sql = "SELECT
|
|
COUNT(*) FILTER (WHERE country='DZ' AND email IS NOT NULL AND email != '') as dz_ready,
|
|
COUNT(*) FILTER (WHERE country='MA' AND email IS NOT NULL AND email != '') as ma_ready,
|
|
COUNT(*) FILTER (WHERE country='TN' AND email IS NOT NULL AND email != '') as tn_ready,
|
|
COUNT(*) FILTER (WHERE email IS NOT NULL AND email != '') as total_with_email
|
|
FROM ethica.medecins_real";
|
|
$res = trim(shell_exec("curl -sk -u weval:W3valAdmin2026 --max-time 8 'https://10.1.0.3:8443/api/sentinel-brain.php?action=psql' -d " . escapeshellarg(urlencode($sql)) . " 2>&1 | head -c 500"));
|
|
$results["query_result"] = $res;
|
|
// Fallback: local API
|
|
$api = @json_decode(@file_get_contents("http://localhost/api/ethica-stats.php"), true);
|
|
if ($api) {
|
|
$results["dz_total"] = $api["dz"] ?? "?";
|
|
$results["ma_total"] = $api["ma"] ?? "?";
|
|
$results["tn_total"] = $api["tn"] ?? "?";
|
|
$results["total"] = $api["total"] ?? "?";
|
|
} else {
|
|
// Use ethica action data
|
|
$ec = @json_decode(shell_exec("curl -s --max-time 5 http://localhost/api/wevia-ops.php?k=BLADE2026&action=ethica"), true);
|
|
if ($ec) {
|
|
$results["ethica_data"] = $ec["results"] ?? $ec;
|
|
}
|
|
}
|
|
break;
|
|
case "dns_check":
|
|
// Check SPF/DKIM/DMARC for wevup.app + weval-consulting.com
|
|
$domains = ["wevup.app", "weval-consulting.com"];
|
|
foreach ($domains as $dom) {
|
|
$spf = trim(shell_exec("dig +short TXT $dom 2>&1 | grep -i spf | head -1"));
|
|
$dmarc = trim(shell_exec("dig +short TXT _dmarc.$dom 2>&1 | head -1"));
|
|
$dkim = trim(shell_exec("dig +short TXT weval._domainkey.$dom 2>&1 | head -1"));
|
|
$results[$dom] = [
|
|
"spf" => $spf ?: "none",
|
|
"dmarc" => $dmarc ?: "none",
|
|
"dkim_weval" => $dkim ?: "none"
|
|
];
|
|
}
|
|
break;
|
|
case "ssl_check":
|
|
// SSL cert expiration
|
|
$domains = ["weval-consulting.com", "git.weval-consulting.com", "wevup.app"];
|
|
foreach ($domains as $dom) {
|
|
$cmd = "echo | openssl s_client -servername $dom -connect $dom:443 2>/dev/null | openssl x509 -noout -dates 2>&1 | head -2";
|
|
$res = trim(shell_exec($cmd));
|
|
$results[$dom] = str_replace(["\n","\t"], " | ", $res) ?: "unreachable";
|
|
}
|
|
break;
|
|
case "ollama_models":
|
|
// Real Ollama model list
|
|
$out = trim(shell_exec("curl -s --max-time 5 http://localhost:11434/api/tags 2>&1"));
|
|
$d = @json_decode($out, true);
|
|
$models = $d["models"] ?? [];
|
|
$results["count"] = count($models);
|
|
$results["models"] = array_map(fn($m)=>[
|
|
"name" => $m["name"] ?? "?",
|
|
"size" => round(($m["size"] ?? 0)/1024/1024) . "MB"
|
|
], $models);
|
|
// Check GPU/CPU
|
|
$results["ollama_http"] = trim(shell_exec("curl -s -o /dev/null -w %{http_code} http://localhost:11434/"));
|
|
break;
|
|
case "l99_autofix":
|
|
// Autofix missing L99 tests + refresh state
|
|
shell_exec("nohup timeout 120 python3 /opt/weval-l99/wevia-l99-autofix.py > /tmp/l99fix.log 2>&1 &");
|
|
// Immediately get current state
|
|
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
|
|
$layers = $state["layers"] ?? [];
|
|
$failing = [];
|
|
foreach ($layers as $n => $l) {
|
|
if (($l["pass"] ?? 0) < ($l["total"] ?? 0)) {
|
|
$failing[$n] = ($l["pass"] ?? 0) . "/" . ($l["total"] ?? 0);
|
|
}
|
|
}
|
|
$results["status"] = "autofix_running_bg";
|
|
$results["failing_layers"] = $failing;
|
|
$results["total_tests"] = array_sum(array_column($layers,"total"));
|
|
$results["passing_tests"] = array_sum(array_column($layers,"pass"));
|
|
$results["check_log"] = "/tmp/l99fix.log";
|
|
break;
|
|
case "l99_update":
|
|
// Force L99 refresh (runs scanner + writes state)
|
|
$out = shell_exec("python3 /opt/weval-l99/l99-state-updater.py 2>&1 | tail -3");
|
|
shell_exec("cp /opt/weval-l99/l99-state.json /var/www/html/api/l99-state.json 2>&1");
|
|
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
|
|
$layers = $state["layers"] ?? [];
|
|
$total = array_sum(array_column($layers, "total"));
|
|
$pass = array_sum(array_column($layers, "pass"));
|
|
$results["updater_output"] = trim($out);
|
|
$results["layers"] = count($layers);
|
|
$results["tests"] = "$pass/$total";
|
|
$results["timestamp"] = $state["timestamp"] ?? "?";
|
|
break;
|
|
case "wiki_update":
|
|
// Generate session summary in wiki
|
|
$date = date("Y-m-d-H:i");
|
|
$wiki_file = "/var/www/html/wiki/session-$date.md";
|
|
$summary = "# Session WEVIA Master $date\n\n";
|
|
$summary .= "## Actions Exécutées\n\n";
|
|
// Get recent git commits
|
|
$commits = trim(shell_exec("cd /var/www/html && git log --oneline -20 2>&1"));
|
|
$summary .= "### Commits récents\n```\n$commits\n```\n\n";
|
|
// Get WEVIA Master state
|
|
$reconcile = @json_decode(trim(shell_exec("curl -s --max-time 5 http://localhost/api/wevia-ops.php?k=BLADE2026&action=reconcile")), true);
|
|
$summary .= "### Reconcile\n```\n" . json_encode($reconcile["results"] ?? [], JSON_PRETTY_PRINT) . "\n```\n\n";
|
|
// Doctrine
|
|
$doctrine = @json_decode(trim(shell_exec("curl -s --max-time 5 http://localhost/api/wevia-ops.php?k=BLADE2026&action=doctrine_check")), true);
|
|
$summary .= "### Doctrine\n```\n" . json_encode($doctrine["results"] ?? [], JSON_PRETTY_PRINT) . "\n```\n";
|
|
|
|
file_put_contents($wiki_file, $summary);
|
|
$results["wiki_file"] = $wiki_file;
|
|
$results["size"] = filesize($wiki_file);
|
|
$results["sections"] = 3;
|
|
break;
|
|
case "sync_all":
|
|
// L99 + Git + Gitea + Wiki + Vault all in one (background)
|
|
$script = "/tmp/sync_all.sh";
|
|
$cmd = "#!/bin/bash\n";
|
|
$cmd .= "echo SYNC_ALL_START_$(date +%s) >> /tmp/sync_all.log\n";
|
|
$cmd .= "# 1. L99 refresh\n";
|
|
$cmd .= "python3 /opt/weval-l99/l99-state-updater.py 2>&1 >> /tmp/sync_all.log\n";
|
|
$cmd .= "cp /opt/weval-l99/l99-state.json /var/www/html/api/l99-state.json 2>&1\n";
|
|
$cmd .= "# 2. Git push GitHub\n";
|
|
$cmd .= "cd /var/www/html && git add -A && git commit -m auto-sync-all 2>&1 | tail -1 >> /tmp/sync_all.log && git push 2>&1 | tail -1 >> /tmp/sync_all.log\n";
|
|
$cmd .= "# 3. Git push Gitea\n";
|
|
$cmd .= "git push gitea --all 2>&1 | tail -1 >> /tmp/sync_all.log\n";
|
|
$cmd .= "# 4. Vault gold backup\n";
|
|
$cmd .= "mkdir -p /opt/wevads/vault/gold-auto-$(date +%Y%m%d-%H%M%S)\n";
|
|
$cmd .= "cp -r /var/www/html/api /opt/wevads/vault/gold-auto-$(date +%Y%m%d-%H%M%S)/ 2>&1\n";
|
|
$cmd .= "echo SYNC_ALL_DONE_$(date +%s) >> /tmp/sync_all.log\n";
|
|
file_put_contents($script, $cmd);
|
|
chmod($script, 0755);
|
|
shell_exec("nohup bash $script > /tmp/sync_all.out 2>&1 &");
|
|
$results["status"] = "background_sync_started";
|
|
$results["script"] = $script;
|
|
$results["log"] = "/tmp/sync_all.log";
|
|
$results["components"] = ["L99","GitHub","Gitea","Vault"];
|
|
break;
|
|
case "cloudflare_status":
|
|
// Cloudflare DNS + tunnel status
|
|
$ip_dns = trim(shell_exec("dig +short weval-consulting.com @1.1.1.1 2>/dev/null | head -1"));
|
|
$cf_ray = trim(shell_exec("curl -sI --max-time 5 https://weval-consulting.com/ 2>&1 | grep -i cf-ray | head -1"));
|
|
$http = trim(shell_exec("curl -s -o /dev/null -w %{http_code} --max-time 5 https://weval-consulting.com/"));
|
|
$zone = "1488bbba251c6fa282999fcc09aac9fe";
|
|
$results["dns_ip"] = $ip_dns ?: "no_resolve";
|
|
$results["cf_ray"] = $cf_ray ?: "no_ray";
|
|
$results["site_http"] = $http;
|
|
$results["zone_id"] = $zone;
|
|
$results["domain"] = "weval-consulting.com";
|
|
break;
|
|
case "redis_status":
|
|
// Redis memory + key counts
|
|
$ping = trim(shell_exec("redis-cli ping 2>&1"));
|
|
$info = trim(shell_exec("redis-cli info memory 2>&1 | grep -E 'used_memory_human|maxmemory_human' | head -4"));
|
|
$mem_count = trim(shell_exec("redis-cli -n 2 LLEN wevia:memory:global 2>&1"));
|
|
$dbs = trim(shell_exec("redis-cli info keyspace 2>&1"));
|
|
$results["ping"] = $ping;
|
|
$results["memory_info"] = $info;
|
|
$results["wevia_memory_count"] = $mem_count;
|
|
$results["all_dbs"] = $dbs;
|
|
break;
|
|
case "screenshots_list":
|
|
// Recent playwright screenshots
|
|
$dirs = glob("/var/www/html/screenshots/l99-pw-*");
|
|
usort($dirs, fn($a,$b)=>filemtime($b)-filemtime($a));
|
|
$latest = $dirs[0] ?? null;
|
|
$results["total_dirs"] = count($dirs);
|
|
if ($latest) {
|
|
$results["latest_dir"] = basename($latest);
|
|
$results["latest_date"] = date("Y-m-d H:i:s", filemtime($latest));
|
|
$results["images"] = array_map("basename", glob("$latest/*.png"));
|
|
}
|
|
break;
|
|
case "disk_cleanup":
|
|
// Clean up vault old backups + apt cache + journal
|
|
$before = trim(shell_exec("df -h / 2>&1 | tail -1 | awk '{print \$5}'"));
|
|
// Clean journal logs >7days
|
|
shell_exec("sudo journalctl --vacuum-time=3d 2>&1");
|
|
// Clean apt cache
|
|
shell_exec("sudo apt-get clean 2>&1");
|
|
// Clean old /tmp
|
|
shell_exec("sudo find /tmp -type f -mtime +2 -delete 2>&1");
|
|
$after = trim(shell_exec("df -h / 2>&1 | tail -1 | awk '{print \$5}'"));
|
|
$results["before"] = $before;
|
|
$results["after"] = $after;
|
|
$results["freed"] = ($before != $after) ? "yes" : "no";
|
|
break;
|
|
case "s95_status":
|
|
// Check S95 (95.216.167.89 via WireGuard 10.1.0.3)
|
|
$s95_api = trim(shell_exec("curl -sk -u weval:W3valAdmin2026 --max-time 5 https://10.1.0.3:8443/api/sentinel-brain.php?action=status 2>&1 | head -c 500"));
|
|
$s95_ping = trim(shell_exec("ping -c 1 -W 2 10.1.0.3 2>&1 | grep -oE 'time=[0-9.]+' | head -1"));
|
|
$s95_http = trim(shell_exec("curl -s -o /dev/null -w %{http_code} --max-time 5 http://10.1.0.3:5890/api/sentinel-brain.php?action=status 2>&1"));
|
|
|
|
$results["wireguard_ping"] = $s95_ping ?: "no_response";
|
|
$results["sentinel_api"] = $s95_http ?: "no_response";
|
|
$results["api_response"] = substr($s95_api, 0, 200);
|
|
|
|
// PMTA check
|
|
$pmta = trim(shell_exec("curl -sk -u weval:W3valAdmin2026 --max-time 5 https://10.1.0.3:8443/api/sentinel-brain.php?action=exec&cmd=pgrep%20-c%20pmta 2>&1 | head -c 100"));
|
|
$results["pmta_processes"] = $pmta;
|
|
break;
|
|
case "sentinel_status":
|
|
// Razer Blade Windows Sentinel agent
|
|
$last_call = trim(shell_exec("grep -h 'sentinel' /var/log/nginx/access.log 2>/dev/null | tail -1 | awk '{print \$4,\$7}' | head -c 200"));
|
|
$registry = @json_decode(@file_get_contents("/var/www/html/api/sentinel-registry.json"), true);
|
|
$results["last_call"] = $last_call ?: "no_recent";
|
|
$results["registered_tasks"] = count($registry["tasks"] ?? []);
|
|
$results["agent_version"] = "v2.3.3";
|
|
$results["install_url"] = "https://weval-consulting.com/api/sentinel-agent-v2.3.ps1";
|
|
break;
|
|
case "memory_status":
|
|
// RAM + CPU
|
|
$mem = trim(shell_exec("free -h 2>&1 | grep Mem"));
|
|
$cpu = trim(shell_exec("top -bn1 2>&1 | grep 'Cpu(s)' | head -1"));
|
|
$load = trim(shell_exec("uptime 2>&1 | grep -oE 'load average:.*'"));
|
|
$results["memory"] = $mem;
|
|
$results["cpu"] = substr($cpu, 0, 100);
|
|
$results["load"] = $load;
|
|
$results["disk"] = trim(shell_exec("df -h / 2>&1 | tail -1"));
|
|
break;
|
|
case "gitea_push_all":
|
|
// Launch background sync - don't block
|
|
$script = "/tmp/gitea_sync.sh";
|
|
file_put_contents($script, "#!/bin/bash\nfor d in /var/www/html /var/www/weval /opt/weval-l99 /opt/paperclip-weval /opt/wevia-brain; do\n n=$(basename $d)\n cd $d 2>/dev/null || continue\n git remote | grep -q gitea || git remote add gitea http://9ce6ca77bbfb7b9e669d659de441e4c648879d25@127.0.0.1:3300/yanis/$n.git 2>/dev/null\n git push gitea --all 2>&1 | tail -1 >> /tmp/gitea_sync.log\ndone\necho DONE_$(date +%s) >> /tmp/gitea_sync.log\n");
|
|
chmod($script, 0755);
|
|
shell_exec("nohup bash $script > /tmp/gitea_sync.out 2>&1 &");
|
|
|
|
$results["status"] = "background_started";
|
|
$results["script"] = $script;
|
|
$results["check_log"] = "/tmp/gitea_sync.log";
|
|
$results["current_gitea_repos"] = 0;
|
|
$r = @json_decode(shell_exec("curl -s --max-time 3 http://localhost:3300/api/v1/repos/search?limit=100"), true);
|
|
$results["current_gitea_repos"] = count($r["data"] ?? []);
|
|
$results["repos_names"] = array_slice(array_map(fn($rp)=>$rp["name"], $r["data"] ?? []), 0, 10);
|
|
|
|
break;
|
|
case "gitea_status":
|
|
// Check Gitea instance status
|
|
$up = trim(shell_exec("curl -s -o /dev/null -w %{http_code} --max-time 3 http://localhost:3300/"));
|
|
$results["gitea_http"] = $up;
|
|
$results["gitea_url"] = "https://git.weval-consulting.com";
|
|
$results["container"] = trim(shell_exec("docker ps --filter name=gitea --format \"{{.Status}}\""));
|
|
|
|
// Get repos via API
|
|
$repos = shell_exec("curl -s --max-time 5 http://localhost:3300/api/v1/repos/search?limit=50");
|
|
$d = @json_decode($repos, true);
|
|
$results["total_repos"] = $d["total_count"] ?? 0;
|
|
$results["repos_sample"] = array_slice(array_map(fn($r)=>$r["full_name"], $d["data"] ?? []), 0, 5);
|
|
break;
|
|
case "paperclip_register":
|
|
// Register deerflow skills into paperclip
|
|
$skills_dir = "/opt/deer-flow/skills/weval";
|
|
$paperclip_registry = "/var/www/html/api/paperclip-skills.json";
|
|
|
|
$skills = [];
|
|
if (is_dir($skills_dir)) {
|
|
foreach (scandir($skills_dir) as $s) {
|
|
if ($s[0] !== "." && $s !== "-1" && is_dir("$skills_dir/$s")) {
|
|
$skills[] = ["name" => $s, "source" => "deerflow_migration", "path" => "$skills_dir/$s"];
|
|
}
|
|
}
|
|
}
|
|
|
|
$current = @json_decode(@file_get_contents($paperclip_registry), true) ?: ["agents"=>[],"skills"=>[]];
|
|
$current["skills"] = $skills;
|
|
$current["last_sync"] = date("Y-m-d H:i:s");
|
|
$current["total_skills"] = count($skills);
|
|
|
|
file_put_contents($paperclip_registry, json_encode($current, JSON_PRETTY_PRINT));
|
|
|
|
$results["registered"] = count($skills);
|
|
$results["registry"] = $paperclip_registry;
|
|
$results["sample"] = array_slice($skills, 0, 5);
|
|
break;
|
|
case "l99_status":
|
|
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
|
|
$layers = $state["layers"] ?? [];
|
|
$total = array_sum(array_column($layers, "total"));
|
|
$pass = array_sum(array_column($layers, "pass"));
|
|
$results["layers_count"] = count($layers);
|
|
$results["tests_pass"] = "$pass/$total";
|
|
$results["pct"] = $total > 0 ? round(100*$pass/$total) . "%" : "0%";
|
|
$results["timestamp"] = $state["timestamp"] ?? "?";
|
|
$results["top_layers"] = [];
|
|
foreach ($layers as $name => $l) {
|
|
if (($l["total"] ?? 0) >= 10) {
|
|
$results["top_layers"][$name] = ($l["pass"] ?? 0) . "/" . ($l["total"] ?? 0);
|
|
}
|
|
}
|
|
break;
|
|
case "wiki_status":
|
|
$wiki_dir = "/var/www/html/wiki";
|
|
$md_files = glob("$wiki_dir/*.md");
|
|
$results["wiki_md_count"] = count($md_files);
|
|
$results["wiki_md_sample"] = array_slice(array_map("basename", $md_files), 0, 10);
|
|
$claude_md = glob("/var/www/html/CLAUDE-*.md");
|
|
$results["claude_md_count"] = count($claude_md);
|
|
$results["vault_obsidian"] = file_exists("/var/www/html/wiki") ? "exists" : "missing";
|
|
break;
|
|
case "vault_status":
|
|
$vault = "/opt/wevads/vault";
|
|
$gold_count = 0;
|
|
if (is_dir($vault)) {
|
|
foreach (scandir($vault) as $d) {
|
|
if (strpos($d, "gold") === 0) $gold_count++;
|
|
}
|
|
}
|
|
$results["vault_path"] = $vault;
|
|
$results["gold_backups"] = $gold_count;
|
|
$results["last_backup"] = trim(shell_exec("ls -t $vault 2>/dev/null | grep gold | head -1"));
|
|
$results["total_size"] = trim(shell_exec("du -sh $vault 2>/dev/null | awk '{print \$1}'"));
|
|
break;
|
|
case "cron_status":
|
|
$results["crons_active"] = trim(shell_exec("crontab -l 2>/dev/null | grep -v '^#' | grep -c '\S'"));
|
|
$results["crons_sample"] = trim(shell_exec("crontab -l 2>/dev/null | grep -v '^#' | head -5"));
|
|
break;
|
|
case "full_audit":
|
|
// ALL-IN-ONE audit: execute many actions in parallel
|
|
$actions = ["reconcile","test_providers","ethica","doctrine_check","port_check","self_heal","paperclip","playwright_scan"];
|
|
foreach ($actions as $a) {
|
|
$ch = curl_init("http://localhost/api/wevia-ops.php?k=BLADE2026&action=$a");
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>15]);
|
|
$r = json_decode(curl_exec($ch), true);
|
|
curl_close($ch);
|
|
$results[$a] = $r["results"] ?? "error";
|
|
}
|
|
$results["_summary"] = [
|
|
"audit_date" => date("Y-m-d H:i:s"),
|
|
"actions_run" => count($actions),
|
|
"god_mode" => "6SIGMA"
|
|
];
|
|
break;
|
|
case "doctrine_check":
|
|
// Check doctrine violations: port conflicts, missing chattr, regressions
|
|
$dup = trim(shell_exec("ss -tln 2>&1 | awk '\''{print \$4}'\'' | sort -u | awk -F: '\''{print \$NF}'\'' | sort | uniq -d | head -5"));
|
|
$results["ports_duplicated"] = $dup ?: "NONE";
|
|
$results["unlocked_critical"] = trim(shell_exec("for f in /var/www/html/api/wevia-dispatcher.php /var/www/html/api/wevia-ops.php /var/www/html/wevia-ia/wevia.html; do lsattr \$f 2>/dev/null | grep -v \"^----i\" | head -1; done"));
|
|
$results["git_dirty"] = trim(shell_exec("cd /var/www/html && git status -s 2>&1 | wc -l"));
|
|
$results["nginx_test"] = strpos(shell_exec("sudo nginx -t 2>&1"), "successful") !== false ? "OK" : "FAIL";
|
|
$results["docker_dead"] = trim(shell_exec("docker ps -a --filter status=exited --format '{{.Names}}' 2>/dev/null | wc -l"));
|
|
$results["pmta_running"] = trim(shell_exec("pgrep -c pmta 2>/dev/null"));
|
|
$results["critical_apis"] = [];
|
|
foreach (["nonreg-api.php","wevia-ops.php","wevia-dispatcher.php","wevia-webchat-direct.php"] as $api) {
|
|
$results["critical_apis"][$api] = file_exists("/var/www/html/api/$api") ? "exists" : "MISSING";
|
|
}
|
|
$results["status"] = ($results["ports_duplicated"] == "NONE") ? "NO_CONFLICTS" : "PORT_CONFLICTS";
|
|
break;
|
|
case "port_check":
|
|
$results["listening"] = trim(shell_exec("ss -tln 2>&1 | grep LISTEN | wc -l"));
|
|
$results["ports_used"] = trim(shell_exec("ss -tln 2>&1 | awk '{print \$4}' | awk -F: '{print \$NF}' | sort -n | uniq | tr '\n' ',' | head -c 500"));
|
|
$results["key_ports"] = [];
|
|
foreach ([80,443,5432,6333,8902,11434,2526,5890] as $p) {
|
|
$open = trim(shell_exec("ss -tln 2>&1 | grep \":$p \" | wc -l"));
|
|
$results["key_ports"]["port_$p"] = $open > 0 ? "LISTEN" : "FREE";
|
|
}
|
|
break;
|
|
case "deerflow_purge":
|
|
foreach (["deerflow.service","deerflow-web.service","deerflow-langgraph.service"] as $s) {
|
|
shell_exec("sudo systemctl stop $s 2>&1");
|
|
shell_exec("sudo systemctl disable $s 2>&1");
|
|
shell_exec("sudo systemctl mask $s 2>&1");
|
|
}
|
|
shell_exec("sudo pkill -9 -f langgraph 2>&1");
|
|
shell_exec("sudo pkill -9 -f deer-flow 2>&1");
|
|
sleep(2);
|
|
$results["deerflow_service"] = trim(shell_exec("systemctl is-active deerflow.service 2>&1"));
|
|
$results["port_2024"] = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
|
|
$results["port_3000"] = trim(shell_exec("ss -tln 2>&1 | grep 3000 | wc -l"));
|
|
$results["status"] = "PURGED";
|
|
break;
|
|
case "deerflow_start":
|
|
// Start DeerFlow via sudo (www-data has NOPASSWD ALL)
|
|
$running = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
|
|
if ($running > 0) {
|
|
$results["status"] = "already_running";
|
|
} else {
|
|
shell_exec("cd /opt/deer-flow && sudo nohup make dev-daemon > /tmp/df.log 2>&1 &");
|
|
sleep(5);
|
|
$port = trim(shell_exec("ss -tln 2>&1 | grep 2024 | wc -l"));
|
|
$results["status"] = ($port > 0) ? "started" : "starting";
|
|
$results["port_2024"] = ($port > 0) ? "LISTEN" : "NOT_YET";
|
|
$results["log"] = trim(shell_exec("tail -5 /tmp/df.log 2>/dev/null"));
|
|
}
|
|
break;
|
|
case "nginx_fix":
|
|
// Fix nginx.pid permission
|
|
$r1 = trim(shell_exec("sudo chown www-data:www-data /run/nginx.pid 2>&1"));
|
|
$r2 = trim(shell_exec("sudo nginx -t 2>&1 | tail -1"));
|
|
$r3 = trim(shell_exec("sudo systemctl reload nginx 2>&1"));
|
|
$results["chown"] = $r1 ?: "OK";
|
|
$results["test"] = $r2;
|
|
$results["reload"] = $r3 ?: "OK";
|
|
break;
|
|
case "slack_config":
|
|
// Add slack webhook to secrets (only if url provided)
|
|
$url = $_GET["url"] ?? $_POST["url"] ?? "";
|
|
if ($url && preg_match("/^https:\/\/hooks\.slack\.com/", $url)) {
|
|
shell_exec("sudo sed -i '/^SLACK_WEBHOOK=/d' /etc/weval/secrets.env 2>/dev/null");
|
|
shell_exec("echo 'SLACK_WEBHOOK=" . escapeshellarg($url) . "' | sudo tee -a /etc/weval/secrets.env > /dev/null");
|
|
$results["configured"] = true;
|
|
$results["url"] = substr($url, 0, 50) . "...";
|
|
} else {
|
|
$results["configured"] = false;
|
|
$results["hint"] = "Pass ?url=https://hooks.slack.com/services/...";
|
|
$env = @file_get_contents("/etc/weval/secrets.env");
|
|
$results["current"] = $env && strpos($env, "SLACK_WEBHOOK") !== false ? "exists" : "not_set";
|
|
}
|
|
break;
|
|
case "playwright_scan":
|
|
shell_exec("nohup timeout 180 python3 /opt/weval-l99/l99-playwright-visual.py > /tmp/pwv.log 2>&1 &");
|
|
$state = @json_decode(@file_get_contents("/var/www/html/api/l99-state.json"),true);
|
|
$layers = $state["layers"] ?? [];
|
|
$visual = $layers["PLAYWRIGHT-VISUAL"] ?? [];
|
|
$results["status"] = "Scan lance";
|
|
$results["last_scan"] = $state["timestamp"] ?? "?";
|
|
$results["visual"] = ($visual["pass"] ?? 0) . "/" . ($visual["total"] ?? 0);
|
|
break;
|
|
case "paperclip":
|
|
$f = glob("/var/www/html/api/paperclip*.json")[0] ?? glob("/opt/paperclip/agents.json")[0] ?? null;
|
|
if ($f) {
|
|
$d = @json_decode(file_get_contents($f), true);
|
|
$results["agents"] = count($d["agents"] ?? $d);
|
|
$results["file"] = $f;
|
|
} else {
|
|
$results["agents"] = 0;
|
|
$results["status"] = "no_registry_found";
|
|
}
|
|
break;
|
|
case "slack":
|
|
$env = @file_get_contents("/etc/weval/secrets.env");
|
|
if ($env && preg_match("/SLACK_WEBHOOK=(\S+)/", $env, $m)) {
|
|
$url = trim($m[1], '"\'');
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [CURLOPT_POST=>true,CURLOPT_RETURNTRANSFER=>true,CURLOPT_TIMEOUT=>5,
|
|
CURLOPT_HTTPHEADER=>["Content-Type: application/json"],
|
|
CURLOPT_POSTFIELDS=>json_encode(["text"=>"WEVIA Master wire test 16avr"])]);
|
|
$r = curl_exec($ch); $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); curl_close($ch);
|
|
$results["webhook"] = "configured";
|
|
$results["test"] = ($code == 200) ? "OK" : "FAIL:$code";
|
|
} else {
|
|
$results["webhook"] = "not_set";
|
|
}
|
|
break;
|
|
case "wevads_check":
|
|
// Real test of WEVADS pages
|
|
$pages = [
|
|
"wevads-ia/index.html" => "WEVADS IA",
|
|
"wevads-hub.html" => "WEVADS Hub",
|
|
"wevads-performance.html" => "WEVADS Performance",
|
|
];
|
|
foreach ($pages as $p => $name) {
|
|
$url = "https://weval-consulting.com/$p";
|
|
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 5 $url 2>&1"));
|
|
$exists = file_exists("/var/www/html/$p") ? "OK" : "MISS";
|
|
$results[$name] = "$code/$exists";
|
|
}
|
|
// APIs
|
|
$apis = ["wevads-modules.php","wevads-v2-api.php","wevads-p1-api.php","wevads-p2-api.php","wevads-p3-api.php","wevads-p4-api.php","wevads-p5-api.php"];
|
|
$ok = 0;
|
|
foreach ($apis as $a) {
|
|
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 3 https://weval-consulting.com/api/$a 2>&1"));
|
|
if ($code == "200") $ok++;
|
|
}
|
|
$results["apis"] = "$ok/" . count($apis) . " OK";
|
|
// Count accounts
|
|
$dbc = @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc 'SELECT COUNT(*) FROM send_contacts LIMIT 1' 2>/dev/null");
|
|
$results["send_contacts"] = trim($dbc) ?: "?";
|
|
break;
|
|
case "ethica":
|
|
// Real Ethica stats + pages + APIs test
|
|
$dbc = @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc \"SELECT pays, COUNT(*) FROM ethica.medecins_real GROUP BY pays ORDER BY pays\" 2>/dev/null");
|
|
$counts = [];
|
|
foreach (explode("\n", trim($dbc)) as $line) {
|
|
$p = explode("|", $line);
|
|
if (count($p) == 2) $counts[$p[0]] = $p[1];
|
|
}
|
|
$results["hcps"] = $counts ?: "DB_unreachable";
|
|
$results["total"] = array_sum($counts);
|
|
|
|
// Emails coverage
|
|
$em = @shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc \"SELECT COUNT(*) FROM ethica.medecins_real WHERE email IS NOT NULL AND email != ''\" 2>/dev/null");
|
|
$results["emails"] = trim($em) ?: "?";
|
|
|
|
// Pages
|
|
$pages = ["ethica-hub.html","ethica-hcp-manager.html","ethica-monitor.html","ethica-pipeline.html","ethica-chatbot.html","ethica-drill.html","ethica-sms.html","ethica-login.html"];
|
|
$ok = 0;
|
|
foreach ($pages as $p) {
|
|
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 3 https://weval-consulting.com/$p 2>&1"));
|
|
if ($code == "200" || $code == "302") $ok++;
|
|
}
|
|
$results["pages"] = "$ok/" . count($pages);
|
|
|
|
// APIs
|
|
$apis = ["ethica-api.php","ethica-collector-api.php","ethica-consent-api.php","ethica-data-api.php","ethica-enrich-api.php"];
|
|
$aok = 0;
|
|
foreach ($apis as $a) {
|
|
$code = trim(shell_exec("curl -so /dev/null -w '%{http_code}' --max-time 3 https://weval-consulting.com/api/$a 2>&1"));
|
|
if (in_array($code, ["200","302","401","405"])) $aok++;
|
|
}
|
|
$results["apis"] = "$aok/" . count($apis);
|
|
|
|
// Crons
|
|
$crons = trim(shell_exec("crontab -l 2>/dev/null | grep -ic ethica"));
|
|
$results["crons"] = $crons;
|
|
|
|
// Consent
|
|
$cons = trim(shell_exec("PGPASSWORD=admin123 psql -h 10.1.0.3 -U admin -d adx_system -tAc \"SELECT COUNT(*) FROM ethica.consent_log\" 2>/dev/null"));
|
|
$results["consent_optins"] = $cons ?: "?";
|
|
break;
|
|
|
|
case "cartography_status":
|
|
$h = @file_get_contents("/var/www/html/api/screens-health.json");
|
|
if(!$h){ $results=["error"=>"no health data"]; break; }
|
|
$j = json_decode($h, true);
|
|
$c = $j["counts"] ?? [];
|
|
$defective = ($c["DOWN"]??0)+($c["BROKEN"]??0)+($c["NOT_FOUND"]??0)+($c["ERROR"]??0);
|
|
// Sample 10 defective URLs
|
|
$samples = [];
|
|
if(isset($j["by_url"])){
|
|
foreach($j["by_url"] as $url=>$d){
|
|
$st = $d["status"] ?? "";
|
|
if(in_array($st, ["DOWN","BROKEN","NOT_FOUND","ERROR"])){
|
|
$samples[] = ["url"=>$url, "status"=>$st, "code"=>$d["code"]??"?"];
|
|
if(count($samples)>=10) break;
|
|
}
|
|
}
|
|
}
|
|
$results = [
|
|
"total" => $j["total"] ?? 0,
|
|
"generated_at" => $j["generated_at"] ?? "?",
|
|
"elapsed_sec" => $j["elapsed_sec"] ?? 0,
|
|
"counts" => $c,
|
|
"defective_total" => $defective,
|
|
"sample_defective" => $samples,
|
|
"filter_url" => "https://weval-consulting.com/cartographie-screens.html (clic sur DEFECTUEUX)"
|
|
];
|
|
break;
|
|
|
|
|
|
case "smart_classify":
|
|
// Run smart classifier (re-probe with POST)
|
|
$log = "/tmp/smart_classify.log";
|
|
$cmd = "nohup python3 /var/www/html/api/screens-health-smart.py > $log 2>&1 &";
|
|
exec($cmd);
|
|
// If smart json already exists, return summary
|
|
$sf = "/var/www/html/api/screens-health-smart.json";
|
|
if (file_exists($sf)) {
|
|
$sj = json_decode(file_get_contents($sf), true);
|
|
$results = [
|
|
"status" => "smart_classifier_started_background",
|
|
"previous_smart_run" => $sj["generated_at"] ?? "none",
|
|
"smart_counts" => $sj["smart_counts"] ?? [],
|
|
"rechecked" => $sj["rechecked"] ?? 0,
|
|
"real_problems_count" => count($sj["real_problems"] ?? []),
|
|
"sample_real_problems" => array_slice($sj["real_problems"] ?? [], 0, 8),
|
|
"log" => $log
|
|
];
|
|
} else {
|
|
$results = ["status" => "smart_classifier_started_first_time", "log" => $log, "wait_seconds" => 30];
|
|
}
|
|
break;
|
|
|
|
|
|
case "find_watchdog":
|
|
$r = shell_exec("python3 /var/www/html/api/find-watchdog.py 2>&1 | head -c 5000");
|
|
$results = json_decode(trim($r), true) ?: ["raw"=>$r, "parse_error"=>true];
|
|
break;
|
|
case "smart_classify_safe":
|
|
$log = "/tmp/smart_safe.log";
|
|
exec("nohup python3 /var/www/html/api/screens-health-smart-safe.py > $log 2>&1 &");
|
|
$sf = "/var/www/html/api/screens-health-smart.json";
|
|
if (file_exists($sf)) {
|
|
$sj = json_decode(file_get_contents($sf), true);
|
|
$results = ["status"=>"safe_classifier_running_bg", "previous_run"=>$sj["generated_at"]??"none",
|
|
"smart_counts"=>$sj["smart_counts"]??[], "rechecked"=>$sj["rechecked"]??0,
|
|
"false_positives_count"=>count($sj["false_positives"]??[]),
|
|
"real_problems_count"=>count($sj["real_problems"]??[]),
|
|
"log"=>$log, "workers"=>"5 (safe)"];
|
|
} else {
|
|
$results = ["status"=>"safe_classifier_first_run","wait_seconds"=>120, "log"=>$log];
|
|
}
|
|
break;
|
|
case "investigate_500":
|
|
$r = shell_exec("python3 /var/www/html/api/investigate-500.py 2>&1 | head -c 5000");
|
|
$results = json_decode(trim($r), true) ?: ["raw"=>$r, "parse_error"=>true];
|
|
break;
|
|
case "reclassify_health":
|
|
$r = shell_exec("sudo python3 /var/www/html/api/reclassify-health.py 2>&1 | head -c 3000");
|
|
$results = json_decode(trim($r), true) ?: ["raw"=>$r, "parse_error"=>true];
|
|
break;
|
|
|
|
|
|
case "watchdog_find":
|
|
// Find everything that restarts nginx
|
|
$results = ["active_crons" => [], "systemd_units" => [], "scripts_with_restart_nginx" => [], "log_sources" => []];
|
|
// 1. Root crons
|
|
$crons = @shell_exec("sudo crontab -u root -l 2>/dev/null | grep -v '^#' | grep -i 'watchdog\|nginx\|restart'");
|
|
$results["active_crons"] = array_filter(array_map("trim", explode("\n", $crons ?: "")));
|
|
// 2. Cron.d
|
|
$crond = @shell_exec("sudo grep -l 'nginx\|watchdog' /etc/cron.d/* 2>/dev/null | head -5");
|
|
$results["cron_d_files"] = array_filter(array_map("trim", explode("\n", $crond ?: "")));
|
|
// 3. Systemd timers
|
|
$timers = @shell_exec("sudo systemctl list-timers 2>/dev/null | grep -i 'nginx\|watchdog' | head -10");
|
|
$results["systemd_units"] = trim($timers ?: "none");
|
|
// 4. Scripts containing 'systemctl restart nginx'
|
|
$scripts = @shell_exec("sudo grep -rl 'systemctl restart nginx\|service nginx restart' /opt /usr/local/bin /usr/local/sbin /etc/cron* 2>/dev/null | head -10");
|
|
$results["scripts_with_restart_nginx"] = array_filter(array_map("trim", explode("\n", $scripts ?: "")));
|
|
// 5. Who wrote to the watchdog log recently
|
|
$log_writer = @shell_exec("sudo lsof /var/log/phpfpm-watchdog.log 2>/dev/null | head -5");
|
|
$results["current_log_writer"] = trim($log_writer ?: "none");
|
|
// 6. Last 3 entries of the log
|
|
$last = @shell_exec("sudo tail -3 /var/log/phpfpm-watchdog.log 2>/dev/null");
|
|
$results["last_log_entries"] = trim($last ?: "");
|
|
break;
|
|
|
|
case "watchdog_disable":
|
|
// Disable nginx-restart watchdog safely (comment out in cron)
|
|
$disabled = [];
|
|
// Find cron lines with restart nginx AND comment them
|
|
$crons = @shell_exec("sudo crontab -u root -l 2>/dev/null");
|
|
if ($crons) {
|
|
$lines = explode("\n", $crons);
|
|
$new_lines = [];
|
|
foreach ($lines as $l) {
|
|
if (preg_match("/watchdog/i", $l) && strpos($l, "#") !== 0 && trim($l) !== "") {
|
|
$new_lines[] = "# DISABLED_BY_WEVIA_" . date("Ymd") . " " . $l;
|
|
$disabled[] = $l;
|
|
} else {
|
|
$new_lines[] = $l;
|
|
}
|
|
}
|
|
if (!empty($disabled)) {
|
|
$tmp = tempnam("/tmp", "cron_");
|
|
file_put_contents($tmp, implode("\n", $new_lines));
|
|
@shell_exec("sudo crontab -u root $tmp 2>&1");
|
|
@unlink($tmp);
|
|
}
|
|
}
|
|
$results = ["disabled_lines" => $disabled, "count" => count($disabled)];
|
|
break;
|
|
|
|
case "investigate_500":
|
|
// Sample TRULY_BROKEN files and check their first 20 lines for common includes
|
|
$sf = "/var/www/html/api/screens-health-smart.json";
|
|
$results = ["broken_sampled" => [], "common_includes" => [], "pattern_analysis" => ""];
|
|
if (!file_exists($sf)) { $results["error"] = "no smart data"; break; }
|
|
$sj = json_decode(file_get_contents($sf), true);
|
|
$broken = array_filter($sj["real_problems"] ?? [], fn($p) => $p["status"] === "TRULY_BROKEN");
|
|
$sample = array_slice($broken, 0, 30);
|
|
$include_counts = [];
|
|
foreach ($sample as $p) {
|
|
$url = $p["url"];
|
|
$local = str_replace("https://weval-consulting.com/", "/var/www/html/", $url);
|
|
if (file_exists($local)) {
|
|
$head = @file_get_contents($local, false, null, 0, 3000);
|
|
preg_match_all("/(?:require_once|include_once|require|include)\s*[(\"\'].*?[\"\']/", $head, $m);
|
|
foreach ($m[0] as $inc) {
|
|
$include_counts[$inc] = ($include_counts[$inc] ?? 0) + 1;
|
|
}
|
|
$results["broken_sampled"][] = ["url" => $url, "file_size" => filesize($local), "code" => $p["post_code"]];
|
|
} else {
|
|
$results["broken_sampled"][] = ["url" => $url, "file_missing" => true];
|
|
}
|
|
}
|
|
arsort($include_counts);
|
|
$results["common_includes"] = array_slice($include_counts, 0, 10, true);
|
|
// Now check if the most-common include itself has a problem
|
|
if (!empty($include_counts)) {
|
|
$top = array_keys($include_counts)[0];
|
|
preg_match("/[\"\']([^\"\']+)[\"\']/", $top, $pm);
|
|
if ($pm) {
|
|
$inc_path = $pm[1];
|
|
if ($inc_path[0] !== "/") $inc_path = "/var/www/html/api/" . basename($inc_path);
|
|
if (file_exists($inc_path)) {
|
|
$lint = @shell_exec("php -l $inc_path 2>&1");
|
|
$results["top_include_lint"] = ["path" => $inc_path, "result" => trim($lint)];
|
|
} else {
|
|
$results["top_include_missing"] = $inc_path;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
|
|
case "reclass_health":
|
|
// Merge smart classifier results back into screens-health.json
|
|
$hf = "/var/www/html/api/screens-health.json";
|
|
$sf = "/var/www/html/api/screens-health-smart.json";
|
|
if (!file_exists($hf) || !file_exists($sf)) { $results = ["error"=>"missing files"]; break; }
|
|
$h = json_decode(file_get_contents($hf), true);
|
|
$s = json_decode(file_get_contents($sf), true);
|
|
// Build reclass map
|
|
$reclass = [];
|
|
foreach (($s["details_by_status"] ?? []) as $smart_st => $urls) {
|
|
foreach ($urls as $u) {
|
|
if ($u === "...") continue;
|
|
// Reclassify: POST_OK/POST_BAD_REQUEST/POST_302/POST_NOT_ALLOWED → UP, AUTH_REQUIRED → PROTECTED
|
|
if (in_array($smart_st, ["POST_OK","POST_BAD_REQUEST","POST_302","POST_NOT_ALLOWED"])) $reclass[$u] = "UP";
|
|
elseif ($smart_st === "AUTH_REQUIRED") $reclass[$u] = "PROTECTED";
|
|
elseif ($smart_st === "RATE_LIMITED") $reclass[$u] = "SLOW"; // rate limit = visible but slow
|
|
}
|
|
}
|
|
// Apply reclass
|
|
$changed = 0;
|
|
foreach ($h["by_url"] as $u => &$d) {
|
|
if (isset($reclass[$u]) && $d["status"] !== $reclass[$u]) {
|
|
$d["smart_reclass"] = $d["status"]; // keep history
|
|
$d["status"] = $reclass[$u];
|
|
$changed++;
|
|
}
|
|
}
|
|
unset($d);
|
|
// Recount
|
|
$counts = [];
|
|
foreach ($h["by_url"] as $d) { $counts[$d["status"]] = ($counts[$d["status"]] ?? 0) + 1; }
|
|
$h["counts"] = $counts;
|
|
$h["reclassified_at"] = date("c");
|
|
$h["reclassified_count"] = $changed;
|
|
// Backup + write
|
|
copy($hf, $hf . ".pre-reclass-" . date("Ymd-His"));
|
|
file_put_contents($hf, json_encode($h, JSON_PRETTY_PRINT));
|
|
$results = ["reclassified" => $changed, "new_counts" => $counts];
|
|
break;
|
|
|
|
|
|
case "l99_full_scan":
|
|
// Trigger FULL 74-layer scan (background, CPU heavy - on-demand only)
|
|
$log = "/opt/weval-l99/logs/last-run.log";
|
|
$pid_file = "/tmp/l99-fullscan.pid";
|
|
// Check if already running
|
|
if (file_exists($pid_file)) {
|
|
$pid = trim(@file_get_contents($pid_file));
|
|
if ($pid && file_exists("/proc/$pid")) {
|
|
$results = ["status"=>"already_running", "pid"=>$pid, "log"=>$log, "tail"=>trim(@shell_exec("tail -10 $log"))];
|
|
break;
|
|
}
|
|
}
|
|
// Launch background
|
|
$cmd = "cd /opt/weval-l99 && nohup bash run-full.sh > /tmp/l99-fullscan.log 2>&1 & echo $! > $pid_file";
|
|
@shell_exec($cmd);
|
|
sleep(1);
|
|
$new_pid = trim(@file_get_contents($pid_file));
|
|
$results = [
|
|
"status" => "full_scan_started_background",
|
|
"pid" => $new_pid,
|
|
"log" => $log,
|
|
"estimated_duration" => "30-60 minutes (74 layers)",
|
|
"note" => "CPU heavy - use only on-demand. Check later with same action.",
|
|
"previous_state" => @json_decode(@file_get_contents("/opt/weval-l99/l99-state.json"), true)
|
|
];
|
|
break;
|
|
|
|
case "l99_full_status":
|
|
// Check full-scan progress
|
|
$log = "/opt/weval-l99/logs/last-run.log";
|
|
$pid_file = "/tmp/l99-fullscan.pid";
|
|
$running = false;
|
|
if (file_exists($pid_file)) {
|
|
$pid = trim(@file_get_contents($pid_file));
|
|
if ($pid && file_exists("/proc/$pid")) $running = true;
|
|
}
|
|
// Count layer progress in log
|
|
$layers_done = 0;
|
|
if (file_exists($log)) {
|
|
$layers_done = (int) trim(@shell_exec("grep -c '^\s*OK\s\[' $log 2>/dev/null"));
|
|
}
|
|
$results = [
|
|
"running" => $running,
|
|
"layers_seen_ok" => $layers_done,
|
|
"log_file" => $log,
|
|
"last_lines" => trim(@shell_exec("tail -8 $log 2>/dev/null")),
|
|
"current_state" => @json_decode(@file_get_contents("/opt/weval-l99/l99-state.json"), true)["layers_count"] ?? "?"
|
|
];
|
|
break;
|
|
|
|
case "file_create":
|
|
// OPUS WIRE 16avr — permet à WEVIA de créer des scripts/fichiers via chat NL
|
|
// Allowlist stricte + syntax check + GOLD auto si override
|
|
$target = $_GET['path'] ?? $_POST['path'] ?? '';
|
|
$content_b64 = $_GET['content_b64'] ?? $_POST['content_b64'] ?? '';
|
|
$allowed_prefixes = ['/tmp/', '/opt/weval-ops/', '/var/log/wevia/', '/var/www/html/wiki/', '/opt/obsidian-vault/sessions/', '/opt/obsidian-vault/learnings/'];
|
|
$ok = false;
|
|
foreach ($allowed_prefixes as $p) if (strpos($target, $p) === 0) $ok = true;
|
|
if (!$ok || !$content_b64) { $results = ["error"=>"path_not_allowed_or_empty","allowed"=>$allowed_prefixes]; break; }
|
|
$bin = base64_decode($content_b64, true);
|
|
if ($bin === false) { $results = ["error"=>"b64_decode_fail"]; break; }
|
|
// Syntax check selon extension
|
|
$ext = strtolower(pathinfo($target, PATHINFO_EXTENSION));
|
|
$tmp = tempnam('/tmp', 'fc');
|
|
file_put_contents($tmp, $bin);
|
|
$lint = '';
|
|
if ($ext === 'php') $lint = shell_exec("php8.4 -l $tmp 2>&1");
|
|
elseif ($ext === 'sh') $lint = shell_exec("bash -n $tmp 2>&1");
|
|
elseif ($ext === 'py') $lint = shell_exec("python3 -c \"import py_compile,sys; py_compile.compile(sys.argv[1])\" $tmp 2>&1");
|
|
if ($lint && $ext === 'php' && strpos($lint,'No syntax errors')===false) { @unlink($tmp); $results=["error"=>"php_lint_fail","detail"=>$lint]; break; }
|
|
if ($lint && $ext === 'sh' && trim($lint) !== '') { @unlink($tmp); $results=["error"=>"bash_lint_fail","detail"=>$lint]; break; }
|
|
if ($lint && $ext === 'py' && trim($lint) !== '') { @unlink($tmp); $results=["error"=>"python_lint_fail","detail"=>$lint]; break; }
|
|
// GOLD auto si override
|
|
$existed = file_exists($target);
|
|
if ($existed) {
|
|
$gdir = '/opt/wevads/vault/auto-gold-' . date('Ymd');
|
|
@mkdir($gdir, 0755, true);
|
|
@copy($target, $gdir . '/' . basename($target) . '.' . time() . '.GOLD');
|
|
}
|
|
// Write + chmod exec if script — sudo fallback pour /opt/*
|
|
$dir = dirname($target);
|
|
if (!is_dir($dir)) { @mkdir($dir, 0755, true); if (!is_dir($dir)) shell_exec("sudo mkdir -p " . escapeshellarg($dir)); }
|
|
$n = @file_put_contents($target, $bin);
|
|
if ($n === false && strpos($target, "/opt/") === 0) {
|
|
$tmpw = tempnam("/tmp", "fcw");
|
|
file_put_contents($tmpw, $bin);
|
|
shell_exec("sudo cp " . escapeshellarg($tmpw) . " " . escapeshellarg($target));
|
|
shell_exec("sudo chown www-data:www-data " . escapeshellarg($target));
|
|
@unlink($tmpw);
|
|
clearstatcache();
|
|
$n = file_exists($target) ? filesize($target) : false;
|
|
}
|
|
if (in_array($ext, ['sh','py'])) { @chmod($target, 0755); shell_exec("sudo chmod +x " . escapeshellarg($target)); }
|
|
@unlink($tmp);
|
|
$results = [
|
|
"wrote" => $n,
|
|
"path" => $target,
|
|
"md5" => md5_file($target),
|
|
"existed" => $existed,
|
|
"lint_ext" => $ext,
|
|
"lint_ok" => true,
|
|
"gold_backup" => $existed,
|
|
];
|
|
break;
|
|
|
|
case "generate_script":
|
|
// OPUS v2 — ferme la boucle autonomie: NL → LLM souverain → file_create
|
|
// Usage: ?action=generate_script&description=...&target=/tmp/foo.sh&lang=bash|php|python
|
|
$desc = $_REQUEST["description"] ?? "";
|
|
$target = $_REQUEST["target"] ?? "";
|
|
$lang = strtolower($_REQUEST["lang"] ?? "bash");
|
|
$lang_hints = [
|
|
"bash" => "shebang #!/bin/bash, set -u, PAS de log fichier sauf si demande explicite, exit codes propres, pas d'interactif, sortie stdout pour la reponse",
|
|
"php" => "tag <?php strict, file_put_contents safe, no deprecated, PHP 8.4 syntax, escapeshellarg",
|
|
"python" => "python3 shebang, try/except globaux, json.dump utf-8, no external deps sauf stdlib",
|
|
];
|
|
if (!$desc || !$target) { $results=["error"=>"missing description or target"]; break; }
|
|
$hint = $lang_hints[$lang] ?? "";
|
|
$system = "Tu generes UNIQUEMENT du code $lang sans markdown, sans backticks, sans explication. Regles: $hint. Le code doit etre directement executable. Zero commentaire superflu.";
|
|
$user = "Genere un script $lang qui fait: $desc\n\nUNIQUEMENT le code brut, rien d'autre.";
|
|
|
|
$ch = curl_init("http://127.0.0.1:4000/v1/chat/completions");
|
|
curl_setopt_array($ch, [
|
|
CURLOPT_POST => true,
|
|
CURLOPT_HTTPHEADER => ["Content-Type: application/json"],
|
|
CURLOPT_POSTFIELDS => json_encode([
|
|
"messages" => [
|
|
["role"=>"system","content"=>$system],
|
|
["role"=>"user","content"=>$user],
|
|
],
|
|
"max_tokens" => 1500,
|
|
"stream" => false,
|
|
"temperature" => 0.1,
|
|
]),
|
|
CURLOPT_RETURNTRANSFER => true,
|
|
CURLOPT_TIMEOUT => 30,
|
|
]);
|
|
$raw = curl_exec($ch); curl_close($ch);
|
|
$d = @json_decode($raw, true);
|
|
$code = $d["choices"][0]["message"]["content"] ?? "";
|
|
// Strip markdown si LLM a mis quand même
|
|
$code = preg_replace('/^```[a-z]*\n?/m', '', $code);
|
|
$code = preg_replace('/\n?```\s*$/m', '', $code);
|
|
$code = trim($code);
|
|
if (!$code) { $results=["error"=>"llm_empty","raw"=>substr($raw,0,400)]; break; }
|
|
|
|
// Delegate to file_create internally via re-assign
|
|
$_REQUEST["path"] = $target;
|
|
$_REQUEST["content_b64"] = base64_encode($code);
|
|
// Simule le switch file_create en recapturant sa logique
|
|
$target2 = $_REQUEST["path"];
|
|
$b64 = $_REQUEST["content_b64"];
|
|
$allowed = ["/tmp/", "/opt/weval-ops/", "/var/log/wevia/", "/var/www/html/wiki/", "/opt/obsidian-vault/sessions/", "/opt/obsidian-vault/learnings/"];
|
|
$ok = false; foreach($allowed as $p) if (strpos($target2, $p) === 0) $ok = true;
|
|
if (!$ok) { $results=["error"=>"path_not_allowed","generated_code"=>substr($code,0,500)]; break; }
|
|
|
|
$bin = base64_decode($b64, true);
|
|
$ext = strtolower(pathinfo($target2, PATHINFO_EXTENSION));
|
|
$tmpf = tempnam("/tmp","gs");
|
|
file_put_contents($tmpf, $bin);
|
|
$lint = "";
|
|
if ($ext === "php") $lint = shell_exec("php8.4 -l $tmpf 2>&1");
|
|
elseif ($ext === "sh") $lint = shell_exec("bash -n $tmpf 2>&1");
|
|
elseif ($ext === "py") $lint = shell_exec("python3 -c \"import py_compile,sys; py_compile.compile(sys.argv[1])\" $tmpf 2>&1");
|
|
$lint_fail = false;
|
|
if ($ext==="php" && $lint && strpos($lint,"No syntax errors")===false) $lint_fail = true;
|
|
if ($ext==="sh" && $lint && trim($lint)!=="") $lint_fail = true;
|
|
if ($ext==="py" && $lint && trim($lint)!=="") $lint_fail = true;
|
|
if ($lint_fail) { @unlink($tmpf); $results=["error"=>"llm_generated_bad_syntax","lint"=>$lint,"code_preview"=>substr($code,0,500)]; break; }
|
|
|
|
// Write with sudo fallback si /opt
|
|
$n = @file_put_contents($target2, $bin);
|
|
if ($n === false && strpos($target2, "/opt/") === 0) {
|
|
shell_exec("sudo cp " . escapeshellarg($tmpf) . " " . escapeshellarg($target2));
|
|
shell_exec("sudo chown www-data:www-data " . escapeshellarg($target2));
|
|
clearstatcache();
|
|
$n = file_exists($target2) ? filesize($target2) : false;
|
|
}
|
|
if (in_array($ext,["sh","py"])) { @chmod($target2,0755); shell_exec("sudo chmod +x " . escapeshellarg($target2)); }
|
|
@unlink($tmpf);
|
|
|
|
$results = [
|
|
"ok" => (bool)$n,
|
|
"path" => $target2,
|
|
"lang" => $lang,
|
|
"bytes" => $n,
|
|
"md5" => $n ? md5_file($target2) : null,
|
|
"lint_passed" => !$lint_fail,
|
|
"llm_model" => $d["model"] ?? "sovereign-cascade",
|
|
"code_preview" => substr($code, 0, 300),
|
|
];
|
|
break;
|
|
|
|
|
|
case "deep_diagnostic_modules":
|
|
// Deep diagnose cartographie + L99 modules
|
|
$results = ["cartographie" => [], "l99_module" => [], "l99_saas" => [], "issues" => []];
|
|
|
|
// === Cartographie ===
|
|
$hf = "/var/www/html/api/screens-health.json";
|
|
if (file_exists($hf)) {
|
|
$h = json_decode(file_get_contents($hf), true);
|
|
$total_404 = 0; $total_500 = 0; $sample_404 = []; $sample_500 = [];
|
|
foreach (($h["by_url"] ?? []) as $u => $d) {
|
|
$code = $d["code"] ?? 0;
|
|
if ($code == 404) { $total_404++; if (count($sample_404) < 5) $sample_404[] = $u; }
|
|
elseif ($code >= 500 && $code < 600) { $total_500++; if (count($sample_500) < 5) $sample_500[] = $u; }
|
|
}
|
|
$results["cartographie"] = [
|
|
"total_screens" => $h["total"] ?? 0,
|
|
"counts" => $h["counts"] ?? [],
|
|
"real_404_count" => $total_404,
|
|
"real_500_count" => $total_500,
|
|
"sample_404" => $sample_404,
|
|
"sample_500" => $sample_500,
|
|
"reclassified" => $h["reclassified_count"] ?? 0,
|
|
"last_scan" => $h["generated_at"] ?? "unknown"
|
|
];
|
|
if ($total_404 > 0) $results["issues"][] = "Cartographie: $total_404 vraies pages 404 detectees (iframes seraient blanches)";
|
|
} else {
|
|
$results["issues"][] = "screens-health.json MISSING";
|
|
}
|
|
|
|
// === L99 main ===
|
|
$l99 = "/opt/weval-l99/l99-state.json";
|
|
if (file_exists($l99)) {
|
|
$j = json_decode(file_get_contents($l99), true);
|
|
$results["l99_module"] = [
|
|
"total" => $j["total"] ?? 0,
|
|
"pass" => $j["pass"] ?? 0,
|
|
"fail" => $j["fail"] ?? 0,
|
|
"warn" => $j["warn"] ?? 0,
|
|
"layers_count" => $j["layers_count"] ?? 0,
|
|
"timestamp" => $j["timestamp"] ?? "unknown"
|
|
];
|
|
if (($j["layers_count"] ?? 0) < 20) $results["issues"][] = "L99: seulement ".($j["layers_count"])." layers (doctrine attend 74+). Relancer full_scan.";
|
|
}
|
|
|
|
// === L99 SAAS visual ===
|
|
$vis = "/opt/weval-l99/l99-visual-state.json";
|
|
if (file_exists($vis)) {
|
|
$v = json_decode(file_get_contents($vis), true);
|
|
$results["l99_saas"] = [
|
|
"targets" => $v["targets"] ?? $v["total"] ?? 0,
|
|
"pass" => $v["pass"] ?? 0,
|
|
"fail" => $v["fail"] ?? 0,
|
|
"warn_count" => 0,
|
|
"warn_details" => []
|
|
];
|
|
// Count warns
|
|
if (isset($v["results"])) {
|
|
foreach ($v["results"] as $rn) {
|
|
if (($rn["status"] ?? "") === "WARN") {
|
|
$results["l99_saas"]["warn_count"]++;
|
|
if (count($results["l99_saas"]["warn_details"]) < 10) {
|
|
$results["l99_saas"]["warn_details"][] = ($rn["name"] ?? "?") . " (" . ($rn["pct"] ?? 0) . "% diff)";
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// === Check l99-master.py patch applied ===
|
|
$l99_master = "/opt/weval-l99/l99-master.py";
|
|
if (file_exists($l99_master)) {
|
|
$mc = @file_get_contents($l99_master);
|
|
$results["l99_master_patched"] = strpos($mc, "OPUS_FIX_MINB_500") !== false;
|
|
if (!$results["l99_master_patched"]) $results["issues"][] = "l99-master.py threshold NOT patched (404 still passes as OK)";
|
|
}
|
|
|
|
// === Check cartographie-screens.html v2 patch ===
|
|
$carto = "/var/www/html/cartographie-screens.html";
|
|
if (file_exists($carto)) {
|
|
$cc = @file_get_contents($carto);
|
|
$results["carto_v2_patched"] = strpos($cc, "PREVIEW_V2_HTTPCHECK") !== false;
|
|
if (!$results["carto_v2_patched"]) $results["issues"][] = "cartographie preview v2 not patched";
|
|
}
|
|
|
|
// Summary
|
|
$results["summary"] = [
|
|
"total_issues" => count($results["issues"]),
|
|
"carto_404_in_previews" => $total_404 ?? 0,
|
|
"l99_missing_layers" => max(0, 74 - ($j["layers_count"] ?? 0)),
|
|
"l99_visual_warns" => $results["l99_saas"]["warn_count"] ?? 0
|
|
];
|
|
break;
|
|
|
|
|
|
case "coverage_audit":
|
|
// Deep coverage audit: detect gaps in screen scanning
|
|
$results = [
|
|
"total_scanned" => 0,
|
|
"by_domain" => [],
|
|
"by_category" => [],
|
|
"gaps" => [],
|
|
"spa_sub_routes_missing" => [],
|
|
"suggested_urls" => []
|
|
];
|
|
$hf = "/var/www/html/api/screens-health.json";
|
|
if (!file_exists($hf)) { $results["error"] = "no screens-health"; break; }
|
|
$h = json_decode(file_get_contents($hf), true);
|
|
$urls = array_keys($h["by_url"] ?? []);
|
|
$results["total_scanned"] = count($urls);
|
|
|
|
// Group by domain
|
|
$by_dom = [];
|
|
foreach ($urls as $u) {
|
|
preg_match("#https?://([^/]+)#", $u, $m);
|
|
$d = $m[1] ?? "unknown";
|
|
$by_dom[$d] = ($by_dom[$d] ?? 0) + 1;
|
|
}
|
|
arsort($by_dom);
|
|
$results["by_domain"] = array_slice($by_dom, 0, 20);
|
|
|
|
// Test known subdomains not already fully scanned
|
|
$test_doms = [
|
|
"https://app.weval-consulting.com" => "WEVAL_app",
|
|
"https://weval.ma" => "WEVAL_life_ma",
|
|
"https://git.weval-consulting.com" => "Gitea",
|
|
"https://api.weval-consulting.com" => "API_subdomain",
|
|
"https://admin.weval-consulting.com" => "Admin",
|
|
"https://chat.weval-consulting.com" => "Chat",
|
|
"https://saas.weval-consulting.com" => "SaaS",
|
|
"https://office.weval-consulting.com" => "Office",
|
|
"https://docs.weval-consulting.com" => "Docs",
|
|
"https://status.weval-consulting.com" => "Status"
|
|
];
|
|
foreach ($test_doms as $url => $label) {
|
|
$ch = curl_init($url);
|
|
curl_setopt_array($ch, [CURLOPT_NOBODY => true, CURLOPT_RETURNTRANSFER => true, CURLOPT_TIMEOUT => 4, CURLOPT_SSL_VERIFYPEER => false]);
|
|
curl_exec($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_close($ch);
|
|
$exists = isset($by_dom[parse_url($url, PHP_URL_HOST)]);
|
|
$results["suggested_urls"][] = ["url" => $url, "label" => $label, "http_code" => $code, "in_scan" => $exists];
|
|
if ($code && $code != 404 && !$exists) {
|
|
$results["gaps"][] = "SUBDOMAIN $label ($url) = HTTP $code but NOT in scan";
|
|
}
|
|
}
|
|
|
|
// Detect SPA files with data-page/data-tab NOT scanned as sub-routes
|
|
$spa_files = [
|
|
"/var/www/html/wevads-ia/index.html" => "WEVADS IA SPA",
|
|
"/var/www/html/l99.html" => "L99 Command Center",
|
|
"/var/www/html/l99-saas.html" => "L99 Mission Control",
|
|
"/var/www/html/wevia-master.html" => "WEVIA Master",
|
|
"/var/www/html/growth-engine-v2.html" => "Growth Engine",
|
|
"/var/www/html/agents-archi.html" => "Agents Archi"
|
|
];
|
|
foreach ($spa_files as $path => $label) {
|
|
if (file_exists($path)) {
|
|
$content = file_get_contents($path);
|
|
preg_match_all('/data-(page|tab|route|section)="([^"]+)"/', $content, $m);
|
|
$routes = array_unique($m[2]);
|
|
preg_match_all('/(?:showSection|showTab|openPage|navigateTo)\([\"\']([^\"\']+)[\"\']/', $content, $m2);
|
|
$routes = array_unique(array_merge($routes, $m2[1]));
|
|
if (count($routes) > 0) {
|
|
$results["spa_sub_routes_missing"][] = [
|
|
"file" => basename($path),
|
|
"label" => $label,
|
|
"sub_routes_detected" => count($routes),
|
|
"sample" => array_slice($routes, 0, 10)
|
|
];
|
|
}
|
|
}
|
|
}
|
|
|
|
// Categorize URLs
|
|
$cats = ["API" => 0, "HTML" => 0, "chatbot_generated" => 0, "products" => 0, "blog" => 0, "wevads_sub" => 0, "other" => 0];
|
|
foreach ($urls as $u) {
|
|
if (strpos($u, "wevads.weval-consulting.com") !== false) $cats["wevads_sub"]++;
|
|
elseif (strpos($u, "/api/") !== false) $cats["API"]++;
|
|
elseif (strpos($u, "/generated/") !== false) $cats["chatbot_generated"]++;
|
|
elseif (strpos($u, "/products/") !== false) $cats["products"]++;
|
|
elseif (strpos($u, "/blog/") !== false) $cats["blog"]++;
|
|
elseif (strpos($u, ".html") !== false) $cats["HTML"]++;
|
|
else $cats["other"]++;
|
|
}
|
|
arsort($cats);
|
|
$results["by_category"] = $cats;
|
|
|
|
$results["summary"] = [
|
|
"total" => count($urls),
|
|
"gaps_count" => count($results["gaps"]),
|
|
"spa_with_subroutes" => count($results["spa_sub_routes_missing"]),
|
|
"recommendation" => count($results["gaps"]) > 0 ? "Add detected subdomains to screens-autodiscovery.py sources" : "Coverage complete for top-level"
|
|
];
|
|
break;
|
|
|
|
case "run_fast_tests":
|
|
$batteries = [
|
|
"nonreg" => ["type"=>"http","url"=>"http://127.0.0.1/api/nonreg-api.php?cat=summary","json"=>1],
|
|
"l99" => ["type"=>"http","url"=>"http://127.0.0.1/api/l99-api.php?action=stats","json"=>1],
|
|
"qa_hub" => ["type"=>"http","url"=>"http://127.0.0.1/api/qa-hub-api.php?mode=summary","json"=>1],
|
|
"test_llm" => ["type"=>"http","url"=>"http://127.0.0.1/api/test-llm.php","keyword"=>"ok"],
|
|
"test_groq" => ["type"=>"http","url"=>"http://127.0.0.1/api/test-groq.php","keyword"=>"content"],
|
|
"test_redis" => ["type"=>"http","url"=>"http://127.0.0.1/api/test-redis.php","keyword"=>"ok"],
|
|
"wevia_chat" => ["type"=>"http","url"=>"http://127.0.0.1/api/wevia-chat-test.php","keyword"=>"ok"],
|
|
"functional" => ["type"=>"shell","cmd"=>"timeout 30 python3 /opt/weval-l99/functional-tests.py 2>&1 | tail -15"],
|
|
"ethica_chat" => ["type"=>"shell","cmd"=>"timeout 30 python3 /opt/weval-l99/test-ethica-chatbot.py 2>&1 | tail -10"],
|
|
"wevads_endpoints" => ["type"=>"shell","cmd"=>"timeout 25 bash /opt/weval-l99/test-wevads-endpoints.sh 2>&1 | tail -10"],
|
|
"partners" => ["type"=>"shell","cmd"=>"timeout 20 python3 /opt/weval-l99/test_partners.py 2>&1 | tail -10"],
|
|
"playwright_state" => ["type"=>"file","path"=>"/opt/weval-l99/playwright-visual-state.json","keyword"=>"PASS"],
|
|
];
|
|
$start = microtime(true);
|
|
$mh = curl_multi_init();
|
|
$hdls = [];
|
|
foreach ($batteries as $n => $b) {
|
|
if ($b["type"] !== "http") continue;
|
|
$ch = curl_init($b["url"]);
|
|
curl_setopt_array($ch, [CURLOPT_RETURNTRANSFER=>true, CURLOPT_TIMEOUT=>20, CURLOPT_FOLLOWLOCATION=>true]);
|
|
curl_multi_add_handle($mh, $ch);
|
|
$hdls[$n] = $ch;
|
|
}
|
|
$running = null;
|
|
do { curl_multi_exec($mh, $running); usleep(50000); } while ($running > 0);
|
|
$out = [];
|
|
foreach ($hdls as $n => $ch) {
|
|
$resp = curl_multi_getcontent($ch);
|
|
$code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
|
curl_multi_remove_handle($mh, $ch);
|
|
curl_close($ch);
|
|
$b = $batteries[$n];
|
|
$pass = false; $detail = "http$code";
|
|
if ($code >= 200 && $code < 400) {
|
|
if (isset($b["keyword"])) {
|
|
$pass = (stripos($resp, $b["keyword"]) !== false);
|
|
$detail = $pass ? "kw_ok" : "kw_miss:" . $b["keyword"];
|
|
} else {
|
|
$j = @json_decode($resp, true);
|
|
$p = $j["pass"] ?? $j["score"] ?? null;
|
|
$f = $j["fail"] ?? null;
|
|
$pass = ($p !== null && ($f === null || $f === 0 || $f === "0"));
|
|
$detail = "pass=$p fail=$f";
|
|
}
|
|
}
|
|
$out[$n] = ["pass"=>$pass, "http"=>$code, "detail"=>$detail];
|
|
}
|
|
curl_multi_close($mh);
|
|
|
|
foreach ($batteries as $n => $b) {
|
|
if ($b["type"] === "shell") {
|
|
$r = @shell_exec($b["cmd"]);
|
|
$pass = ($r && stripos($r, "error") === false && stripos($r, "fail") === false && strpos($r, "Traceback") === false);
|
|
$out[$n] = ["pass"=>$pass, "type"=>"shell", "output"=>substr(trim($r ?? ''), 0, 280)];
|
|
} elseif ($b["type"] === "file") {
|
|
$content = @file_get_contents($b["path"]);
|
|
$pass = ($content && stripos($content, $b["keyword"] ?? "PASS") !== false);
|
|
$out[$n] = ["pass"=>$pass, "type"=>"file", "detail"=>$pass ? "found" : "missing"];
|
|
}
|
|
}
|
|
|
|
$passed = count(array_filter($out, fn($v) => $v["pass"] === true));
|
|
$failed = count(array_filter($out, fn($v) => $v["pass"] === false));
|
|
$results = [
|
|
"summary" => ["total"=>count($out), "passed"=>$passed, "failed"=>$failed, "elapsed_s"=>round(microtime(true)-$start,2)],
|
|
"batteries" => $out,
|
|
];
|
|
break;
|
|
|
|
|
|
case "batch_html_guard":
|
|
$results = ["scanned"=>0,"patched"=>[],"already"=>[],"failed"=>[]];
|
|
$raw = shell_exec("grep -rln 'await res.json()' /var/www/html/*.html 2>/dev/null") ?: "";
|
|
$raw .= "\n" . (shell_exec("grep -rln '\\.then(r=>r\\.json())\\|\\.then(r => r\\.json())' /var/www/html/*.html 2>/dev/null") ?: "");
|
|
$files = array_unique(array_filter(array_map("trim", explode("\n", $raw))));
|
|
foreach ($files as $fp) {
|
|
if (!file_exists($fp)) continue;
|
|
$results["scanned"]++;
|
|
$ct = file_get_contents($fp);
|
|
if (strpos($ct, "HTML_GUARD") !== false) { $results["already"][] = basename($fp); continue; }
|
|
$orig = $ct;
|
|
$ct = preg_replace(
|
|
'/(\s*)(var|let|const)\s+(\w+)\s*=\s*await\s+(\w+)\.json\(\)\s*;/',
|
|
'$1/* HTML_GUARD_V2_BATCH */ $2 _t_$3=await $4.text(); $2 $3; {var _q=(_t_$3||"").trim();if(_q.startsWith("<!DOCTYPE")||_q.startsWith("<html")){$3={error:"[HTTP "+($4.status||"?")+"] Backend indisponible",isHtmlError:true};}else{try{$3=JSON.parse(_q)}catch(e){$3={error:"[JSON] "+e.message}}}}',
|
|
$ct
|
|
);
|
|
$ct = preg_replace(
|
|
'/\.then\(\s*(\w+)\s*=>\s*\1\.json\(\)\s*\)/',
|
|
'.then($1=>$1.text().then(t=>{/* HTML_GUARD_V2_BATCH */var q=(t||"").trim();if(q.startsWith("<!DOCTYPE")||q.startsWith("<html")){return{error:"[HTTP "+$1.status+"]",isHtmlError:true}}try{return JSON.parse(q)}catch(e){return{error:"JSON "+e.message}}}))',
|
|
$ct
|
|
);
|
|
if ($ct !== $orig) {
|
|
shell_exec("sudo chattr -i " . escapeshellarg($fp) . " 2>/dev/null");
|
|
$w = @file_put_contents($fp, $ct);
|
|
if ($w === false) {
|
|
$tmp = tempnam("/tmp","bhg");
|
|
file_put_contents($tmp, $ct);
|
|
shell_exec("sudo cp " . escapeshellarg($tmp) . " " . escapeshellarg($fp));
|
|
unlink($tmp);
|
|
$w = filesize($fp);
|
|
}
|
|
shell_exec("sudo chattr +i " . escapeshellarg($fp) . " 2>/dev/null");
|
|
if (strpos(file_get_contents($fp), "HTML_GUARD_V2_BATCH") !== false) {
|
|
$results["patched"][] = basename($fp);
|
|
} else {
|
|
$results["failed"][] = basename($fp);
|
|
}
|
|
}
|
|
}
|
|
$results["summary"] = ["scanned"=>$results["scanned"],"patched"=>count($results["patched"]),"already"=>count($results["already"]),"failed"=>count($results["failed"])];
|
|
break;
|
|
default:
|
|
$results=["actions"=>["test_providers","webchat","nonreg","reconcile","git_push","ethica","docker_list","git_log","disk","ports","crons","services","playwright_scan","paperclip","slack"]];
|
|
}
|
|
echo json_encode(["ok"=>true,"action"=>$action,"results"=>$results]);
|