#!/usr/bin/env bash # FINAL-201: Fresh Install End-to-End Test # Run on a freshly installed Archipelago node to verify the complete user journey. # Usage: scp this script to the node, then: bash test-fresh-install-e2e.sh set -euo pipefail NODE="${1:-localhost}" BASE="http://${NODE}" PASS="${2:-password123}" COOKIE_JAR="/tmp/e2e-cookies.txt" PASS_COUNT=0 FAIL_COUNT=0 SKIP_COUNT=0 green() { printf "\033[32m✓ %s\033[0m\n" "$1"; } red() { printf "\033[31m✗ %s\033[0m\n" "$1"; } yellow(){ printf "\033[33m⊘ %s\033[0m\n" "$1"; } header(){ printf "\n\033[1;36m━━━ %s ━━━\033[0m\n" "$1"; } pass() { PASS_COUNT=$((PASS_COUNT + 1)); green "$1"; } fail() { FAIL_COUNT=$((FAIL_COUNT + 1)); red "$1"; } skip() { SKIP_COUNT=$((SKIP_COUNT + 1)); yellow "$1 (skipped)"; } rpc() { local method="$1" local params="${2:-{}}" curl -s -b "$COOKIE_JAR" -c "$COOKIE_JAR" \ -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"$method\",\"params\":$params}" \ "${BASE}/rpc/" 2>/dev/null } # ─── Phase 1: Boot & Accessibility ─────────────────────────────── header "Phase 1: Boot & Accessibility" if curl -s -o /dev/null -w "%{http_code}" "${BASE}/health" | grep -q "200"; then pass "Backend health endpoint responds 200" else fail "Backend health endpoint not responding" fi HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/") if [ "$HTTP_CODE" = "200" ] || [ "$HTTP_CODE" = "302" ]; then pass "Web UI loads (HTTP $HTTP_CODE)" else fail "Web UI not loading (HTTP $HTTP_CODE)" fi if curl -s "${BASE}/" | grep -q "Archipelago"; then pass "Web UI contains Archipelago branding" else fail "Web UI missing Archipelago branding" fi # ─── Phase 2: Onboarding ───────────────────────────────────────── header "Phase 2: Onboarding & Authentication" # Check if onboarding is needed or already done LOGIN_RESP=$(curl -s -c "$COOKIE_JAR" -H "Content-Type: application/json" \ -d "{\"jsonrpc\":\"2.0\",\"id\":1,\"method\":\"auth.login\",\"params\":{\"password\":\"$PASS\"}}" \ "${BASE}/rpc/" 2>/dev/null) if echo "$LOGIN_RESP" | grep -q '"result"'; then pass "Authentication successful" else fail "Authentication failed: $LOGIN_RESP" fi # Verify session works SESSION_CHECK=$(rpc "system.info") if echo "$SESSION_CHECK" | grep -q '"result"'; then pass "Session is valid after login" else fail "Session invalid after login" fi # ─── Phase 3: Identity (DID) ───────────────────────────────────── header "Phase 3: Identity System" ID_LIST=$(rpc "identity.list") if echo "$ID_LIST" | grep -q '"result"'; then pass "identity.list RPC responds" ID_COUNT=$(echo "$ID_LIST" | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('result',{}).get('identities',[])))" 2>/dev/null || echo "0") if [ "$ID_COUNT" -gt "0" ]; then pass "At least one identity exists ($ID_COUNT found)" else # Create one CREATE_ID=$(rpc "identity.create" '{"name":"Test Identity","purpose":"personal"}') if echo "$CREATE_ID" | grep -q '"result"'; then pass "Created test identity" else fail "Failed to create identity" fi fi else fail "identity.list RPC failed" fi # Test signing SIGN_RESP=$(rpc "identity.sign" '{"message":"test message"}') if echo "$SIGN_RESP" | grep -q '"result"'; then pass "Identity signing works" else skip "Identity signing" fi # Test Nostr key NOSTR_RESP=$(rpc "identity.create-nostr-key" '{}') if echo "$NOSTR_RESP" | grep -q '"result"' || echo "$NOSTR_RESP" | grep -q "already"; then pass "Nostr key generation works" else skip "Nostr key generation" fi # ─── Phase 4: App Installation ─────────────────────────────────── header "Phase 4: Core App Installation" check_app_status() { local app_id="$1" local resp resp=$(rpc "package.status" "{\"id\":\"$app_id\"}") echo "$resp" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r.get('result',{}).get('status','unknown'))" 2>/dev/null || echo "unknown" } install_app() { local app_id="$1" local timeout="${2:-120}" local status status=$(check_app_status "$app_id") if [ "$status" = "running" ]; then pass "$app_id already running" return 0 fi rpc "package.install" "{\"id\":\"$app_id\"}" > /dev/null 2>&1 local elapsed=0 while [ $elapsed -lt $timeout ]; do sleep 5 elapsed=$((elapsed + 5)) status=$(check_app_status "$app_id") if [ "$status" = "running" ]; then pass "$app_id installed and running (${elapsed}s)" return 0 fi done fail "$app_id failed to start within ${timeout}s (status: $status)" return 1 } # Install Bitcoin Knots (foundation) install_app "bitcoin-knots" 180 # Install LND (requires Bitcoin) install_app "lnd" 120 # Install Electrs (requires Bitcoin) install_app "electrs" 120 # ─── Phase 5: Lightning Channels ───────────────────────────────── header "Phase 5: Lightning (LND)" LND_INFO=$(rpc "lnd.getinfo") if echo "$LND_INFO" | grep -q '"result"'; then pass "LND getinfo responds" SYNCED=$(echo "$LND_INFO" | python3 -c "import sys,json; r=json.load(sys.stdin); print(r.get('result',{}).get('synced_to_chain',False))" 2>/dev/null) if [ "$SYNCED" = "True" ]; then pass "LND synced to chain" else skip "LND chain sync (may take time)" fi else fail "LND getinfo failed" fi # Test wallet address generation ADDR_RESP=$(rpc "lnd.newaddress") if echo "$ADDR_RESP" | grep -q '"result"'; then pass "LND new address generation works" else fail "LND new address generation failed" fi # Test invoice creation INV_RESP=$(rpc "lnd.createinvoice" '{"value":1000,"memo":"E2E test invoice"}') if echo "$INV_RESP" | grep -q '"result"'; then pass "LND invoice creation works" else fail "LND invoice creation failed" fi # ─── Phase 6: Content Sharing ──────────────────────────────────── header "Phase 6: Content & Sharing" CONTENT_LIST=$(rpc "content.list-mine") if echo "$CONTENT_LIST" | grep -q '"result"'; then pass "content.list-mine RPC responds" else skip "Content listing" fi # ─── Phase 7: Networking & Peers ───────────────────────────────── header "Phase 7: Networking" VIS_RESP=$(rpc "network.get-visibility") if echo "$VIS_RESP" | grep -q '"result"'; then pass "network.get-visibility RPC responds" else skip "Network visibility" fi DIAG_RESP=$(rpc "network.diagnostics") if echo "$DIAG_RESP" | grep -q '"result"'; then pass "network.diagnostics RPC responds" else skip "Network diagnostics" fi # ─── Phase 8: Tor Services ─────────────────────────────────────── header "Phase 8: Tor Services" TOR_RESP=$(rpc "tor.list-services") if echo "$TOR_RESP" | grep -q '"result"'; then pass "tor.list-services RPC responds" SVC_COUNT=$(echo "$TOR_RESP" | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('result',{}).get('services',[])))" 2>/dev/null || echo "0") if [ "$SVC_COUNT" -gt "0" ]; then pass "Tor hidden services configured ($SVC_COUNT)" else skip "No Tor services configured yet" fi else skip "Tor services" fi # ─── Phase 9: Easy Mode Goals ──────────────────────────────────── header "Phase 9: Easy Mode & Goals" # Check goal pages load for goal in open-a-shop accept-payments store-photos store-files run-lightning-node create-identity back-up-everything; do GOAL_CODE=$(curl -s -o /dev/null -w "%{http_code}" -b "$COOKIE_JAR" "${BASE}/dashboard/goals/${goal}") if [ "$GOAL_CODE" = "200" ]; then pass "Goal page loads: $goal" else skip "Goal page: $goal (HTTP $GOAL_CODE)" fi done # ─── Phase 10: AIUI Chat ───────────────────────────────────────── header "Phase 10: AIUI Chat" AIUI_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/aiui/") if [ "$AIUI_CODE" = "200" ]; then pass "AIUI loads" else skip "AIUI (HTTP $AIUI_CODE)" fi # ─── Phase 11: Multiple Identities ─────────────────────────────── header "Phase 11: Multi-Identity" CREATE_BIZ=$(rpc "identity.create" '{"name":"Business","purpose":"business"}') if echo "$CREATE_BIZ" | grep -q '"result"'; then pass "Created business identity" # Verify multiple identities exist ID_LIST2=$(rpc "identity.list") ID_COUNT2=$(echo "$ID_LIST2" | python3 -c "import sys,json; r=json.load(sys.stdin); print(len(r.get('result',{}).get('identities',[])))" 2>/dev/null || echo "0") if [ "$ID_COUNT2" -ge "2" ]; then pass "Multiple identities exist ($ID_COUNT2)" else fail "Expected 2+ identities, got $ID_COUNT2" fi else skip "Business identity creation" fi # ─── Phase 12: Update System ───────────────────────────────────── header "Phase 12: Update System" UPDATE_STATUS=$(rpc "update.status") if echo "$UPDATE_STATUS" | grep -q '"result"'; then pass "update.status RPC responds" else skip "Update status" fi UPDATE_CHECK=$(rpc "update.check") if echo "$UPDATE_CHECK" | grep -q '"result"' || echo "$UPDATE_CHECK" | grep -q "error"; then pass "update.check RPC responds" else skip "Update check" fi # ─── Phase 13: WebSocket ───────────────────────────────────────── header "Phase 13: WebSocket" WS_CHECK=$(curl -s -o /dev/null -w "%{http_code}" -H "Upgrade: websocket" -H "Connection: Upgrade" "${BASE}/ws/") if [ "$WS_CHECK" = "101" ] || [ "$WS_CHECK" = "400" ] || [ "$WS_CHECK" = "200" ]; then pass "WebSocket endpoint responds (HTTP $WS_CHECK)" else skip "WebSocket (HTTP $WS_CHECK)" fi # ─── Phase 14: UI Asset Verification ───────────────────────────── header "Phase 14: UI Assets" # Check main app JS loads ASSETS_CHECK=$(curl -s "${BASE}/" | grep -o 'src="[^"]*\.js"' | head -3) if [ -n "$ASSETS_CHECK" ]; then pass "JavaScript assets referenced in HTML" else # Vite uses different format ASSETS_CHECK=$(curl -s "${BASE}/" | grep -o 'assets/[^"]*\.js' | head -3) if [ -n "$ASSETS_CHECK" ]; then pass "Vite assets referenced in HTML" else skip "Asset check" fi fi # Check app icons exist for icon in bitcoin-knots lnd electrs; do ICON_CODE=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/assets/img/app-icons/${icon}.png") if [ "$ICON_CODE" = "200" ]; then pass "App icon loads: $icon" else ICON_CODE2=$(curl -s -o /dev/null -w "%{http_code}" "${BASE}/assets/img/app-icons/${icon}.webp") if [ "$ICON_CODE2" = "200" ]; then pass "App icon loads: $icon (.webp)" else skip "App icon: $icon" fi fi done # ─── Summary ───────────────────────────────────────────────────── header "RESULTS" echo "" printf "\033[32m Passed: %d\033[0m\n" "$PASS_COUNT" printf "\033[31m Failed: %d\033[0m\n" "$FAIL_COUNT" printf "\033[33m Skipped: %d\033[0m\n" "$SKIP_COUNT" echo "" TOTAL=$((PASS_COUNT + FAIL_COUNT + SKIP_COUNT)) if [ "$FAIL_COUNT" -eq 0 ]; then printf "\033[1;32m🎉 ALL %d TESTS PASSED (%d skipped)\033[0m\n" "$PASS_COUNT" "$SKIP_COUNT" exit 0 else printf "\033[1;31m⚠ %d/%d TESTS FAILED\033[0m\n" "$FAIL_COUNT" "$TOTAL" exit 1 fi