feat: alive UI wave, galaxy presence, spread and fleet enhancements
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled

Dashboard ambient layer, comrade presence, Mission Deck and War Room, Emberwake supply chain, spread/docs publishing, fleet policy and modules API, CI docker mining, and refreshed USB pack.
This commit is contained in:
AetherForge
2026-06-04 22:36:17 -07:00
parent 1551bd5dad
commit a32860b0d9
154 changed files with 17383 additions and 601 deletions

View File

@@ -0,0 +1,128 @@
# 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
}

123
scripts/ci-docker-mining.sh Normal file
View File

@@ -0,0 +1,123 @@
#!/usr/bin/env bash
# CI Docker mining proof — Linux agent connects and reports hashrate > 0.
# Uses docker/data test wallet + fleet secret (see docker/README.md).
set -euo pipefail
ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
COMPOSE_FILE="${ROOT}/docker/docker-compose.yml"
BASE_URL="${AETHERFORGE_DOCKER_BASE_URL:-http://127.0.0.1:18989}"
E2E_USER="${AETHERFORGE_E2E_USER:-testuser}"
E2E_PASS="${AETHERFORGE_E2E_PASS:-testpass}"
EXPECTED_WORKER="${AETHERFORGE_DOCKER_WORKER:-docker-e2e-linux}"
WAIT_SECONDS="${AETHERFORGE_DOCKER_WAIT_SEC:-180}"
POLL_INTERVAL="${AETHERFORGE_DOCKER_POLL_SEC:-5}"
AUTH_HEADER="Authorization: Basic $(printf '%s:%s' "$E2E_USER" "$E2E_PASS" | base64 | tr -d '\n')"
DEADLINE=0
log() { printf '[ci-docker-mining] %s\n' "$*"; }
fail() { log "ERROR: $*"; exit 1; }
require_docker() {
if ! command -v docker >/dev/null 2>&1; then
fail "docker not found — install Docker Engine 24+ or run on a GHA ubuntu-latest runner"
fi
if ! docker compose version >/dev/null 2>&1; then
fail "docker compose plugin not found"
fi
}
teardown() {
local code=$?
log "Tearing down compose (exit=$code)…"
docker compose -f "$COMPOSE_FILE" down --rmi local -v --remove-orphans 2>/dev/null || true
if [[ $code -ne 0 ]]; then
log "Recent server logs:"
docker compose -f "$COMPOSE_FILE" logs --tail=80 server 2>/dev/null || true
log "Recent agent logs:"
docker compose -f "$COMPOSE_FILE" logs --tail=80 agent 2>/dev/null || true
fi
exit "$code"
}
wait_for_health() {
while (( SECONDS < DEADLINE )); do
if curl -sf "${BASE_URL}/api/v1/health" | grep -q '"status"[[:space:]]*:[[:space:]]*"ok"'; then
log "Server healthy at ${BASE_URL}/api/v1/health"
return 0
fi
sleep "$POLL_INTERVAL"
done
fail "server not healthy within ${WAIT_SECONDS}s (${BASE_URL}/api/v1/health)"
}
assert_mining() {
local agents_json stats_json
while (( SECONDS < DEADLINE )); do
agents_json="$(curl -sf -H "$AUTH_HEADER" "${BASE_URL}/api/v1/agents" || true)"
stats_json="$(curl -sf -H "$AUTH_HEADER" "${BASE_URL}/api/v1/dashboard/stats" || true)"
if [[ -n "$agents_json" && -n "$stats_json" ]]; then
local online_hr
online_hr="$(python3 - <<'PY' "$agents_json" "$EXPECTED_WORKER"
import json, sys
agents = json.loads(sys.argv[1])
worker = sys.argv[2]
hits = []
for a in agents:
if a.get("status") != "online":
continue
hr = max(
float(a.get("hashrate_15s") or 0),
float(a.get("hashrate_1m") or 0),
float(a.get("hashrate_15m") or 0),
)
if hr > 0:
hits.append((a.get("id"), a.get("worker_name") or a.get("name"), hr))
if worker:
named = [h for h in hits if h[1] == worker]
if named:
print(f"{named[0][0]}|{named[0][1]}|{named[0][2]:.2f}")
sys.exit(0)
if hits:
print(f"{hits[0][0]}|{hits[0][1]}|{hits[0][2]:.2f}")
PY
)" || true)"
if [[ -n "$online_hr" ]]; then
IFS='|' read -r agent_id worker_name hashrate <<<"$online_hr"
log "PASS: online agent ${agent_id} (${worker_name}) hashrate=${hashrate} H/s"
log "Fleet stats: $(printf '%s' "$stats_json" | python3 -c 'import json,sys; s=json.load(sys.stdin); print(f"online={s.get(\"online_agents\",0)} total_hr={s.get(\"total_hashrate\",0):.2f}")')"
return 0
fi
local online_count total_hr
online_count="$(printf '%s' "$stats_json" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("online_agents",0))')"
total_hr="$(printf '%s' "$stats_json" | python3 -c 'import json,sys; print(json.load(sys.stdin).get("total_hashrate",0))')"
log "Waiting… online_agents=${online_count} total_hashrate=${total_hr} (need online + hashrate>0)"
else
log "Waiting… agents/stats API not ready"
fi
sleep "$POLL_INTERVAL"
done
fail "no online agent with hashrate>0 within ${WAIT_SECONDS}s (expected worker: ${EXPECTED_WORKER})"
}
main() {
require_docker
trap teardown EXIT
cd "$ROOT"
log "Starting docker compose (build may take several minutes)"
docker compose -f "$COMPOSE_FILE" up --build -d
DEADLINE=$((SECONDS + WAIT_SECONDS))
log "Waiting up to ${WAIT_SECONDS}s for health + mining proof…"
wait_for_health
assert_mining
log "Docker mining proof succeeded"
}
main "$@"

