param( [string]$AgentRoot = "C:\Wooo\Agent99", [string]$SourceRevision = "", [switch]$InstallScheduledTasks ) $ErrorActionPreference = "Stop" $SourceRoot = Split-Path -Parent $MyInvocation.MyCommand.Path $BinDir = Join-Path $AgentRoot "bin" $ConfigDir = Join-Path $AgentRoot "config" $EvidenceDir = Join-Path $AgentRoot "evidence" $LogDir = Join-Path $AgentRoot "logs" $QueueDir = Join-Path $AgentRoot "queue" $RequestDir = Join-Path $AgentRoot "requests" $AlertDir = Join-Path $AgentRoot "alerts" $PlaybookDir = Join-Path $AgentRoot "playbooks" $DashboardDir = Join-Path $AgentRoot "dashboard" $StateDir = Join-Path $AgentRoot "state" foreach ($dir in @($AgentRoot, $BinDir, $ConfigDir, $EvidenceDir, $LogDir, $QueueDir, $RequestDir, $AlertDir, $PlaybookDir, $DashboardDir, $StateDir)) { New-Item -ItemType Directory -Force -Path $dir | Out-Null } function Copy-AgentFile { param([string]$Name) $src = Join-Path $SourceRoot $Name $dst = Join-Path $BinDir $Name if (Test-Path $src) { $srcFull = [System.IO.Path]::GetFullPath($src) $dstFull = [System.IO.Path]::GetFullPath($dst) if ($srcFull -ne $dstFull) { if ([System.IO.Path]::GetExtension($src).ToLowerInvariant() -eq ".ps1") { $content = [System.IO.File]::ReadAllText($src) $utf8Bom = [System.Text.UTF8Encoding]::new($true) [System.IO.File]::WriteAllText($dst, $content, $utf8Bom) } else { Copy-Item -Force $src $dst } } } } $agentFiles = @( "agent99-bootstrap.ps1", "agent99-contract-check.ps1", "agent99-control-plane.ps1", "agent99-deploy.ps1", "agent99-register-tasks.ps1", "agent99-sre-alert-inbox.ps1", "agent99-sre-alert-relay.ps1", "agent99-submit-request.ps1", "agent99-synthetic-tests.ps1", "agent99-telegram-inbox.ps1", "agent99-windows-update-policy.ps1", "host-reboot-scorecard.ps1", "agent99.config.example.json", "agent99.config.99.example.json" ) foreach ($agentFile in $agentFiles) { Copy-AgentFile $agentFile } $resolvedSourceRevision = $SourceRevision if (-not $resolvedSourceRevision) { try { $gitRevision = & git -C $SourceRoot rev-parse HEAD 2>$null if ($LASTEXITCODE -eq 0 -and $gitRevision) { $resolvedSourceRevision = ([string]$gitRevision).Trim() } } catch { $resolvedSourceRevision = "" } } if (-not $resolvedSourceRevision) { $resolvedSourceRevision = "unknown" } $manifestRows = @() foreach ($agentFile in $agentFiles) { $sourcePath = Join-Path $SourceRoot $agentFile $runtimePath = Join-Path $BinDir $agentFile $sourceExists = Test-Path $sourcePath $runtimeExists = Test-Path $runtimePath $sourceSha256 = if ($sourceExists) { (Get-FileHash -Algorithm SHA256 -LiteralPath $sourcePath).Hash.ToLowerInvariant() } else { $null } $runtimeSha256 = if ($runtimeExists) { (Get-FileHash -Algorithm SHA256 -LiteralPath $runtimePath).Hash.ToLowerInvariant() } else { $null } $contentMatched = if ($sourceExists -and $runtimeExists) { [System.IO.File]::ReadAllText($sourcePath) -ceq [System.IO.File]::ReadAllText($runtimePath) } else { $false } $manifestRows += [pscustomobject]@{ name = $agentFile sourceExists = $sourceExists runtimeExists = $runtimeExists sourceSha256 = $sourceSha256 runtimeSha256 = $runtimeSha256 contentMatched = [bool]$contentMatched matched = [bool]$contentMatched } } $runtimeMatched = [bool](@($manifestRows).Count -eq @($agentFiles).Count -and @($manifestRows | Where-Object { -not $_.matched }).Count -eq 0) $runtimeManifest = [pscustomobject]@{ schemaVersion = "agent99_runtime_manifest_v1" generatedAt = (Get-Date -Format o) sourceRevision = $resolvedSourceRevision runtimeMatched = $runtimeMatched fileCount = @($manifestRows).Count mismatchCount = @($manifestRows | Where-Object { -not $_.matched }).Count files = $manifestRows } $manifestPath = Join-Path $StateDir "runtime-manifest.json" $manifestEvidencePath = Join-Path $EvidenceDir ("agent99-RuntimeManifest-" + (Get-Date -Format "yyyyMMdd-HHmmss") + ".json") $runtimeManifest | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $manifestPath $runtimeManifest | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $manifestEvidencePath if (-not $runtimeMatched) { throw "Agent99 runtime manifest mismatch; inspect $manifestEvidencePath" } $exampleConfig = Join-Path $SourceRoot "agent99.config.example.json" $configPath = Join-Path $ConfigDir "agent99.config.json" if (-not (Test-Path $configPath)) { if (Test-Path $exampleConfig) { Copy-Item $exampleConfig $configPath } else { @" { "agentName": "Agent99", "agentRoot": "$($AgentRoot.Replace('\', '\\'))", "sshUser": "wooo", "sshUsers": {}, "vmrunPath": "", "hosts": ["192.168.0.110", "192.168.0.112", "192.168.0.120", "192.168.0.121", "192.168.0.188"], "vms": [], "publicUrls": ["awoooi.wooo.work", "stock.wooo.work", "2026fifa.wooo.work", "gitea.wooo.work", "harbor.wooo.work"], "sreAlertRelay": { "enabled": true, "prefix": "http://+:8787/agent99/sre-alert/", "tokenEnv": "AGENT99_SRE_RELAY_TOKEN", "allowedRemotePrefixes": ["127.", "::1", "192.168.0."] }, "telegram": { "enabled": true, "botTokenEnv": "AGENT99_TELEGRAM_BOT_TOKEN", "chatIdEnv": "AGENT99_TELEGRAM_CHAT_ID", "botTokenEnvAliases": ["AWOOOI_TG_BOT_TOKEN", "OPENCLAW_TG_BOT_TOKEN", "TELEGRAM_BOT_TOKEN", "TELEGRAM_TOKEN", "TG_BOT_TOKEN", "AIDER_WATCH_TELEGRAM_TOKEN"], "chatIdEnvAliases": ["TELEGRAM_CHAT_ID", "TELEGRAM_ALERT_CHAT_ID", "TELEGRAM_P0_CHAT_ID", "TELEGRAM_SECURITY_CHAT_ID", "OPENCLAW_TG_CHAT_ID", "TG_CHAT_ID", "AIDER_WATCH_TELEGRAM_CHAT_ID"], "relay": { "enabled": true, "host": "192.168.0.110", "container": "stockplatform-v2-api-1" }, "alertAllOperations": false, "alertInfoEvents": false, "alertOperatorCommands": true, "alertSreCommandResults": true, "dedupeMinutes": 30 }, "selfHealth": { "scheduledTasks": [ "Wooo-Agent99-Startup-Recovery", "Wooo-Agent99-Heartbeat", "Wooo-Agent99-Performance-Guard", "Wooo-Agent99-Control-Loop", "Wooo-Agent99-Telegram-Inbox", "Wooo-Agent99-SRE-Alert-Inbox", "Wooo-Agent99-SRE-Alert-Relay", "Wooo-Agent99-Self-Health", "Wooo-Agent99-Backup-Health" ] }, "performance": { "enabled": true, "intervalMinutes": 1, "loadPerCoreWarning": 0.9, "loadPerCoreCritical": 1.5, "memoryAvailablePercentCritical": 10, "diskUsedPercentWarning": 90, "diskUsedPercentCritical": 95, "captureTopCpuOnWarning": true, "topCpuTimeoutSeconds": 10, "loadShedding": { "enabled": true, "actions": [] } }, "backupHealth": { "host": "192.168.0.110", "targets": [], "fileChecks": [], "cronPatterns": [] } } "@ | Set-Content -Encoding UTF8 $configPath } } $launcher = Join-Path $AgentRoot "agent99-run.ps1" @" param( [ValidateSet("Status", "Recover", "StartVMs", "PublicSmoke", "HarborRepair", "AwoooRepair", "Perf", "LoadShed", "SelfCheck", "BackupCheck", "ProviderFreshness", "SecurityTriage", "ControlTick")] [string]`$Mode = "Status", [switch]`$ControlledApply, [switch]`$SuppressAlerts, [string]`$SuppressReason = "" ) & "$BinDir\agent99-control-plane.ps1" -Mode `$Mode -ControlledApply:`$ControlledApply -SuppressAlerts:`$SuppressAlerts -SuppressReason `$SuppressReason -ConfigPath "$configPath" -EvidenceDir "$EvidenceDir" "@ | Set-Content -Encoding UTF8 $launcher $submitCmd = Join-Path $AgentRoot "agent99-submit-request.cmd" @" @echo off powershell.exe -NoProfile -ExecutionPolicy Bypass -File "$BinDir\agent99-submit-request.ps1" -RunNow pause "@ | Set-Content -Encoding ASCII $submitCmd function Find-Vmrun { $candidates = @( "C:\Program Files (x86)\VMware\VMware Workstation\vmrun.exe", "C:\Program Files\VMware\VMware Workstation\vmrun.exe" ) foreach ($candidate in $candidates) { if (Test-Path $candidate) { return $candidate } } return "" } $vmrun = Find-Vmrun if ($vmrun) { $config = Get-Content $configPath -Raw | ConvertFrom-Json $config.vmrunPath = $vmrun $config | ConvertTo-Json -Depth 8 | Set-Content -Encoding UTF8 $configPath } if ($InstallScheduledTasks) { & (Join-Path $BinDir "agent99-register-tasks.ps1") -AgentRoot $AgentRoot } Write-Host "Agent99 bootstrap complete" Write-Host "AgentRoot: $AgentRoot" Write-Host "Config: $configPath" Write-Host "Evidence: $EvidenceDir" Write-Host "Runtime manifest: $manifestPath" Write-Host "Source revision: $resolvedSourceRevision" if ($vmrun) { Write-Host "vmrun: $vmrun" } else { Write-Host "vmrun: not detected; update agent99.config.json" }