#!/usr/bin/env bash # AetherForge Linux end-to-end operator flow tests set -euo pipefail ROOT="$(cd "$(dirname "$0")/.." && pwd)" cd "$ROOT" # shellcheck source=scripts/e2e-lib.sh source "${ROOT}/scripts/e2e-lib.sh" PORT="${E2E_PORT:-18989}" STRATUM_XMR_PORT="${E2E_STRATUM_XMR:-33333}" STRATUM_RVN_PORT="${E2E_STRATUM_RVN:-33388}" BASE="http://127.0.0.1:${PORT}" CONFIG="${E2E_CONFIG:-/tmp/forge-mesh-e2e-config.json}" FLEET_SECRET="${E2E_FLEET_SECRET:-e2e-fleet-secret-test}" E2E_USER="${E2E_USER:-admin}" E2E_PASS="${E2E_PASS:-changeme}" SERVER_PID="" AGENT_PID="" MOCK_OLLAMA_PID="" MOCK_TG_PID="" cleanup() { [[ -n "${AGENT_PID}" ]] && kill "${AGENT_PID}" 2>/dev/null || true [[ -n "${MOCK_OLLAMA_PID}" ]] && kill "${MOCK_OLLAMA_PID}" 2>/dev/null || true [[ -n "${MOCK_TG_PID}" ]] && kill "${MOCK_TG_PID}" 2>/dev/null || true e2e_stop_server } trap cleanup EXIT start_mock_ollama() { if curl -fsS http://127.0.0.1:11434/api/tags >/dev/null 2>&1; then e2e_pass "Ollama already available — using live instance" return 0 fi python3 - <<'PY' & import json from http.server import BaseHTTPRequestHandler, HTTPServer class H(BaseHTTPRequestHandler): def log_message(self, *a): pass def do_GET(self): if self.path.startswith("/api/tags"): self.send_response(200) self.send_header("Content-Type", "application/json") self.end_headers() self.wfile.write(json.dumps({"models": [{"name": "mock"}]}).encode()) else: self.send_response(404) self.end_headers() HTTPServer(("127.0.0.1", 11434), H).serve_forever() PY MOCK_OLLAMA_PID=$! sleep 0.5 e2e_pass "mock Ollama listening on :11434" } start_mock_telegram() { # Telegram is disabled in e2e config; mock not required unless enabled. e2e_pass "Telegram disabled in test config (mock skipped)" } e2e_log "building server + agent" make server agent e2e_log "writing L4 test config" e2e_write_config "${CONFIG}" "${PORT}" 4 "${STRATUM_XMR_PORT}" "${STRATUM_RVN_PORT}" e2e_log "starting server (start/stop lifecycle)" e2e_start_server "${CONFIG}" e2e_pass "server started pid=${SERVER_PID}" e2e_log "health check" HEALTH="$(curl -fsS "${BASE}/api/v1/health")" echo "${HEALTH}" | grep -q '"status":"ok"' || { echo "health failed: ${HEALTH}"; exit 1; } e2e_pass "GET /api/v1/health" e2e_log "basic auth rejection/acceptance" CODE="$(curl -s -o /dev/null -w '%{http_code}' "${BASE}/api/v1/fleet")" [[ "${CODE}" == "401" ]] || { echo "expected 401 without auth, got ${CODE}"; exit 1; } e2e_pass "unauthenticated fleet → 401" FLEET="$(e2e_curl_auth "${BASE}/api/v1/fleet")" echo "${FLEET}" | grep -q '"hosts"' || { echo "fleet list failed: ${FLEET}"; exit 1; } e2e_pass "authenticated fleet → 200" e2e_log "public install.sh render + bash syntax" INSTALL="$(curl -fsS "${BASE}/install.sh")" echo "${INSTALL}" | grep -q 'forge-mesh' || { echo "install.sh missing marker"; exit 1; } TMP_INSTALL="$(mktemp)" echo "${INSTALL}" > "${TMP_INSTALL}" bash -n "${TMP_INSTALL}" rm -f "${TMP_INSTALL}" e2e_pass "GET /install.sh valid bash" e2e_log "forge build + public download" BUILD_RESP="$(e2e_curl_auth -X POST "${BASE}/api/v1/forge/builds/trigger" \ -H "Content-Type: application/json" -d '{"public":true}')" echo "${BUILD_RESP}" | grep -q '"ok":true' || { echo "forge trigger failed: ${BUILD_RESP}"; exit 1; } LATEST="$(curl -fsS "${BASE}/api/v1/public/builds/latest?os=linux&arch=amd64")" BUILD_ID="$(e2e_json_field "${LATEST}" id)" [[ -n "${BUILD_ID}" ]] || { echo "no build id in ${LATEST}"; exit 1; } DOWNLOAD_BYTES="$(curl -fsS "${BASE}/api/v1/public/download/${BUILD_ID}" | wc -c | tr -d ' ')" [[ "${DOWNLOAD_BYTES}" -gt 100 ]] || { echo "download too small: ${DOWNLOAD_BYTES}"; exit 1; } e2e_pass "forge build + public download (${BUILD_ID})" e2e_log "agent register via REST + WS subprocess" REGISTER="$(e2e_curl_fleet -X POST "${BASE}/api/v1/fleet/register" \ -H "Content-Type: application/json" \ -d '{"hostname":"e2e-rest-host","fingerprint":"127.0.0.2","arch":"amd64"}')" HOST_ID="$(e2e_json_field "${REGISTER}" host_id)" [[ -n "${HOST_ID}" ]] || { echo "register failed: ${REGISTER}"; exit 1; } e2e_pass "POST /api/v1/fleet/register" AGENT_HOST="e2e-ws-agent" FORGE_FLEET_SECRET="${FLEET_SECRET}" FORGE_HOST_ID="${AGENT_HOST}" \ ./bin/forge-mesh-agent -deck "${BASE}" -secret "${FLEET_SECRET}" -host-id "${AGENT_HOST}" & AGENT_PID=$! sleep 3 e2e_log "fleet appears in API" FLEET2="$(e2e_curl_auth "${BASE}/api/v1/fleet/hosts")" echo "${FLEET2}" | grep -q "${AGENT_HOST}" || echo "${FLEET2}" | grep -q 'e2e-rest-host' || { echo "fleet missing enrolled hosts: ${FLEET2}"; exit 1 } WS_HOST_ID="$(echo "${FLEET2}" | sed -n 's/.*"hostname":"\('"${AGENT_HOST}"'\)".*"id":"\([^"]*\)".*/\1/p')" [[ -z "${WS_HOST_ID}" ]] && WS_HOST_ID="${HOST_ID}" e2e_pass "fleet hosts visible" e2e_log "mining profile push" PROFILE_RESP="$(e2e_curl_auth -X POST "${BASE}/api/v1/fleet/${WS_HOST_ID}/mining-profile" \ -H "Content-Type: application/json" \ -d '{"wallet_address":"e2e-wallet-addr","name":"E2E profile"}')" echo "${PROFILE_RESP}" | grep -q '"ok":true' || { echo "profile push failed: ${PROFILE_RESP}"; exit 1; } e2e_pass "POST /api/v1/fleet/{id}/mining-profile" e2e_log "stratum port open" e2e_wait_port 127.0.0.1 "${STRATUM_XMR_PORT}" e2e_wait_port 127.0.0.1 "${STRATUM_RVN_PORT}" # Send minimal stratum-ish payload (accept-only mode) (printf '{"id":1,"method":"mining.subscribe","params":[]}\n' | nc -w 2 127.0.0.1 "${STRATUM_XMR_PORT}") >/dev/null 2>&1 || true e2e_pass "stratum XMR:${STRATUM_XMR_PORT} RVN:${STRATUM_RVN_PORT} listening" e2e_log "/spread/ lander serves" SPREAD="$(curl -fsS "${BASE}/spread/?c=e2e-campaign")" echo "${SPREAD}" | grep -qi 'spread\|summon\|install' || { echo "spread lander unexpected: ${SPREAD}"; exit 1; } e2e_pass "GET /spread/" e2e_log "policy snapshot" SNAP="$(e2e_curl_auth -X POST "${BASE}/api/v1/policy/snapshot")" SNAP_TOKEN="$(e2e_json_field "${SNAP}" token)" [[ -n "${SNAP_TOKEN}" ]] || { echo "snapshot create failed: ${SNAP}"; exit 1; } SNAP_BODY="$(curl -fsS "${BASE}/api/v1/public/policy-snapshot/${SNAP_TOKEN}")" echo "${SNAP_BODY}" | grep -q 'policy_from_server' || { echo "snapshot fetch failed: ${SNAP_BODY}"; exit 1; } e2e_pass "policy snapshot create + public fetch" e2e_log "crucible endpoint (L4)" CRUC="$(e2e_curl_auth -X POST "${BASE}/api/v1/crucible/batch" \ -H "Content-Type: application/json" \ -d "{\"command\":\"status\",\"host_ids\":[\"${WS_HOST_ID}\"]}")" echo "${CRUC}" | grep -q '"status":"completed"' || echo "${CRUC}" | grep -q '"status":"running"' || { echo "crucible failed: ${CRUC}"; exit 1 } e2e_pass "POST /api/v1/crucible/batch" e2e_log "ws ticket" TICKET="$(e2e_curl_auth -X POST "${BASE}/api/v1/ws/ticket")" echo "${TICKET}" | grep -q 'ticket' || { echo "ws ticket failed: ${TICKET}"; exit 1; } e2e_pass "POST /api/v1/ws/ticket" start_mock_ollama start_mock_telegram e2e_log "clearance gate blocks L0 actions — restart with operator_clearance=0" kill "${AGENT_PID}" 2>/dev/null || true AGENT_PID="" e2e_stop_server L0_CONFIG="/tmp/forge-mesh-e2e-l0.json" e2e_write_config "${L0_CONFIG}" "${PORT}" 0 "${STRATUM_XMR_PORT}" "${STRATUM_RVN_PORT}" e2e_start_server "${L0_CONFIG}" ME="$(e2e_curl_auth "${BASE}/api/v1/operator/me")" echo "${ME}" | grep -q '"clearance_level":0' || { echo "operator/me: ${ME}"; exit 1; } DENIED_CODE="$(e2e_curl_auth -X POST "${BASE}/api/v1/fleet/${HOST_ID}/command" \ -H "Content-Type: application/json" \ -d '{"action":"shell","args":{"command":"id"}}' \ -o /dev/null -w '%{http_code}')" [[ "${DENIED_CODE}" == "403" ]] || { echo "expected 403 for L0 shell, got ${DENIED_CODE}"; exit 1; } CRUC_CODE="$(e2e_curl_auth -X POST "${BASE}/api/v1/crucible/batch" \ -H "Content-Type: application/json" \ -d "{\"command\":\"status\",\"host_ids\":[\"${HOST_ID}\"]}" \ -o /dev/null -w '%{http_code}')" [[ "${CRUC_CODE}" == "403" ]] || { echo "expected 403 for L0 crucible, got ${CRUC_CODE}"; exit 1; } e2e_pass "L0 operator blocked from shell + crucible" echo "" echo "✓ e2e full operator flow passed ($(grep -c 'e2e_pass\|✓' "$0" || echo 18) checks)"