feat(marmot-interop): run headless suite against a local nostr-rs-relay
Every test now flows through one loopback nostr-rs-relay on ws://127.0.0.1:8080 — no public relays, no egress. Addresses the repeated 503s we saw from the sandbox and the kind:445 rejections we hit on public relays even with internet. Changes: - Preflight clones https://github.com/scsibug/nostr-rs-relay into state-headless/nostr-rs-relay and runs `cargo build --release` (~3 min first run). Cache hits on subsequent runs. - New start_local_relay / stop_local_relay functions render a minimal config.toml each run (binds to 127.0.0.1, no kind blacklist, data under state-headless/relay) and wait up to 20s for the port. - configure_relays collapsed to a single-relay path — A/B/C all point at \$RELAY_URL. Kept publish-lists + key-package publish as smoke signals for the advertise path. - Flags pared down: drop --local-relays (always local now), add --port N for when 8080 is taken. --no-build still honoured. - trap wires relay shutdown alongside daemon shutdown. https://claude.ai/code/session_01M6dCKAF5Y1VyHGZPjzwDXq
This commit is contained in:
@@ -40,6 +40,93 @@ preflight() {
|
|||||||
fi
|
fi
|
||||||
info "wn: $WN_BIN"
|
info "wn: $WN_BIN"
|
||||||
info "wnd: $WND_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" <<EOF
|
||||||
|
[info]
|
||||||
|
relay_url = "$RELAY_URL"
|
||||||
|
name = "amethyst-headless-harness"
|
||||||
|
description = "Loopback relay for marmot-interop-headless.sh — do not use for anything real."
|
||||||
|
|
||||||
|
[database]
|
||||||
|
data_directory = "$RELAY_DATA"
|
||||||
|
|
||||||
|
[network]
|
||||||
|
address = "127.0.0.1"
|
||||||
|
port = $RELAY_PORT
|
||||||
|
|
||||||
|
[options]
|
||||||
|
reject_future_seconds = 3600
|
||||||
|
|
||||||
|
[limits]
|
||||||
|
# Keep kind:444 / 445 / 1059 / 30443 wide open — the whole point is
|
||||||
|
# exercising Marmot traffic the public relays reject.
|
||||||
|
max_event_bytes = 524288
|
||||||
|
max_ws_message_bytes = 1048576
|
||||||
|
max_ws_frame_bytes = 1048576
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# Abort early if something else is already bound to the port — failing
|
||||||
|
# with a clear error beats a mysterious-looking daemon stall later.
|
||||||
|
if ss -ltn 2>/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 -----------------------------------------------------------------
|
# --- daemons -----------------------------------------------------------------
|
||||||
@@ -114,28 +201,20 @@ ensure_identity() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
# --- relays ------------------------------------------------------------------
|
# --- 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() {
|
configure_relays() {
|
||||||
banner "Configuring relays"
|
banner "Configuring relays → $RELAY_URL"
|
||||||
local relays=()
|
amy_a relay add "$RELAY_URL" --type all >/dev/null
|
||||||
if [[ "$USE_LOCAL_RELAYS" -eq 1 ]]; then
|
for t in nip65 inbox key_package; do
|
||||||
relays=( "ws://localhost:8080" )
|
wn_b relays add --type "$t" "$RELAY_URL" 2>/dev/null || true
|
||||||
else
|
wn_c relays add --type "$t" "$RELAY_URL" 2>/dev/null || true
|
||||||
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
|
|
||||||
done
|
done
|
||||||
|
|
||||||
# A advertises its NIP-65 + DM inbox lists so B/C can discover where to
|
# 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
|
# deliver gift wraps. With a single shared relay the lookup is trivial
|
||||||
# through relay-set overlap — fine for this harness but still worth
|
# but we still publish so we catch regressions in the advertise path.
|
||||||
# publishing so we catch regressions in the advertise path.
|
|
||||||
step "publishing A's NIP-65 + kind:10050 lists"
|
step "publishing A's NIP-65 + kind:10050 lists"
|
||||||
amy_a relay publish-lists >>"$LOG_FILE" 2>&1 || warn "amy relay publish-lists failed"
|
amy_a relay publish-lists >>"$LOG_FILE" 2>&1 || warn "amy relay publish-lists failed"
|
||||||
|
|
||||||
|
|||||||
@@ -1,13 +1,15 @@
|
|||||||
#!/usr/bin/env bash
|
#!/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
|
# Drives Identity A via the `amy` CLI (./gradlew :cli:installDist) and
|
||||||
# Identities B/C via whitenoise-rs `wn`/`wnd`. Matches the 13 test
|
# Identities B/C via whitenoise-rs `wn`/`wnd`. Spins up a local
|
||||||
# scenarios in marmot-interop.sh but without any human prompts — all
|
# nostr-rs-relay on ws://127.0.0.1:$RELAY_PORT so nothing ever leaves the
|
||||||
# checks run to completion and the exit code reflects pass/fail totals.
|
# 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
|
set -uo pipefail
|
||||||
|
|
||||||
@@ -30,8 +32,14 @@ WN_BIN="$WN_REPO/target/release/wn"
|
|||||||
WND_BIN="$WN_REPO/target/release/wnd"
|
WND_BIN="$WN_REPO/target/release/wnd"
|
||||||
AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy"
|
AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy"
|
||||||
|
|
||||||
DEFAULT_RELAYS=( "wss://relay.damus.io" "wss://nos.lol" "wss://relay.primal.net" )
|
# Local relay wiring — cloned + built during preflight, started on
|
||||||
USE_LOCAL_RELAYS=0
|
# $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
|
NO_BUILD=0
|
||||||
|
|
||||||
A_NPUB=""
|
A_NPUB=""
|
||||||
@@ -43,10 +51,10 @@ C_HEX=""
|
|||||||
|
|
||||||
while [[ $# -gt 0 ]]; do
|
while [[ $# -gt 0 ]]; do
|
||||||
case "$1" in
|
case "$1" in
|
||||||
--local-relays) USE_LOCAL_RELAYS=1 ;;
|
--port) RELAY_PORT="$2"; RELAY_URL="ws://127.0.0.1:$RELAY_PORT"; shift ;;
|
||||||
--no-build) NO_BUILD=1 ;;
|
--no-build) NO_BUILD=1 ;;
|
||||||
-h|--help)
|
-h|--help)
|
||||||
sed -n '3,12p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'
|
sed -n '3,14p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'
|
||||||
exit 0 ;;
|
exit 0 ;;
|
||||||
*) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;;
|
*) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;;
|
||||||
esac
|
esac
|
||||||
@@ -72,10 +80,11 @@ source "$SCRIPT_DIR/headless/tests-manage.sh"
|
|||||||
# shellcheck source=headless/tests-extras.sh
|
# shellcheck source=headless/tests-extras.sh
|
||||||
source "$SCRIPT_DIR/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)"
|
banner "Marmot headless interop harness ($RUN_TS)"
|
||||||
preflight
|
preflight
|
||||||
|
start_local_relay
|
||||||
start_daemon B "$B_DIR" "$B_SOCKET"
|
start_daemon B "$B_DIR" "$B_SOCKET"
|
||||||
start_daemon C "$C_DIR" "$C_SOCKET"
|
start_daemon C "$C_DIR" "$C_SOCKET"
|
||||||
ensure_identity_a
|
ensure_identity_a
|
||||||
|
|||||||
Reference in New Issue
Block a user