View File

@@ -119,7 +119,7 @@ function Invoke-SmokeIfServerUp {
# and Basic auth credentials that match that server's users.json (defaults: testuser/testpass).
if (-not (Test-ServerHealthy)) { return }
Write-Banner "Tier 1b - API smoke (B-01 to B-10)"
& (Join-Path $Root "scripts\smoke-test.ps1") -BaseUrl $BaseUrl -Username $E2EUser -Password $E2EPass
& (Join-Path $Root "scripts\smoke-test.ps1") -BaseUrl $BaseUrl -Username $E2EUser -Password $E2EPass -DataDir $DataDir
if ($LASTEXITCODE -and $LASTEXITCODE -ne 0) { throw "smoke-test failed" }
}

View File

@@ -2,12 +2,23 @@
param(
[string]$BaseUrl = "http://localhost:8989",
[string]$Username,
[string]$Password
[string]$Password,
[string]$FleetSecret,
[string]$DataDir
)
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" } }
if (-not $FleetSecret) { $FleetSecret = $env:AETHERFORGE_FLEET_SECRET }
if (-not $FleetSecret -and $DataDir) {
$cfgPath = Join-Path $DataDir "config.json"
if (Test-Path $cfgPath) {
$cfg = Get-Content $cfgPath -Raw | ConvertFrom-Json
if ($cfg.server.fleet_secret) { $FleetSecret = $cfg.server.fleet_secret }
}
}
$ErrorActionPreference = "Stop"
$passed = 0
$failed = 0
@@ -17,6 +28,28 @@ $results = @()
$cred = [Convert]::ToBase64String([Text.Encoding]::ASCII.GetBytes("${Username}:${Password}"))
$authHeaders = @{ Authorization = "Basic $cred" }
function Invoke-AgentRestMethod {
param(
[string]$Uri,
[string]$Method = "Post",
[string]$Body,
[string]$ContentType = "application/json"
)
if (-not $FleetSecret) { throw "fleet secret required for agent API; pass -FleetSecret, -DataDir, or set AETHERFORGE_FLEET_SECRET" }
$headers = @{ "X-Fleet-Secret" = $FleetSecret }
$params = @{
Uri = $Uri
Method = $Method
Headers = $headers
}
if ($Body) {
$params.Body = $Body
$params.ContentType = $ContentType
}
return Invoke-RestMethod @params
}
function Invoke-AuthRestMethod {
param(
[string]$Uri,
@@ -109,15 +142,18 @@ Invoke-SmokeTest "B-07" "POST /agent/decide" {
shares_bad = 0
} | ConvertTo-Json -Depth 5
try {
Invoke-RestMethod "$BaseUrl/api/v1/agent/decide" -Method Post -Body $body -ContentType "application/json" | Out-Null
Invoke-AgentRestMethod "$BaseUrl/api/v1/agent/decide" -Body $body | Out-Null
} catch {
if ($_.Exception.Response.StatusCode.value__ -ge 500) { throw $_ }
$code = $_.Exception.Response.StatusCode.value__
if ($code -eq 403) { return }
if ($code -ge 500) { throw $_ }
throw $_
}
}
Invoke-SmokeTest "B-08" "POST /agent/report array" {
$body = '[{"agent_id":"smoke-agent","tool":"sleep","success":true,"output":"ok"}]'
$r = Invoke-RestMethod "$BaseUrl/api/v1/agent/report" -Method Post -Body $body -ContentType "application/json"
$r = Invoke-AgentRestMethod "$BaseUrl/api/v1/agent/report" -Body $body
if (-not $r.success) { throw "report not accepted" }
}