#!/usr/bin/env bash # test-integration-full.sh — Full federation + sharing + DWN integration test # # Tests the complete feature set on the primary server: # 1. Federation peer connectivity # 2. Content sharing (add, catalog, access control) # 3. DWN message write + query # 4. DWN sync trigger # 5. Health monitor (container crash + restart detection) # 6. Tor rotation (already tested separately, just verify endpoint) # 7. NIP-07 signing (server-side) # # Usage: ./scripts/test-integration-full.sh [target-ip] set -uo pipefail TARGET="${1:-192.168.1.228}" SSH_KEY="${ARCHIPELAGO_SSH_KEY:-$HOME/.ssh/archipelago-deploy}" SSH="ssh -i $SSH_KEY -o StrictHostKeyChecking=no -o ConnectTimeout=10 archipelago@$TARGET" PASS=0 FAIL=0 WARN=0 check() { local name="$1" local ok="$2" if [ "$ok" = "true" ]; then echo " ✅ $name" ((PASS++)) else echo " ❌ $name" ((FAIL++)) fi } warn() { echo " ⚠️ $1" ((WARN++)) } json_get() { python3 -c "import sys,json; d=json.load(sys.stdin); r=d.get('result',{}); print(r.get('$1','') if isinstance(r,dict) else '')" 2>/dev/null } json_err() { python3 -c "import sys,json; d=json.load(sys.stdin); e=d.get('error'); print(e.get('message','') if e else '')" 2>/dev/null } echo "🔗 Full Integration Test — $TARGET" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "" # Login echo "Authenticating..." $SSH "curl -s -c /tmp/cookiejar http://localhost:5678/rpc/v1 -H 'Content-Type: application/json' -d '{\"method\":\"auth.login\",\"params\":{\"password\":\"password123\"}}'" >/dev/null 2>&1 CSRF=$($SSH "grep csrf_token /tmp/cookiejar 2>/dev/null | awk '{print \$NF}'" 2>/dev/null) rpc() { local method="$1" local params="${2:-}" local body if [ -n "$params" ]; then body="{\"method\":\"$method\",\"params\":$params}" else body="{\"method\":\"$method\"}" fi $SSH "curl -s -b /tmp/cookiejar -H 'Content-Type: application/json' -H 'X-CSRF-Token: $CSRF' http://localhost:5678/rpc/v1 -d '$body'" 2>/dev/null } # ━━━━━━━━━━ 1. FEDERATION ━━━━━━━━━━ echo "" echo "1. Federation Peers" FED_RESP=$(rpc "federation.list-nodes") PEER_COUNT=$(echo "$FED_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',{}).get('nodes',[])))" 2>/dev/null) check "Federation peers exist ($PEER_COUNT peers)" "$([ "$PEER_COUNT" -ge 1 ] && echo true || echo false)" # Node DID DID_RESP=$(rpc "node.did") DID=$(echo "$DID_RESP" | json_get "did") check "Node DID valid" "$(echo "$DID" | grep -q '^did:key:z' && echo true || echo false)" # Nostr pubkey PK_RESP=$(rpc "node.nostr-pubkey") NPUB=$(echo "$PK_RESP" | json_get "nostr_npub") check "Nostr npub valid" "$(echo "$NPUB" | grep -q '^npub1' && echo true || echo false)" # Tor address TOR_RESP=$(rpc "node.tor-address") TOR_ADDR=$(echo "$TOR_RESP" | json_get "tor_address") check "Tor address valid" "$(echo "$TOR_ADDR" | grep -q '.onion$' && echo true || echo false)" # ━━━━━━━━━━ 2. CONTENT SHARING ━━━━━━━━━━ echo "" echo "2. Content Sharing" # Create a test file $SSH "echo 'Integration test content $(date)' | sudo tee /var/lib/archipelago/filebrowser/integration-test.txt > /dev/null" 2>/dev/null # Add to content catalog ADD_RESP=$(rpc "content.add" "{\"filename\":\"integration-test.txt\",\"title\":\"Integration Test\",\"description\":\"Automated test file\"}") ADD_ERR=$(echo "$ADD_RESP" | json_err) if [ -n "$ADD_ERR" ] && echo "$ADD_ERR" | grep -q "already exists"; then check "Content add (already exists, OK)" "true" else ADD_ID=$(echo "$ADD_RESP" | python3 -c "import sys,json; r=json.load(sys.stdin).get('result',{}); i=r.get('item',r); print(i.get('id',''))" 2>/dev/null) check "Content added to catalog" "$([ -n "$ADD_ID" ] && echo true || echo false)" fi # List catalog LIST_RESP=$(rpc "content.list-mine") ITEM_COUNT=$(echo "$LIST_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',{}).get('items',[])))" 2>/dev/null) check "Content catalog has items ($ITEM_COUNT)" "$([ "$ITEM_COUNT" -ge 1 ] && echo true || echo false)" # Set access to free (uses content ID) FIRST_ID=$(echo "$LIST_RESP" | python3 -c "import sys,json; items=json.load(sys.stdin).get('result',{}).get('items',[]); print(items[0].get('id','') if items else '')" 2>/dev/null) PRICE_RESP=$(rpc "content.set-pricing" "{\"id\":\"$FIRST_ID\",\"access\":\"free\"}") PRICE_ERR=$(echo "$PRICE_RESP" | json_err) check "Set access mode" "$([ -z "$PRICE_ERR" ] && echo true || echo false)" # Verify content is accessible via HTTP CONTENT_STATUS=$($SSH "curl -s -o /dev/null -w '%{http_code}' http://localhost/content" 2>/dev/null) check "Content catalog HTTP endpoint" "$([ "$CONTENT_STATUS" = "200" ] && echo true || echo false)" # ━━━━━━━━━━ 3. DWN MESSAGES ━━━━━━━━━━ echo "" echo "3. DWN Protocol & Messages" # DWN status DWN_RESP=$(rpc "dwn.status") DWN_ERR=$(echo "$DWN_RESP" | json_err) check "DWN status endpoint" "$([ -z "$DWN_ERR" ] && echo true || echo false)" # Register a test protocol PROTO_RESP=$(rpc "dwn.register-protocol" "{\"protocol\":\"https://archipelago.dev/protocols/integration-test\",\"published\":true}") PROTO_ERR=$(echo "$PROTO_RESP" | json_err) check "Register DWN protocol" "$([ -z "$PROTO_ERR" ] && echo true || echo false)" # Write a test message WRITE_RESP=$(rpc "dwn.write-message" "{\"author\":\"$DID\",\"protocol\":\"https://archipelago.dev/protocols/integration-test\",\"data\":{\"test\":true,\"timestamp\":$(date +%s)}}") RECORD_ID=$(echo "$WRITE_RESP" | json_get "record_id") check "Write DWN message" "$([ -n "$RECORD_ID" ] && echo true || echo false)" # Query messages QUERY_RESP=$(rpc "dwn.query-messages" "{\"protocol\":\"https://archipelago.dev/protocols/integration-test\"}") MSG_COUNT=$(echo "$QUERY_RESP" | json_get "count") check "Query DWN messages (count: $MSG_COUNT)" "$([ "$MSG_COUNT" -ge 1 ] && echo true || echo false)" # List protocols PROTOS_RESP=$(rpc "dwn.list-protocols") PROTO_COUNT=$(echo "$PROTOS_RESP" | python3 -c "import sys,json; print(len(json.load(sys.stdin).get('result',{}).get('protocols',[])))" 2>/dev/null) check "List DWN protocols ($PROTO_COUNT)" "$([ "$PROTO_COUNT" -ge 1 ] && echo true || echo false)" # ━━━━━━━━━━ 4. DWN SYNC ━━━━━━━━━━ echo "" echo "4. DWN Sync" SYNC_RESP=$(rpc "dwn.sync") SYNC_ERR=$(echo "$SYNC_RESP" | json_err) SYNC_STATUS=$(echo "$SYNC_RESP" | json_get "sync_status") # Sync may fail if peers are unreachable over Tor, but endpoint should work if [ -z "$SYNC_ERR" ]; then check "DWN sync executed ($SYNC_STATUS)" "true" else warn "DWN sync returned error: $SYNC_ERR (peers may be unreachable)" check "DWN sync endpoint exists" "true" fi # ━━━━━━━━━━ 5. HEALTH MONITOR ━━━━━━━━━━ echo "" echo "5. Health Monitor" # System stats STATS_RESP=$(rpc "system.stats") CPU=$(echo "$STATS_RESP" | json_get "cpu_usage_percent") check "System stats (CPU: ${CPU:-?}%)" "$([ -n "$CPU" ] && echo true || echo false)" # Container list CONTAINERS=$($SSH "sudo podman ps --format '{{.Names}}' 2>/dev/null | wc -l" 2>/dev/null | tr -d '[:space:]') check "Containers running ($CONTAINERS)" "$([ "$CONTAINERS" -ge 5 ] && echo true || echo false)" # Health endpoint HEALTH_STATUS=$($SSH "curl -s -o /dev/null -w '%{http_code}' http://localhost/health" 2>/dev/null) check "Health endpoint OK" "$([ "$HEALTH_STATUS" = "200" ] && echo true || echo false)" # Container crash + auto-restart test echo " Stopping filebrowser to test auto-restart..." $SSH "sudo podman stop filebrowser 2>/dev/null" >/dev/null 2>&1 sleep 5 # Check if health monitor detected + restarted (poll for up to 90s) RESTARTED="false" for i in $(seq 1 18); do FB_STATUS=$($SSH "sudo podman inspect filebrowser --format '{{.State.Status}}' 2>/dev/null" 2>/dev/null | tr -d '[:space:]') if [ "$FB_STATUS" = "running" ]; then RESTARTED="true" echo " Restarted after ~$((i * 5))s" break fi sleep 5 done check "Health monitor auto-restarted filebrowser" "$RESTARTED" # ━━━━━━━━━━ 6. TOR ROTATION ━━━━━━━━━━ echo "" echo "6. Tor Rotation (endpoint check only)" # Don't actually rotate again — just verify endpoint responds TOR_LIST_RESP=$(rpc "tor.list-services") TOR_LIST_ERR=$(echo "$TOR_LIST_RESP" | json_err) check "tor.list-services endpoint" "$([ -z "$TOR_LIST_ERR" ] && echo true || echo false)" CLEANUP_RESP=$(rpc "tor.cleanup-rotated") CLEANUP_ERR=$(echo "$CLEANUP_RESP" | json_err) check "tor.cleanup-rotated endpoint" "$([ -z "$CLEANUP_ERR" ] && echo true || echo false)" # ━━━━━━━━━━ 7. NIP-07 SIGNING ━━━━━━━━━━ echo "" echo "7. NIP-07 Signing" NODE_PK=$(echo "$PK_RESP" | json_get "nostr_pubkey") SIGN_RESP=$(rpc "node.nostr-sign" "{\"event\":{\"kind\":1,\"content\":\"integration test\",\"created_at\":$(date +%s),\"tags\":[]}}") SIGN_PK=$(echo "$SIGN_RESP" | json_get "pubkey") SIGN_SIG=$(echo "$SIGN_RESP" | json_get "sig") check "Event signed" "$([ ${#SIGN_SIG} -gt 60 ] && echo true || echo false)" check "Signing pubkey matches node key" "$([ "$SIGN_PK" = "$NODE_PK" ] && echo true || echo false)" # nostr-provider.js injection JS_OK=$($SSH "curl -s -o /dev/null -w '%{http_code}' http://localhost/nostr-provider.js" 2>/dev/null) check "nostr-provider.js served" "$([ "$JS_OK" = "200" ] && echo true || echo false)" # ━━━━━━━━━━ SUMMARY ━━━━━━━━━━ echo "" echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━" echo "Results: $PASS passed, $FAIL failed, $WARN warnings" echo "" if [ $FAIL -eq 0 ]; then echo "✅ All integration tests passed!" else echo "❌ $FAIL tests failed — review output above" fi [ $FAIL -eq 0 ] && exit 0 || exit 1