#!/usr/bin/env bash # # test-all-features.sh — Comprehensive single-node feature validation # # Usage: ./scripts/test-all-features.sh [TARGET_IP] [--iterations N] # # Runs all feature checks on a single node: # - System health (CPU, RAM, disk, services) # - Container lifecycle (running, count, health monitor) # - Federation (peers, trust, sync) # - Tor (hidden service reachable) # - DWN (status, write, query) # - NIP-07 (provider injection) # - Backup (create, list, verify, delete) # - Identity (DID, credentials) # - Monitoring (metrics endpoint) # # Exit 0 = all checks passed = production ready # Output: TAP format set -uo pipefail TARGET="${1:-192.168.1.228}" ITERATIONS=10 SSH_KEY="${HOME}/.ssh/archipelago-deploy" SSH_OPTS="-i ${SSH_KEY} -o StrictHostKeyChecking=no -o ConnectTimeout=10" SUDO_PASS="EwPDR8q45l0Upx@" PASS=0 FAIL=0 TEST_NUM=0 # Parse args shift || true while [[ $# -gt 0 ]]; do case "$1" in --iterations) ITERATIONS="$2"; shift 2 ;; *) echo "Unknown arg: $1"; exit 1 ;; esac done tap_ok() { TEST_NUM=$((TEST_NUM+1)); PASS=$((PASS+1)); echo "ok ${TEST_NUM} - $1"; } tap_fail() { TEST_NUM=$((TEST_NUM+1)); FAIL=$((FAIL+1)); echo "not ok ${TEST_NUM} - $1"; echo "# $2"; } ssh_cmd() { ssh ${SSH_OPTS} "archipelago@${TARGET}" "$@" 2>/dev/null; } ssh_sudo() { ssh ${SSH_OPTS} "archipelago@${TARGET}" "echo '${SUDO_PASS}' | sudo -S $*" 2>/dev/null; } get_session() { curl -s -D- -o/dev/null -X POST \ -H "Content-Type: application/json" \ -d '{"method":"auth.login","params":{"password":"password123"}}' \ "http://${TARGET}:5678/rpc/v1" 2>/dev/null | grep -i "set-cookie" | tr '\r' '\n' } rpc() { local method="$1" local params="${2:-\{\}}" local session="$3" local csrf="$4" local timeout="${5:-30}" local body if [[ "$params" == "{}" || "$params" == "\{\}" ]]; then body="{\"method\":\"${method}\"}" else body="{\"method\":\"${method}\",\"params\":${params}}" fi curl -s --max-time "$timeout" -X POST \ -H "Content-Type: application/json" \ -H "Cookie: session=${session}; csrf_token=${csrf}" \ -H "X-CSRF-Token: ${csrf}" \ -d "$body" \ "http://${TARGET}:5678/rpc/v1" 2>/dev/null } echo "TAP version 13" echo "# Archipelago Full Feature Validation" echo "# Target: ${TARGET}" echo "# Iterations: ${ITERATIONS}" echo "# Started: $(date -u +%Y-%m-%dT%H:%M:%SZ)" for i in $(seq 1 "$ITERATIONS"); do echo "" echo "# ── Iteration ${i}/${ITERATIONS} [$(date +%H:%M:%S)] ──" # Re-authenticate each iteration to handle backend restarts session_header=$(get_session) SESSION=$(echo "$session_header" | sed -n 's/.*session=\([^;]*\).*/\1/p' | head -1 | tr -d '[:space:]') CSRF=$(echo "$session_header" | sed -n 's/.*csrf_token=\([^;]*\).*/\1/p' | head -1 | tr -d '[:space:]') if [[ -z "$SESSION" ]]; then tap_fail "auth-${i}" "Cannot authenticate" continue fi # ── System Health ───────────────────────────────────────────────────── health=$(curl -s --max-time 5 "http://${TARGET}/health" 2>/dev/null || echo "fail") if [[ "$health" == "OK" ]]; then tap_ok "health-${i}"; else tap_fail "health-${i}" "$health"; fi mem_avail=$(ssh_cmd "awk '/MemAvailable/ {print \$2}' /proc/meminfo" 2>/dev/null | tr -d '[:space:]') if [[ -n "$mem_avail" ]] && [[ "$mem_avail" -gt 524288 ]]; then tap_ok "memory-${i} # ${mem_avail}kB" else tap_fail "memory-${i}" "Available: ${mem_avail:-unknown}kB" fi disk=$(ssh_cmd "df / --output=pcent | tail -1" 2>/dev/null | tr -d '[:space:]%') if [[ -n "$disk" ]] && [[ "$disk" -lt 85 ]]; then tap_ok "disk-${i} # ${disk}%" else tap_fail "disk-${i}" "${disk:-unknown}%" fi # ── Containers ──────────────────────────────────────────────────────── count=$(ssh_sudo "podman ps --format '{{.Names}}' | wc -l" 2>/dev/null | tail -1 | tr -d '[:space:]') if [[ -n "$count" ]] && [[ "$count" -ge 20 ]]; then tap_ok "containers-${i} # ${count}" else tap_fail "containers-${i}" "Only ${count:-0}" fi exited=$(ssh_sudo "podman ps -a --format '{{.State}}' | grep -c -i exited" 2>/dev/null || echo "0") exited=$(echo "$exited" | tail -1 | tr -d '[:space:]') if [[ "$exited" == "0" ]]; then tap_ok "no-exited-${i}" else tap_fail "no-exited-${i}" "${exited} exited" fi # ── Federation ──────────────────────────────────────────────────────── fed_result=$(rpc "federation.list-nodes" "{}" "$SESSION" "$CSRF") peer_count=$(echo "$fed_result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(len(d.get('result',{}).get('nodes',[])))" 2>/dev/null || echo "0") if [[ "$peer_count" -ge 1 ]]; then tap_ok "federation-peers-${i} # ${peer_count}" else tap_fail "federation-peers-${i}" "No peers" fi # ── DWN ─────────────────────────────────────────────────────────────── dwn_result=$(rpc "dwn.status" "{}" "$SESSION" "$CSRF") dwn_running=$(echo "$dwn_result" | python3 -c "import sys,json; d=json.load(sys.stdin); print('yes' if d.get('result',{}).get('running') else 'no')" 2>/dev/null || echo "error") if [[ "$dwn_running" == "yes" ]]; then tap_ok "dwn-running-${i}" else tap_fail "dwn-running-${i}" "DWN not running" fi # ── Identity ────────────────────────────────────────────────────────── did_result=$(rpc "node.did" "{}" "$SESSION" "$CSRF") did=$(echo "$did_result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('did',''))" 2>/dev/null || echo "") if [[ "$did" == did:* ]]; then tap_ok "identity-did-${i} # ${did:0:20}..." else tap_fail "identity-did-${i}" "No DID: ${did}" fi # ── NIP-07 ──────────────────────────────────────────────────────────── provider=$(curl -s --connect-timeout 5 "http://${TARGET}/app/mempool/" 2>/dev/null | grep -c "nostr-provider" 2>/dev/null || true) provider=$(echo "$provider" | tr -d '[:space:]') [[ -z "$provider" ]] && provider=0 if [[ "$provider" -gt 0 ]]; then tap_ok "nip07-provider-${i}" else tap_fail "nip07-provider-${i}" "nostr-provider.js not found" fi # ── Backup (only first iteration to avoid rate limits) ──────────────── if [[ "$i" -eq 1 ]]; then bk_pass="test-allfeatures-$(date +%s)" bk_result=$(rpc "backup.create" "{\"passphrase\":\"${bk_pass}\",\"description\":\"allfeatures-test\"}" "$SESSION" "$CSRF" 60) bk_id=$(echo "$bk_result" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('result',{}).get('id',''))" 2>/dev/null || echo "") if [[ -n "$bk_id" && "$bk_id" != "None" ]]; then tap_ok "backup-create # ${bk_id:0:8}" # Verify vr=$(rpc "backup.verify" "{\"id\":\"${bk_id}\",\"passphrase\":\"${bk_pass}\"}" "$SESSION" "$CSRF" 60) valid=$(echo "$vr" | python3 -c "import sys,json; d=json.load(sys.stdin); print('yes' if d.get('result',{}).get('valid') else 'no')" 2>/dev/null || echo "no") if [[ "$valid" == "yes" ]]; then tap_ok "backup-verify"; else tap_fail "backup-verify" "$vr"; fi # Delete rpc "backup.delete" "{\"id\":\"${bk_id}\"}" "$SESSION" "$CSRF" 10 >/dev/null 2>&1 tap_ok "backup-delete" else tap_fail "backup-create" "${bk_result:0:100}" fi fi done echo "" TOTAL=$((PASS + FAIL)) echo "1..${TOTAL}" echo "" echo "# ═══════════════════════════════════════════════════════════════" echo "# Results: ${PASS} passed, ${FAIL} failed, ${TOTAL} total" echo "# Finished: $(date -u +%Y-%m-%dT%H:%M:%SZ)" echo "# ═══════════════════════════════════════════════════════════════" if [[ "$FAIL" -gt 0 ]]; then exit 1 fi exit 0