diff --git a/tools/marmot-interop/headless/setup.sh b/tools/marmot-interop/headless/setup.sh index 686af8be1..d66c01cf9 100644 --- a/tools/marmot-interop/headless/setup.sh +++ b/tools/marmot-interop/headless/setup.sh @@ -40,6 +40,93 @@ preflight() { fi info "wn: $WN_BIN" info "wnd: $WND_BIN" + + # Clone/build nostr-rs-relay — the harness's single loopback relay. + if [[ ! -x "$RELAY_BIN" ]]; then + if [[ "$NO_BUILD" -eq 1 ]]; then + fail_msg "nostr-rs-relay not found at $RELAY_BIN and --no-build set"; exit 1 + fi + if [[ ! -d "$RELAY_REPO/.git" ]]; then + step "cloning nostr-rs-relay into $RELAY_REPO" + git clone --depth 1 https://github.com/scsibug/nostr-rs-relay "$RELAY_REPO" \ + 2>&1 | tee -a "$LOG_FILE" + fi + step "building nostr-rs-relay (~3 min first run)" + ( cd "$RELAY_REPO" && cargo build --release --bin nostr-rs-relay ) \ + 2>&1 | tee -a "$LOG_FILE" + fi + info "relay bin: $RELAY_BIN" +} + +# --- local relay ------------------------------------------------------------- +# Start nostr-rs-relay on $RELAY_PORT with a minimal config. Every test +# runs against this one loopback endpoint — no external network traffic. +start_local_relay() { + banner "Starting local nostr-rs-relay on $RELAY_URL" + mkdir -p "$RELAY_DATA" "$RELAY_DATA/logs" + + # Render a minimal config file each run so port/limits come from the + # harness rather than whatever was left on disk from a previous session. + cat >"$RELAY_DATA/config.toml" </dev/null | awk '{print $4}' | grep -qE "[:.]$RELAY_PORT\$"; then + fail_msg "port $RELAY_PORT already in use — pass --port N or free it" + exit 1 + fi + + nohup "$RELAY_BIN" --db "$RELAY_DATA" --config "$RELAY_DATA/config.toml" \ + >"$RELAY_DATA/logs/stdout.log" 2>"$RELAY_DATA/logs/stderr.log" & + echo "$!" > "$RELAY_DATA/pid" + step "relay pid $(cat "$RELAY_DATA/pid"); waiting for $RELAY_URL …" + + local deadline=$(( $(date +%s) + 20 )) + while [[ $(date +%s) -lt $deadline ]]; do + if curl -sSf -m 1 "http://127.0.0.1:$RELAY_PORT/" >/dev/null 2>&1; then + info "relay up" + return 0 + fi + sleep 0.5 + done + fail_msg "relay never came up (see $RELAY_DATA/logs/stderr.log)" + tail -n 40 "$RELAY_DATA/logs/stderr.log" 2>/dev/null | sed 's/^/ /' >&2 || true + exit 1 +} + +stop_local_relay() { + local pid_file="$RELAY_DATA/pid" + [[ -f "$pid_file" ]] || return 0 + local pid; pid=$(cat "$pid_file" 2>/dev/null || echo "") + if [[ -n "$pid" ]] && kill -0 "$pid" 2>/dev/null; then + info "stopping relay pid $pid" + kill "$pid" 2>/dev/null || true + sleep 1 + kill -9 "$pid" 2>/dev/null || true + fi + rm -f "$pid_file" } # --- daemons ----------------------------------------------------------------- @@ -114,28 +201,20 @@ ensure_identity() { } # --- relays ------------------------------------------------------------------ +# Point all three identities at the loopback relay. We never publish any +# test traffic off-box — public relays reject kind:445 anyway and the +# goal here is tight, deterministic iteration. configure_relays() { - banner "Configuring relays" - local relays=() - if [[ "$USE_LOCAL_RELAYS" -eq 1 ]]; then - relays=( "ws://localhost:8080" ) - else - relays=( "${DEFAULT_RELAYS[@]}" ) - fi - - for r in "${relays[@]}"; do - step "adding $r to A/B/C" - amy_a relay add "$r" --type all >/dev/null - for t in nip65 inbox key_package; do - wn_b relays add --type "$t" "$r" 2>/dev/null || true - wn_c relays add --type "$t" "$r" 2>/dev/null || true - done + banner "Configuring relays → $RELAY_URL" + amy_a relay add "$RELAY_URL" --type all >/dev/null + for t in nip65 inbox key_package; do + wn_b relays add --type "$t" "$RELAY_URL" 2>/dev/null || true + wn_c relays add --type "$t" "$RELAY_URL" 2>/dev/null || true done # A advertises its NIP-65 + DM inbox lists so B/C can discover where to - # deliver gift wraps. Without this, cross-client welcomes only work - # through relay-set overlap — fine for this harness but still worth - # publishing so we catch regressions in the advertise path. + # deliver gift wraps. With a single shared relay the lookup is trivial + # but we still publish so we catch regressions in the advertise path. step "publishing A's NIP-65 + kind:10050 lists" amy_a relay publish-lists >>"$LOG_FILE" 2>&1 || warn "amy relay publish-lists failed" diff --git a/tools/marmot-interop/marmot-interop-headless.sh b/tools/marmot-interop/marmot-interop-headless.sh index c4d0e9501..0cff73d83 100755 --- a/tools/marmot-interop/marmot-interop-headless.sh +++ b/tools/marmot-interop/marmot-interop-headless.sh @@ -1,13 +1,15 @@ #!/usr/bin/env bash # -# marmot-interop-headless.sh — zero-prompt interop harness. +# marmot-interop-headless.sh — zero-prompt, zero-internet interop harness. # # Drives Identity A via the `amy` CLI (./gradlew :cli:installDist) and -# Identities B/C via whitenoise-rs `wn`/`wnd`. Matches the 13 test -# scenarios in marmot-interop.sh but without any human prompts — all -# checks run to completion and the exit code reflects pass/fail totals. +# Identities B/C via whitenoise-rs `wn`/`wnd`. Spins up a local +# nostr-rs-relay on ws://127.0.0.1:$RELAY_PORT so nothing ever leaves the +# machine. Matches the 13 test scenarios in marmot-interop.sh but without +# any human prompts — all checks run to completion and the exit code +# reflects pass/fail totals. # -# Usage: ./marmot-interop-headless.sh [--local-relays] [--no-build] +# Usage: ./marmot-interop-headless.sh [--port N] [--no-build] # set -uo pipefail @@ -30,8 +32,14 @@ WN_BIN="$WN_REPO/target/release/wn" WND_BIN="$WN_REPO/target/release/wnd" AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy" -DEFAULT_RELAYS=( "wss://relay.damus.io" "wss://nos.lol" "wss://relay.primal.net" ) -USE_LOCAL_RELAYS=0 +# Local relay wiring — cloned + built during preflight, started on +# $RELAY_PORT. The harness never touches the public internet for test +# traffic; wn/wnd/amy all point at this one loopback endpoint. +RELAY_REPO="${RELAY_REPO:-$STATE_DIR/nostr-rs-relay}" +RELAY_BIN="$RELAY_REPO/target/release/nostr-rs-relay" +RELAY_DATA="$STATE_DIR/relay" +RELAY_PORT="${RELAY_PORT:-8080}" +RELAY_URL="ws://127.0.0.1:$RELAY_PORT" NO_BUILD=0 A_NPUB="" @@ -43,10 +51,10 @@ C_HEX="" while [[ $# -gt 0 ]]; do case "$1" in - --local-relays) USE_LOCAL_RELAYS=1 ;; - --no-build) NO_BUILD=1 ;; + --port) RELAY_PORT="$2"; RELAY_URL="ws://127.0.0.1:$RELAY_PORT"; shift ;; + --no-build) NO_BUILD=1 ;; -h|--help) - sed -n '3,12p' "${BASH_SOURCE[0]}" | sed 's/^# \?//' + sed -n '3,14p' "${BASH_SOURCE[0]}" | sed 's/^# \?//' exit 0 ;; *) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;; esac @@ -72,10 +80,11 @@ source "$SCRIPT_DIR/headless/tests-manage.sh" # shellcheck source=headless/tests-extras.sh source "$SCRIPT_DIR/headless/tests-extras.sh" -trap 'stop_daemons; print_summary' EXIT +trap 'stop_daemons; stop_local_relay; print_summary' EXIT banner "Marmot headless interop harness ($RUN_TS)" preflight +start_local_relay start_daemon B "$B_DIR" "$B_SOCKET" start_daemon C "$C_DIR" "$C_SOCKET" ensure_identity_a