Files
amethyst/cli/tests/marmot/marmot-interop-headless.sh
T
Claude 1b307e5955 fix(cli/tests): bind marmot harness relay to 127.0.0.2 (avoid localhost strip)
Quartz's RelayUrlNormalizer.isLocalHost strips literal 127.0.0.1 /
localhost / 192.168.* / .local / umbrel out of NIP-17 inbox
(kind:10050) and KeyPackage (kind:10051) relay-list events as a
privacy guard. The marmot harness was binding the loopback relay to
ws://127.0.0.1:8080, so when amy added that URL to its kind:10050 /
kind:10051 events the parser silently dropped it on read — leaving
the harness publishing to Amethyst's PUBLIC default relays
(nos.lol, nostr.mom, …) instead of the local one. Whitenoise (which
only listens on the local socket) never saw amy's KeyPackage,
breaking every Test 01–16 scenario at the first hop.

The DM harness already worked around this by using 127.0.0.2 (still
pure loopback, not on the strip list — see dm-interop-headless.sh:34).
This commit applies the same workaround to the marmot harness:

- Default RELAY_HOST=127.0.0.2 (overridable via env or --host).
- RELAY_URL is now derived from RELAY_HOST + RELAY_PORT.
- New --host flag for parity with the DM harness.

setup.sh's relay config already reads RELAY_HOST when binding
nostr-rs-relay (line 173, address = "${RELAY_HOST:-127.0.0.1}"), so
no other change is needed — the relay binds where amy is trying to
reach it.
2026-04-25 15:58:15 +00:00

139 lines
4.4 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# 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`. 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 [--port N] [--no-build]
#
set -uo pipefail
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd -- "$SCRIPT_DIR/../../.." && pwd)"
TESTS_DIR="$(cd -- "$SCRIPT_DIR/.." && pwd)"
STATE_DIR="$SCRIPT_DIR/state-headless"
LOG_DIR="$STATE_DIR/logs"
# A is the amy account inside the fake $HOME=$STATE_DIR layout. B and
# C are wnd (whitenoise) state dirs — different binary, separate
# convention, so they stay as plain $STATE_DIR siblings.
A_DIR="$STATE_DIR/.amy/A"
B_DIR="$STATE_DIR/B"
C_DIR="$STATE_DIR/C"
B_SOCKET="$B_DIR/release/wnd.sock"
C_SOCKET="$C_DIR/release/wnd.sock"
RUN_TS="$(date +%Y%m%d-%H%M%S)"
LOG_FILE="$LOG_DIR/run-$RUN_TS.log"
RESULTS_FILE="$STATE_DIR/results-$RUN_TS.tsv"
WN_REPO="${WN_REPO:-$SCRIPT_DIR/state/whitenoise-rs}"
WN_BIN="$WN_REPO/target/release/wn"
WND_BIN="$WN_REPO/target/release/wnd"
AMY_BIN="$REPO_ROOT/cli/build/install/amy/bin/amy"
# 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.
#
# Bind to 127.0.0.2 rather than 127.0.0.1: Quartz's RelayUrlNormalizer
# strips literal 127.0.0.1 / localhost / 192.168.* out of NIP-17 inbox
# (kind:10050) and KeyPackage (kind:10051) relay-list events as a
# privacy guard, which would silently leave the harness publishing to
# Amethyst's public defaults instead of the loopback. 127.0.0.2 is
# still pure loopback (no network traffic) but isn't on the strip list.
RELAY_HOST="${RELAY_HOST:-127.0.0.2}"
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://$RELAY_HOST:$RELAY_PORT"
NO_BUILD=0
A_NPUB=""
A_HEX=""
B_NPUB=""
B_HEX=""
C_NPUB=""
C_HEX=""
while [[ $# -gt 0 ]]; do
case "$1" in
--port) RELAY_PORT="$2"; RELAY_URL="ws://$RELAY_HOST:$RELAY_PORT"; shift ;;
--host) RELAY_HOST="$2"; RELAY_URL="ws://$RELAY_HOST:$RELAY_PORT"; shift ;;
--no-build) NO_BUILD=1 ;;
-h|--help)
sed -n '3,14p' "${BASH_SOURCE[0]}" | sed 's/^# \?//'
exit 0 ;;
*) printf 'unknown flag: %s\n' "$1" >&2; exit 2 ;;
esac
shift
done
mkdir -p "$STATE_DIR" "$LOG_DIR" "$B_DIR/logs" "$C_DIR/logs"
: >"$LOG_FILE"
: >"$RESULTS_FILE"
# Reuse colours / logging / dump_daemon_diagnostics from the interactive harness.
# shellcheck source=../lib.sh
source "$TESTS_DIR/lib.sh"
# shellcheck source=setup.sh
source "$SCRIPT_DIR/setup.sh"
# shellcheck source=../headless/helpers.sh
source "$TESTS_DIR/headless/helpers.sh"
# shellcheck source=tests-create.sh
source "$SCRIPT_DIR/tests-create.sh"
# shellcheck source=tests-manage.sh
source "$SCRIPT_DIR/tests-manage.sh"
# shellcheck source=tests-extras.sh
source "$SCRIPT_DIR/tests-extras.sh"
# Make sure Ctrl+C / SIGTERM / SIGHUP all run the full cleanup path —
# otherwise wnd is nohup'd and keeps running after the script dies,
# and the next run's `ss` check then complains the port is in use.
# Trap INT/TERM/HUP forces `exit`, which triggers the EXIT handler once.
cleanup() {
local rc=$?
trap - EXIT INT TERM HUP
stop_daemons
stop_local_relay
print_summary
exit "$rc"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
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
ensure_identity B
ensure_identity C
configure_relays
test_01_keypackage_discovery
test_02_a_creates_group
test_03_b_creates_group
test_04_three_member_group
test_05_b_adds_a_existing
test_06_member_removal
test_07_metadata_rename
test_08_admin_promote_demote
test_09_reply_react_unreact
test_10_concurrent_commits
test_11_leave_group
test_12_offline_catchup
test_13_keypackage_rotation
test_14_wn_removes_a
test_15_wn_member_leaves
test_16_wn_keypackage_rotation