# CI Docker mining proof — Linux agent connects and reports hashrate > 0 (Windows + Docker Desktop). param( [string]$BaseUrl = "http://127.0.0.1:18989", [string]$Username = "", [string]$Password = "", [string]$ExpectedWorker = "docker-e2e-linux", [int]$WaitSeconds = 180, [int]$PollIntervalSec = 5, [switch]$SkipTeardown ) $ErrorActionPreference = "Stop" $Root = Split-Path -Parent (Split-Path -Parent $MyInvocation.MyCommand.Path) $ComposeFile = Join-Path $Root "docker\docker-compose.yml" if (-not $Username) { $Username = if ($env:AETHERFORGE_E2E_USER) { $env:AETHERFORGE_E2E_USER } else { "testuser" } } if (-not $Password) { $Password = if ($env:AETHERFORGE_E2E_PASS) { $env:AETHERFORGE_E2E_PASS } else { "testpass" } } $cred = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${Username}:${Password}")) $authHeaders = @{ Authorization = "Basic $cred" } function Write-Log([string]$Message) { Write-Host "[ci-docker-mining] $Message" } function Ensure-Docker { if (-not (Get-Command docker -ErrorAction SilentlyContinue)) { throw "docker not found — install Docker Desktop with compose v2" } docker compose version 2>$null | Out-Null if ($LASTEXITCODE -ne 0) { throw "docker compose plugin not available" } } function Invoke-Teardown { if ($SkipTeardown) { return } Write-Log "Tearing down compose…" Push-Location $Root try { docker compose -f $ComposeFile down --rmi local -v --remove-orphans 2>$null } finally { Pop-Location } } function Wait-ForHealth { param([datetime]$Deadline) while ((Get-Date) -lt $Deadline) { try { $r = Invoke-RestMethod "$BaseUrl/api/v1/health" -TimeoutSec 5 if ($r.status -eq "ok") { Write-Log "Server healthy at $BaseUrl/api/v1/health" return } } catch {} Start-Sleep -Seconds $PollIntervalSec } throw "server not healthy within ${WaitSeconds}s ($BaseUrl/api/v1/health)" } function Assert-Mining { param([datetime]$Deadline) while ((Get-Date) -lt $Deadline) { try { $agents = Invoke-RestMethod "$BaseUrl/api/v1/agents" -Headers $authHeaders -TimeoutSec 10 $stats = Invoke-RestMethod "$BaseUrl/api/v1/dashboard/stats" -Headers $authHeaders -TimeoutSec 10 $online = @($agents | Where-Object { $_.status -eq "online" -and ( [double]$_.hashrate_15s -gt 0 -or [double]$_.hashrate_1m -gt 0 -or [double]$_.hashrate_15m -gt 0 ) }) $named = @($online | Where-Object { $_.worker_name -eq $ExpectedWorker -or $_.name -eq $ExpectedWorker }) $hit = if ($named.Count -gt 0) { $named[0] } elseif ($online.Count -gt 0) { $online[0] } else { $null } if ($hit) { $hr = [Math]::Max([double]$hit.hashrate_15s, [Math]::Max([double]$hit.hashrate_1m, [double]$hit.hashrate_15m)) Write-Log "PASS: online agent $($hit.id) ($($hit.worker_name)) hashrate=$([math]::Round($hr, 2)) H/s" Write-Log "Fleet stats: online=$($stats.online_agents) total_hr=$([math]::Round([double]$stats.total_hashrate, 2))" return } Write-Log "Waiting… online_agents=$($stats.online_agents) total_hashrate=$($stats.total_hashrate) (need online + hashrate>0)" } catch { Write-Log "Waiting… agents/stats API not ready ($($_.Exception.Message))" } Start-Sleep -Seconds $PollIntervalSec } throw "no online agent with hashrate>0 within ${WaitSeconds}s (expected worker: $ExpectedWorker)" } function Show-FailureLogs { Push-Location $Root try { Write-Log "Recent server logs:" docker compose -f $ComposeFile logs --tail=80 server 2>$null Write-Log "Recent agent logs:" docker compose -f $ComposeFile logs --tail=80 agent 2>$null } finally { Pop-Location } } Ensure-Docker try { Push-Location $Root Write-Log "Starting docker compose (build may take several minutes)…" docker compose -f $ComposeFile up --build -d if ($LASTEXITCODE -ne 0) { throw "docker compose up failed (exit $LASTEXITCODE)" } Pop-Location $deadline = (Get-Date).AddSeconds($WaitSeconds) Write-Log "Waiting up to ${WaitSeconds}s for health + mining proof…" Wait-ForHealth -Deadline $deadline Assert-Mining -Deadline $deadline Write-Log "Docker mining proof succeeded" exit 0 } catch { Write-Host "[ci-docker-mining] ERROR: $($_.Exception.Message)" -ForegroundColor Red Show-FailureLogs exit 1 } finally { Invoke-Teardown }