"auth"])); } if ($action === "status") { $hb = file_exists($HEARTBEAT) ? json_decode(file_get_contents($HEARTBEAT), true) : null; $tasks = glob($TASKS_DIR . "/task_*.json"); $pending = $done = $failed = 0; foreach ($tasks as $t) { $d = json_decode(file_get_contents($t), true); if ($d["status"] === "pending") $pending++; elseif ($d["status"] === "done") $done++; elseif ($d["status"] === "failed") $failed++; } $online = $hb && (time() - strtotime($hb["ts"] ?? "2000-01-01")) < 120; echo json_encode(["ok" => true, "blade" => [ "online" => $online, "page_version" => "3.1", "force_reload" => true, "heartbeat" => $hb, "tasks" => ["pending" => $pending, "done" => $done, "failed" => $failed, "total" => count($tasks)] ]]); exit; } // Auth required for all other actions if ($k !== $KEY) { echo json_encode(["error" => "auth"]); exit; } switch ($action) { case "push": $type = $_REQUEST["type"] ?? "powershell"; $cmd = $_REQUEST["cmd"] ?? ""; $label = $_REQUEST["label"] ?? "task"; $priority = intval($_REQUEST["priority"] ?? 5); if (!$cmd) { echo json_encode(["error" => "no cmd"]); exit; } $id = "task_" . date("Ymd_His") . "_" . substr(md5(uniqid()), 0, 6); $task = [ "id" => $id, "type" => $type, "cmd" => $cmd, "label" => $label, "priority" => $priority, "status" => "pending", "created" => date("c"), "started" => null, "completed" => null, "result" => null, "error" => null, "source" => $_REQUEST["source"] ?? "api" ]; file_put_contents($TASKS_DIR . "/" . $id . ".json", json_encode($task, JSON_PRETTY_PRINT)); echo json_encode(["ok" => true, "task" => $task]); break; case "poll": // Blade polls for pending tasks $tasks = glob($TASKS_DIR . "/task_*.json"); $pending = []; foreach ($tasks as $t) { $d = json_decode(file_get_contents($t), true); if ($d["status"] === "pending") $pending[] = $d; } usort($pending, fn($a, $b) => $b["priority"] - $a["priority"]); echo json_encode(["ok" => true, "tasks" => array_slice($pending, 0, 5)]); break; case "report": $id = $_REQUEST["id"] ?? ""; $f = $TASKS_DIR . "/" . $id . ".json"; if (!file_exists($f)) { echo json_encode(["error" => "not found"]); exit; } $task = json_decode(file_get_contents($f), true); $task["status"] = $_REQUEST["status"] ?? "done"; $task["result"] = $_REQUEST["result"] ?? ""; $task["error"] = $_REQUEST["error"] ?? null; $task["completed"] = date("c"); file_put_contents($f, json_encode($task, JSON_PRETTY_PRINT)); echo json_encode(["ok" => true, "task" => $task]); break; case "oob_result": // NEW v2.3: agent POSTs OOB exec result here $label = $_REQUEST["label"] ?? "unknown"; $result = $_REQUEST["result"] ?? ""; $ts = $_REQUEST["ts"] ?? date("c"); $dir = "/var/www/html/api/blade-tasks"; $f = $dir . "/oob_result_" . date("Ymd_His") . "_" . substr(md5($label.$ts), 0, 6) . ".json"; @file_put_contents($f, json_encode([ "label" => $label, "result" => $result, "ts" => $ts, "received_at" => date("c"), "ip" => $_SERVER["REMOTE_ADDR"] ?? "?" ], JSON_PRETTY_PRINT)); echo json_encode(["ok" => true, "stored" => basename($f)]); break; case "heartbeat": $data = [ "ts" => date("c"), "hostname" => $_REQUEST["hostname"] ?? "blade", "cpu" => $_REQUEST["cpu"] ?? "?", "ram" => $_REQUEST["ram"] ?? "?", "disk" => $_REQUEST["disk"] ?? "?", "uptime" => $_REQUEST["uptime"] ?? "?", "user" => $_REQUEST["user"] ?? "?", "ip" => $_SERVER["REMOTE_ADDR"], "agent_version" => $_REQUEST["version"] ?? "2.0" ]; @file_put_contents($HEARTBEAT, json_encode($data, JSON_PRETTY_PRINT)); $pc = 0; foreach (@glob($TASKS_DIR . "/task_*.json") as $tf) { $td = @json_decode(@file_get_contents($tf), true); if ($td && ($td["status"] ?? "") === "pending") $pc++; } $resp = ["ok" => true, "pending" => $pc]; // ONE-SHOT pending_exec file (injected by Master) $pef = $TASKS_DIR . "/pending_exec.json"; if (file_exists($pef)) { $pe = @json_decode(@file_get_contents($pef), true); if ($pe && isset($pe["exec_cmd"])) { $resp["exec_cmd"] = $pe["exec_cmd"]; $resp["oob_label"] = $pe["label"] ?? "master-oob"; // Archive + delete (one-shot) $arch = $TASKS_DIR . "/pending_exec.archive." . date("Ymd-His") . ".json"; @rename($pef, $arch); } } // Legacy upgrade fallback if (!isset($resp["exec_cmd"]) && $pc > 0 && ($_REQUEST["version"] ?? "2.0") < "3.0") { $resp["exec_cmd"] = "irm https://weval-consulting.com/api/blade-tasks/wevia-agent-v3.ps1 | iex"; $resp["upgrade"] = "v3.0"; } echo json_encode($resp); break; case "list": $tasks = glob($TASKS_DIR . "/task_*.json"); $all = []; foreach (array_slice(array_reverse($tasks), 0, 50) as $t) { $all[] = json_decode(file_get_contents($t), true); } echo json_encode(["ok" => true, "tasks" => $all]); break; case "agent_version": echo json_encode(["ok" => true, "version" => "2.2", "url" => "https://weval-consulting.com/products/weval-sentinel-agent.ps1"]); exit; case "clear": $tasks = glob($TASKS_DIR . "/task_*.json"); $cleared = 0; foreach ($tasks as $t) { $d = json_decode(file_get_contents($t), true); if ($d["status"] !== "pending") { unlink($t); $cleared++; } } echo json_encode(["ok" => true, "cleared" => $cleared]); break; default: echo json_encode(["error" => "unknown action", "actions" => ["status","push","poll","report","heartbeat","list","clear"]]); } if ($action === "poll") { $next = null; $pending = 0; $files = glob($TASKS_DIR . "/task_*.json"); sort($files); foreach ($files as $tf) { $td = @json_decode(file_get_contents($tf), true); if (!$td) continue; if (($td["status"] ?? "") === "pending") { if (!$next) { $next = $td; $next["_file"] = basename($tf); $td["status"] = "dispatched"; $td["dispatched_at"] = date("c"); file_put_contents($tf, json_encode($td, JSON_PRETTY_PRINT)); } else { $pending++; } } } die(json_encode(["task" => $next, "pending" => $pending])); } if ($action === "task_done") { $task_file = $_POST["task_file"] ?? $_GET["task_file"] ?? ""; $result = $_POST["result"] ?? $_GET["result"] ?? ""; $tf = $TASKS_DIR . "/" . basename($task_file); if ($task_file && file_exists($tf)) { $td = json_decode(file_get_contents($tf), true); $td["status"] = "done"; $td["completed_at"] = date("c"); $td["result"] = substr($result, 0, 2000); file_put_contents($tf, json_encode($td, JSON_PRETTY_PRINT)); die(json_encode(["ok" => true])); } die(json_encode(["error" => "not found"])); }