diff --git a/admin-v2.html b/admin-v2.html index fd4828b65..a4a1a9332 100644 --- a/admin-v2.html +++ b/admin-v2.html @@ -99,20 +99,22 @@ async function fetchData(){ }catch(e){render();} } -const SERVICES=[ - {n:'Nginx',p:':80/:443',s:'up',t:'system'},{n:'Sovereign API',p:':4000',s:'up',t:'systemd'}, - {n:'Paperclip',p:':3100',s:'up',t:'systemd'},{n:'DeerFlow',p:':3002/:3003',s:'up',t:'systemd'}, - {n:'Ollama 12 models',p:':11434',s:'up',t:'systemd'},{n:'OpenWebUI',p:':8281',s:'up',t:'docker'}, - {n:'Flowise',p:':3033',s:'up',t:'docker'},{n:'n8n',p:':5678',s:'up',t:'docker'}, - {n:'Twenty CRM',p:':3000',s:'up',t:'docker'},{n:'Mattermost',p:':8065',s:'up',t:'docker'}, - {n:'SearXNG',p:':8080',s:'up',t:'docker'},{n:'Qdrant',p:':6333',s:'up',t:'docker'}, - {n:'Plausible',p:':8000',s:'up',t:'docker'},{n:'Authentik SSO',p:':9000',s:'up',t:'docker'}, - {n:'Vaultwarden',p:':8222',s:'up',t:'docker'},{n:'Uptime Kuma',p:':3001',s:'up',t:'docker'}, - {n:'ClickHouse',p:':8123',s:'up',t:'docker'},{n:'Loki',p:':18821',s:'down',t:'docker'}, - {n:'PMTA',p:':25',s:'up',t:'system'},{n:'KumoMTA',p:':587',s:'up',t:'system'}, - {n:'CrowdSec',p:'—',s:'up',t:'systemd'},{n:'Fail2Ban',p:'—',s:'up',t:'systemd'}, - {n:'Blade Sentinel',p:'*/60s',s:'up',t:'agent'} +/* V96.20 Opus 20avr: hardcoded SERVICES replaced by LIVE checks from /api/wevia-services-live.php */ +let SERVICES=[ + {n:'Loading services…',p:'…',s:'up',t:'system'} ]; +async function loadLiveServices(){ + try{ + const r=await fetch('/api/wevia-services-live.php?t='+Date.now()); + const d=await r.json(); + if(d && Array.isArray(d.services)){ + SERVICES = d.services.map(s=>({n:s.n, p:s.p, s:s.s, t:s.t, ms:s.ms})); + if(typeof fetchData==='function') fetchData(); + } + }catch(e){} +} +setTimeout(loadLiveServices, 600); +setInterval(loadLiveServices, 60000); /* V96.15 Opus 19avr: hardcoded ALERTS replaced by LIVE checks from /api/wevia-real-alerts.php (doctrine #4 HONNÊTE) */ let ALERTS=[ diff --git a/api/blade-tasks/wevia-agent-v4.ps1 b/api/blade-tasks/wevia-agent-v4.ps1 new file mode 100644 index 000000000..271389398 --- /dev/null +++ b/api/blade-tasks/wevia-agent-v4.ps1 @@ -0,0 +1,176 @@ +# WEVIA Blade Agent v4.0 — PERSISTENT + SELF-HEALING + MCP-READY +# Install one-liner: irm https://weval-consulting.com/downloads/blade-install.ps1 | iex +# Auto-restart via Windows Task Scheduler + NSSM service +# Polls blade-api.php, executes tasks, reports back, heartbeats every 30s + +$ErrorActionPreference = "Continue" +$VERSION = "4.0" +$BASE = "https://weval-consulting.com/api" +$KEY = "BLADE2026" +$POLL_INTERVAL = 8 +$HEARTBEAT_INTERVAL = 30 +$AGENT_DIR = "C:\ProgramData\WEVAL" +$LOG = "$AGENT_DIR\agent-v4.log" +$PID_FILE = "$AGENT_DIR\agent-v4.pid" +$WATCHDOG_FILE = "$AGENT_DIR\agent-v4.watchdog" + +New-Item -ItemType Directory -Path $AGENT_DIR -Force -ErrorAction SilentlyContinue | Out-Null + +# Write PID for watchdog +$PID | Out-File $PID_FILE -Force + +function Log-Line([string]$msg) { + $line = "[$(Get-Date -Format 'yyyy-MM-ddTHH:mm:ss')] $msg" + Add-Content -Path $LOG -Value $line -ErrorAction SilentlyContinue + Write-Host $line + # Cap log at 5MB by rotating + if ((Get-Item $LOG -ErrorAction SilentlyContinue).Length -gt 5MB) { + Move-Item $LOG "$LOG.1" -Force -ErrorAction SilentlyContinue + } +} + +function Get-SystemStats { + try { + $cpu = [int]((Get-Counter '\Processor(_Total)\% Processor Time' -ErrorAction SilentlyContinue).CounterSamples[0].CookedValue) + $os = Get-CimInstance Win32_OperatingSystem -ErrorAction SilentlyContinue + $ramFree = [int]($os.FreePhysicalMemory / 1024) + $disk = Get-PSDrive C -ErrorAction SilentlyContinue + $diskPctFree = [int](100 * $disk.Free / ($disk.Used + $disk.Free)) + $uptime = [int]((Get-Date) - $os.LastBootUpTime).TotalMinutes + return @{cpu=$cpu; ram_free_mb=$ramFree; disk_free_pct=$diskPctFree; uptime_min=$uptime} + } catch { + return @{cpu=0; ram_free_mb=0; disk_free_pct=0; uptime_min=0} + } +} + +function Send-Heartbeat { + $stats = Get-SystemStats + try { + $body = @{ + k=$KEY; action="heartbeat" + hostname=$env:COMPUTERNAME; version=$VERSION + cpu=$stats.cpu; ram=$stats.ram_free_mb + disk=$stats.disk_free_pct; uptime=$stats.uptime_min + user=$env:USERNAME + } + Invoke-RestMethod -Uri "$BASE/blade-api.php" -Method POST -Body $body -TimeoutSec 10 -ErrorAction Stop | Out-Null + # Write watchdog timestamp + (Get-Date).Ticks | Out-File $WATCHDOG_FILE -Force + return $true + } catch { + Log-Line "HEARTBEAT_ERR $($_.Exception.Message)" + return $false + } +} + +function Poll-Task { + try { + $resp = Invoke-RestMethod -Uri "$BASE/blade-api.php?k=$KEY&action=poll" -TimeoutSec 10 -ErrorAction Stop + if ($resp.tasks -and $resp.tasks.Count -gt 0) { return $resp.tasks[0] } + # Fallback poll endpoint + $resp2 = Invoke-RestMethod -Uri "$BASE/blade-poll.php?k=$KEY&action=poll" -TimeoutSec 10 -ErrorAction SilentlyContinue + if ($resp2.task) { return $resp2.task } + } catch { + Log-Line "POLL_ERR $($_.Exception.Message)" + } + return $null +} + +function Claim-Task([object]$task) { + # Mark as dispatched on server (already done on pull) - but bump to running + try { + $body = @{k=$KEY; action="claim"; id=$task.id} + Invoke-RestMethod -Uri "$BASE/blade-api.php" -Method POST -Body $body -TimeoutSec 5 | Out-Null + } catch {} +} + +function Execute-Task([object]$task) { + $id = $task.id + $label = $task.label + $cmd = $task.cmd + if (-not $cmd) { $cmd = $task.command } + if (-not $cmd -and $task.commands) { + $cmd = ($task.commands -join "`n") + } + if (-not $cmd) { + Log-Line "EXEC_NO_CMD id=$id" + Report-Task $id "failed" "" "task has no cmd/command/commands field" + return + } + + Log-Line "EXEC_START id=$id label=$label cmd_len=$($cmd.Length)" + $startTime = Get-Date + Claim-Task $task + + $tmpScript = "$env:TEMP\wevia-task-$id.ps1" + Set-Content -Path $tmpScript -Value $cmd -Force -Encoding UTF8 + + $output = "" + $rc = 0 + try { + $output = & powershell -ExecutionPolicy Bypass -NoProfile -File $tmpScript 2>&1 | Out-String + $rc = $LASTEXITCODE + } catch { + $output = "EXCEPTION: $($_.Exception.Message)" + $rc = 1 + } finally { + Remove-Item $tmpScript -Force -ErrorAction SilentlyContinue + } + + $duration = [int]((Get-Date) - $startTime).TotalSeconds + $status = if ($rc -eq 0) { "done" } else { "failed" } + $truncated = if ($output.Length -gt 4000) { $output.Substring(0, 4000) + "...[TRUNCATED]" } else { $output } + + Log-Line "EXEC_DONE id=$id rc=$rc status=$status duration=${duration}s output_len=$($output.Length)" + Report-Task $id $status $truncated $null +} + +function Report-Task([string]$id, [string]$status, [string]$result, [string]$error_msg) { + try { + $body = @{ + k=$KEY; action="report"; id=$id; status=$status + result=$result + } + if ($error_msg) { $body.error = $error_msg } + Invoke-RestMethod -Uri "$BASE/blade-api.php" -Method POST -Body $body -TimeoutSec 15 | Out-Null + Log-Line "REPORTED id=$id status=$status" + } catch { + Log-Line "REPORT_ERR id=$id err=$($_.Exception.Message)" + } +} + +# MAIN LOOP +Log-Line "AGENT_V4_START pid=$PID host=$env:COMPUTERNAME user=$env:USERNAME" + +$lastHeartbeat = [DateTime]::MinValue +$lastPollErr = 0 +$consecutiveErrors = 0 + +while ($true) { + try { + # Heartbeat + if (((Get-Date) - $lastHeartbeat).TotalSeconds -ge $HEARTBEAT_INTERVAL) { + if (Send-Heartbeat) { $lastHeartbeat = Get-Date; $consecutiveErrors = 0 } + else { $consecutiveErrors++ } + } + + # Poll task + $task = Poll-Task + if ($task) { + Execute-Task $task + } else { + Start-Sleep -Seconds $POLL_INTERVAL + } + + # Self-heal if many consecutive errors (>10) + if ($consecutiveErrors -gt 10) { + Log-Line "TOO_MANY_ERRORS=$consecutiveErrors sleeping 60s before retry" + Start-Sleep -Seconds 60 + $consecutiveErrors = 0 + } + } catch { + Log-Line "LOOP_ERR $($_.Exception.Message)" + Start-Sleep -Seconds 5 + $consecutiveErrors++ + } +} diff --git a/api/wevia-real-alerts.php b/api/wevia-real-alerts.php index 2d7069a96..9cb18e9c9 100644 --- a/api/wevia-real-alerts.php +++ b/api/wevia-real-alerts.php @@ -75,7 +75,7 @@ if ($wa_ok) { $alerts[] = [ 'id' => 'check_whatsapp', 'ti' => 'WhatsApp Business', - 'ms' => $wa_ok ? ($wa_live ? "Token LIVE (Meta API 200)" : "Token présent ({$wa_token} chars) · API returned $code") : 'Token manquant', + 'ms' => $wa_ok ? ($wa_live ? "Token LIVE (Meta API 200)" : "Token présent (" . strlen($wa_token) . "ch) · API returned $code") : 'Token manquant', 'sv' => ($wa_ok && $wa_live) ? 'ok' : ($wa_ok ? 'warning' : 'critical'), 'evidence' => "WHATSAPP_TOKEN ".strlen($wa_token)."ch · Graph API probe code $code", 'action_required' => $wa_ok ? 'none' : 'regenerate in Meta Business Suite', diff --git a/api/wevia-services-live.php b/api/wevia-services-live.php new file mode 100644 index 000000000..adbc34660 --- /dev/null +++ b/api/wevia-services-live.php @@ -0,0 +1,124 @@ + true, 'ms' => $ms]; } + return ['up' => false, 'ms' => $ms, 'err' => $errstr]; +} + +function probe_http($url, $timeout = 3) { + $start = microtime(true); + $ch = curl_init($url); + curl_setopt_array($ch, [ + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $timeout, + CURLOPT_NOBODY => true, + CURLOPT_SSL_VERIFYPEER => false, + CURLOPT_SSL_VERIFYHOST => false, + ]); + curl_exec($ch); + $code = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $ms = round((microtime(true) - $start) * 1000); + curl_close($ch); + return ['up' => ($code >= 200 && $code < 500), 'ms' => $ms, 'code' => $code]; +} + +function probe_systemd($unit) { + exec("systemctl is-active $unit 2>&1", $o); + $status = trim($o[0] ?? ''); + return ['up' => ($status === 'active'), 'status' => $status]; +} + +function probe_docker($name) { + exec("docker ps --filter name=$name --format '{{.Status}}' 2>&1", $o); + $status = trim(implode(' ', $o)); + $up = (strpos($status, 'Up') !== false); + return ['up' => $up, 'status' => $status ?: 'not found']; +} + +// 23 services from admin-v2 — verify each +$services = [ + // systemd / nginx / FPM + ['n' => 'Nginx', 'p' => ':80/:443', 't' => 'system', 'probe' => 'http://localhost'], + ['n' => 'Apache', 'p' => ':5890/:8443', 't' => 'system', 'probe' => 'http://localhost:5890'], + ['n' => 'PHP-FPM 8.5', 'p' => ':socket', 't' => 'systemd', 'systemd' => 'php8.5-fpm'], + // LLM + APIs + ['n' => 'Sovereign API', 'p' => ':4000', 't' => 'systemd', 'probe' => 'http://localhost:4000/v1/models'], + ['n' => 'Ollama', 'p' => ':11434', 't' => 'systemd', 'probe' => 'http://localhost:11434/api/tags'], + ['n' => 'Paperclip', 'p' => ':3100', 't' => 'systemd', 'probe' => 'http://localhost:3100'], + ['n' => 'DeerFlow', 'p' => ':3002/:3003', 't' => 'systemd', 'probe' => 'http://localhost:3002'], + // Docker containers + ['n' => 'Loki', 'p' => ':3100 (container)', 't' => 'docker', 'docker' => 'loki'], + ['n' => 'Gitea', 'p' => ':3300', 't' => 'docker', 'docker' => 'gitea'], + ['n' => 'Qdrant', 'p' => ':6333', 't' => 'docker', 'docker' => 'qdrant'], + ['n' => 'Mattermost', 'p' => ':8065', 't' => 'docker', 'docker' => 'mattermost'], + ['n' => 'Twenty CRM', 'p' => ':3000', 't' => 'docker', 'docker' => 'twenty'], + ['n' => 'Langfuse', 'p' => ':3333', 't' => 'docker', 'docker' => 'langfuse'], + ['n' => 'Prometheus', 'p' => ':9090', 't' => 'docker', 'docker' => 'prometheus'], + ['n' => 'Uptime-Kuma', 'p' => ':3001', 't' => 'docker', 'docker' => 'uptime-kuma'], + ['n' => 'Vaultwarden', 'p' => ':8222', 't' => 'docker', 'docker' => 'vaultwarden'], + ['n' => 'Redis-Weval', 'p' => ':6379', 't' => 'docker', 'docker' => 'redis-weval'], + ['n' => 'SearXNG', 'p' => ':8080', 't' => 'docker', 'docker' => 'searxng'], + ['n' => 'Plausible', 'p' => ':8000', 't' => 'docker', 'docker' => 'plausible'], + ['n' => 'n8n', 'p' => ':5678', 't' => 'docker', 'docker' => 'n8n'], + ['n' => 'Listmonk', 'p' => ':9000', 't' => 'docker', 'docker' => 'listmonk'], + // PG + Email infra + ['n' => 'PostgreSQL', 'p' => ':5432', 't' => 'systemd', 'systemd' => 'postgresql'], + // Security + ['n' => 'Fail2Ban', 'p' => 'daemon', 't' => 'systemd', 'systemd' => 'fail2ban'], +]; + +$results = []; +$up_count = 0; +$total_ms = 0; + +foreach ($services as $s) { + $r = null; + if (!empty($s['probe'])) { + $r = probe_http($s['probe']); + } elseif (!empty($s['docker'])) { + $r = probe_docker($s['docker']); + } elseif (!empty($s['systemd'])) { + $r = probe_systemd($s['systemd']); + } + + $entry = [ + 'n' => $s['n'], + 'p' => $s['p'], + 't' => $s['t'], + 's' => ($r && !empty($r['up'])) ? 'up' : 'down', + ]; + if (isset($r['ms'])) { $entry['ms'] = $r['ms']; $total_ms += $r['ms']; } + if (isset($r['status'])) $entry['detail'] = $r['status']; + if (isset($r['code'])) $entry['code'] = $r['code']; + + if ($entry['s'] === 'up') $up_count++; + $results[] = $entry; +} + +$total = count($results); +$uptime_pct = $total > 0 ? round(($up_count / $total) * 100, 1) : 0; + +echo json_encode([ + 'v' => 'V96.20-services-live-opus', + 'ts' => date('c'), + 'total' => $total, + 'up' => $up_count, + 'down' => $total - $up_count, + 'uptime_pct' => $uptime_pct, + 'avg_latency_ms' => $total > 0 ? round($total_ms / $total) : 0, + 'services' => $results, +], JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE); diff --git a/api/wevia-vault.php.GOLD-V79-20260420-030934 b/api/wevia-vault.php.GOLD-V79-20260420-030934 new file mode 100644 index 000000000..eb5091435 --- /dev/null +++ b/api/wevia-vault.php.GOLD-V79-20260420-030934 @@ -0,0 +1,85 @@ + 'no query']); exit; } + $results = []; + $iter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($VAULT)); + foreach ($iter as $file) { + if ($file->getExtension() !== 'md') continue; + $content = file_get_contents($file->getPathname()); + if (stripos($content, $q) !== false) { + $rel = str_replace($VAULT . '/', '', $file->getPathname()); + // Extract frontmatter tags + preg_match('/tags:\s*\[([^\]]+)\]/', $content, $tm); + $results[] = [ + 'file' => $rel, + 'tags' => trim($tm[1] ?? ''), + 'snippet' => substr(strip_tags($content), 0, 200), + 'size' => strlen($content) + ]; + } + } + echo json_encode(['query' => $q, 'results' => $results, 'count' => count($results)]); + break; + + case 'read': + $file = $_GET['file'] ?? ''; + $path = realpath($VAULT . '/' . $file); + if (!$path || strpos($path, $VAULT) !== 0 || !file_exists($path)) { + echo json_encode(['error' => 'file not found']); exit; + } + echo json_encode(['file' => $file, 'content' => file_get_contents($path), 'size' => filesize($path)]); + break; + + case 'list': + $dir = $_GET['dir'] ?? ''; + $target = realpath($VAULT . '/' . $dir) ?: $VAULT; + if (strpos($target, $VAULT) !== 0) { echo json_encode(['error' => 'invalid path']); exit; } + $files = []; + foreach (scandir($target) as $f) { + if ($f[0] === '.') continue; + $full = $target . '/' . $f; + $files[] = ['name' => $f, 'type' => is_dir($full) ? 'dir' : 'file', 'size' => is_file($full) ? filesize($full) : 0]; + } + echo json_encode(['dir' => $dir ?: '/', 'files' => $files, 'count' => count($files)]); + break; + + case 'write': + $file = $_POST['file'] ?? ''; + $content = $_POST['content'] ?? ''; + if (!$file || !$content) { echo json_encode(['error' => 'file and content required']); exit; } + $path = $VAULT . '/' . $file; + $dir = dirname($path); + if (!is_dir($dir)) mkdir($dir, 0755, true); + file_put_contents($path, $content); + echo json_encode(['ok' => true, 'file' => $file, 'size' => strlen($content)]); + break; + + case 'stats': + $count = 0; $total = 0; $dirs = []; + $iter = new RecursiveIteratorIterator(new RecursiveDirectoryIterator($VAULT)); + foreach ($iter as $f) { + if ($f->getExtension() === 'md') { $count++; $total += $f->getSize(); } + } + foreach (scandir($VAULT) as $d) { + if ($d[0] !== '.' && is_dir("$VAULT/$d")) { + $n = count(glob("$VAULT/$d/*.md")); + $dirs[] = ['name' => $d, 'files' => $n]; + } + } + echo json_encode(['vault' => $VAULT, 'files' => $count, 'total_bytes' => $total, 'dirs' => $dirs]); + break; + + default: + echo json_encode(['error' => 'unknown action', 'actions' => ['search','read','list','write','stats']]); +} diff --git a/architecture.html b/architecture.html index fe3b74adb..0fb89aed0 100644 --- a/architecture.html +++ b/architecture.html @@ -244,7 +244,7 @@ footer{text-align:center;padding:20px;color:var(--dim);font-size:.58rem;font-fam #ld .spin{width:32px;height:32px;border:2px solid var(--dim2);border-top-color:var(--acc);border-radius:50%;animation:spin .8s linear infinite} -
WEVIA
+
WEVIA
Architecture Director @@ -376,7 +376,7 @@ h+='

Recommendations

';recs.forEach(r=>{h+=recoH(r)});h+='{h+=`
${d.fact}
${d.created_at}
`});h+='
'}return h} function rpC(){const cx=D.cortex||{};let h=`
${kp(cx.fast_lines,'CORTEX','lines','bl')}${kp(cx.router_lines,'Router','lines','p')}${kp(cx.router_functions,'Functions','routing','g')}${kp(D.ai_providers.length,'Providers','0€','o')}
`; -h+='

Smart Router

';[['T0: Local','weval-brain-v3','~200ms','p'],['T1: Free','Cerebras · Groq · SambaNova','~1.3s','g'],['T2: Fallback','Mistral · Cohere · Gemini','~2s','b'],['T3: Emergency','HuggingFace · Replicate','~3s','d']].forEach(([n,d,l,c])=>{h+=`

${n}

${d}
${l}
`}); +h+='

Smart Router

';[['T0: Local','weval-brain-v4','~200ms','p'],['T1: Free','Cerebras · Groq · SambaNova','~1.3s','g'],['T2: Fallback','Mistral · Cohere · Gemini','~2s','b'],['T3: Emergency','HuggingFace · Replicate','~3s','d']].forEach(([n,d,l,c])=>{h+=`

${n}

${d}
${l}
`}); h+='

Providers

'; D.ai_providers.forEach(p=>{h+=``});h+='
ProviderModelTier
${p.name}${p.model}${tg(p.tier,p.tier==='T0'?'p':p.tier==='T1'?'g':p.tier==='T2'?'b':'d')}
';return h} diff --git a/dg-command-center.html.GOLD-V79-20260420-030934 b/dg-command-center.html.GOLD-V79-20260420-030934 new file mode 100644 index 000000000..d8ae9715b --- /dev/null +++ b/dg-command-center.html.GOLD-V79-20260420-030934 @@ -0,0 +1,593 @@ + + + + + +WEVAL · DG Command Center — Real-time Pilotage + + + +
+ +
+
+

🎖️DG Command Center

+
Real-time pilotage — TOC · Conversion · Data · Marketing · CRM · Risk · Alertes
+
+
+
+ 🏠 WTP + 🧮 ROI Sim + 💼 CRM + +
+
+ + +
+
+
🚨 Alertes DG — à traiter maintenant — alertes
+
+
+
+
+ + +
+
+
+
🎯 TOC Theory of Constraints — Goldratt
+
— bottleneck
+
+
+
+ 5 Focusing Steps (Goldratt): + 1. Identifier la contrainte · 2. Exploiter (max) · 3. Subordonner tout le reste · 4. Élever la contrainte · 5. Si brisée → reprendre au 1 +
+
+ +
+
+
🎚️ Conversion Funnel
+
— %
+
+
+
+
+ + +
+
+
🔌 Data Pipelines Health
live
+
+
+
+
📣 Marketing KPIs
WEVADS + Ethica
+
+
+
+ + +
+
+
+
💼 CRM Pipeline by Stage
+
— k€
+
+
+
+
Opps actives
+
Won ce mois
+
Lost
+
Cycle (j)
+
+
+
+
🎯 Top Accounts & Next Steps
+
+
+
+ + +
+
+
+
⚠️ Risk Management WEVAL — Matrice 5×5
+
+
+
+
+
Likelihood
+
L=5
L=4
L=3
L=2
L=1
+
+
+
+
+
+
Impact 1
2
3
4
5
+
+
+
+
+ +
+
📋 Top 8 Risques à traiter
+
+
+
+ +
+ + + + + + + + + + diff --git a/downloads/blade-install.ps1 b/downloads/blade-install.ps1 new file mode 100644 index 000000000..f604a7a05 --- /dev/null +++ b/downloads/blade-install.ps1 @@ -0,0 +1,112 @@ +# WEVIA Blade Install v4.0 — DEFINITIVE persistent install +# Run as Administrator in PowerShell: +# irm https://weval-consulting.com/downloads/blade-install.ps1 | iex + +$ErrorActionPreference = "Continue" +$ProgressPreference = "SilentlyContinue" +$BASE = "https://weval-consulting.com" +$INSTALL_DIR = "C:\ProgramData\WEVAL" +New-Item -ItemType Directory -Path $INSTALL_DIR -Force -ErrorAction SilentlyContinue | Out-Null + +Write-Host "============================================================" -ForegroundColor Cyan +Write-Host " WEVIA Blade Agent v4.0 - DEFINITIVE INSTALL" -ForegroundColor Cyan +Write-Host "============================================================" -ForegroundColor Cyan + +# STEP 1: Kill all existing WEVIA/Sentinel agents +Write-Host "`n[1/6] Killing existing agents..." -ForegroundColor Yellow +Get-Process -Name powershell, pwsh -ErrorAction SilentlyContinue | Where-Object { + try { $_.CommandLine -match "sentinel-agent|blade-agent|wevia-agent" } catch { $false } +} | Stop-Process -Force -ErrorAction SilentlyContinue +Get-ScheduledTask -TaskName "WEVIA*", "Sentinel*", "Blade*" -ErrorAction SilentlyContinue | Unregister-ScheduledTask -Confirm:$false -ErrorAction SilentlyContinue +Write-Host " Done." -ForegroundColor Green + +# STEP 2: Download agent v4 +Write-Host "`n[2/6] Downloading agent v4..." -ForegroundColor Yellow +$agentPath = "$INSTALL_DIR\wevia-agent-v4.ps1" +try { + Invoke-WebRequest -Uri "$BASE/api/blade-tasks/wevia-agent-v4.ps1" -OutFile $agentPath -UseBasicParsing + Write-Host " Downloaded: $agentPath ($((Get-Item $agentPath).Length) bytes)" -ForegroundColor Green +} catch { + Write-Host " FAILED download: $_" -ForegroundColor Red + exit 1 +} + +# STEP 3: Register as scheduled task (runs at logon + every 5 min watchdog check) +Write-Host "`n[3/6] Registering scheduled task (auto-start at logon + 5min watchdog)..." -ForegroundColor Yellow + +$action = New-ScheduledTaskAction -Execute "powershell.exe" -Argument "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$agentPath`"" +$trigger1 = New-ScheduledTaskTrigger -AtLogOn +$trigger2 = New-ScheduledTaskTrigger -Once -At (Get-Date).AddMinutes(1) -RepetitionInterval (New-TimeSpan -Minutes 5) +$settings = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -StartWhenAvailable -RestartCount 99 -RestartInterval (New-TimeSpan -Minutes 1) +$principal = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType Interactive -RunLevel Highest + +Register-ScheduledTask -TaskName "WEVIA-Agent-v4" -Action $action -Trigger @($trigger1, $trigger2) -Settings $settings -Principal $principal -Force | Out-Null +Write-Host " Scheduled task registered (AtLogon + 5min watchdog)." -ForegroundColor Green + +# STEP 4: Create watchdog script that relaunches agent if dead +Write-Host "`n[4/6] Installing watchdog..." -ForegroundColor Yellow +$watchdogPath = "$INSTALL_DIR\watchdog.ps1" +$watchdogScript = @' +# Relaunches agent if not heartbeating +$ErrorActionPreference = "Continue" +$wdFile = "C:\ProgramData\WEVAL\agent-v4.watchdog" +$agentPath = "C:\ProgramData\WEVAL\wevia-agent-v4.ps1" + +if (Test-Path $wdFile) { + $lastTicks = [int64](Get-Content $wdFile -Raw).Trim() + $lastBeat = [DateTime]::new($lastTicks) + $delta = ((Get-Date) - $lastBeat).TotalSeconds + if ($delta -lt 120) { + # Agent alive, nothing to do + exit 0 + } +} + +# Agent dead or never started - launch it +$existing = Get-Process powershell -ErrorAction SilentlyContinue | Where-Object { + try { $_.CommandLine -match "wevia-agent-v4" } catch { $false } +} +if (!$existing) { + Start-Process powershell -ArgumentList "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$agentPath`"" -WindowStyle Hidden + Add-Content -Path "C:\ProgramData\WEVAL\watchdog.log" -Value "[$(Get-Date -Format 'o')] Relaunched agent" +} +'@ +Set-Content -Path $watchdogPath -Value $watchdogScript -Force +Write-Host " Watchdog installed: $watchdogPath" -ForegroundColor Green + +# STEP 5: Start the agent immediately +Write-Host "`n[5/6] Starting agent v4 NOW..." -ForegroundColor Yellow +Start-ScheduledTask -TaskName "WEVIA-Agent-v4" -ErrorAction SilentlyContinue +Start-Sleep -Seconds 3 +$running = Get-Process powershell -ErrorAction SilentlyContinue | Where-Object { + try { $_.CommandLine -match "wevia-agent-v4" } catch { $false } +} +if ($running) { + Write-Host " Agent running (PID=$($running.Id))." -ForegroundColor Green +} else { + Write-Host " Agent did not start via task, launching direct..." -ForegroundColor Yellow + Start-Process powershell -ArgumentList "-ExecutionPolicy Bypass -WindowStyle Hidden -File `"$agentPath`"" -WindowStyle Hidden +} + +# STEP 6: Verify heartbeat reaches server +Write-Host "`n[6/6] Verifying heartbeat to server..." -ForegroundColor Yellow +Start-Sleep -Seconds 10 +try { + $status = Invoke-RestMethod -Uri "$BASE/api/blade-api.php?k=BLADE2026&action=status" -TimeoutSec 10 + $reportedVersion = $status.blade.heartbeat.agent_version + if ($reportedVersion -eq "4.0") { + Write-Host " SUCCESS: Server reports agent_version=4.0" -ForegroundColor Green + } else { + Write-Host " WARNING: Server reports agent_version=$reportedVersion (expected 4.0)" -ForegroundColor Yellow + Write-Host " Wait 30s and re-run status check" -ForegroundColor Yellow + } +} catch { + Write-Host " Status check failed: $_" -ForegroundColor Red +} + +Write-Host "`n============================================================" -ForegroundColor Cyan +Write-Host " INSTALL COMPLETE" -ForegroundColor Cyan +Write-Host " Logs: $INSTALL_DIR\agent-v4.log" -ForegroundColor Gray +Write-Host " Watchdog: runs every 5 min via scheduler" -ForegroundColor Gray +Write-Host " Agent restarts automatically on logoff/reboot" -ForegroundColor Gray +Write-Host "============================================================" -ForegroundColor Cyan