run-market-probe.sh raw
1 #!/usr/bin/env bash
2 set -euo pipefail
3
4 # run-market-probe.sh
5 # Starts the ORLY relay with relaxed ACL, then executes the Market repo's
6 # scripts/startup.ts to publish seed events and finally runs a small NDK-based
7 # fetcher to verify the events can be read back from the relay. The goal is to
8 # print detailed logs to diagnose end-to-end publish/subscribe behavior.
9 #
10 # Usage:
11 # scripts/run-market-probe.sh /path/to/market <hex_private_key>
12 # MARKET_DIR=/path/to/market APP_PRIVATE_KEY=hex scripts/run-market-probe.sh
13 #
14 # Requirements:
15 # - go, bun, curl
16 # - Market repo available locally with scripts/startup.ts (see path above)
17 #
18 # Behavior:
19 # - Clears relay data dir (/tmp/plebeian) each run
20 # - Starts relay on 127.0.0.1:10547 with ORLY_ACL_MODE=none (no auth needed)
21 # - Exports APP_RELAY_URL to ws://127.0.0.1:10547 for the Market startup.ts
22 # - Runs Market's startup.ts to publish events (kinds 31990, 10002, 10000, 30000)
23 # - Runs a temporary TypeScript fetcher using NDK to subscribe & log results
24 #
25
26 # ---------- Config ----------
27 RELAY_HOST="127.0.0.1"
28 RELAY_PORT="10547"
29 RELAY_DATA_DIR="/tmp/plebeian"
30 WAIT_TIMEOUT="120" # seconds - increased for slow startup
31 RELAY_LOG_PREFIX="[relay]"
32 MARKET_LOG_PREFIX="[market-seed]"
33 FETCH_LOG_PREFIX="[fetch]"
34
35 # ---------- Resolve repo root ----------
36 SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
37 REPO_ROOT="$(cd -- "${SCRIPT_DIR}/.." && pwd)"
38 cd "${REPO_ROOT}"
39
40 # ---------- Resolve Market directory and private key ----------
41 MARKET_DIR=${1:-${MARKET_DIR:-}}
42 APP_PRIVATE_KEY_INPUT=${2:-${APP_PRIVATE_KEY:-${NOSTR_SK:-}}}
43 if [[ -z "${MARKET_DIR}" ]]; then
44 echo "ERROR: Market repository directory not provided. Set MARKET_DIR env or pass as first arg." >&2
45 echo "Example: MARKET_DIR=$HOME/src/github.com/PlebianApp/market scripts/run-market-probe.sh" >&2
46 exit 1
47 fi
48 if [[ ! -d "${MARKET_DIR}" ]]; then
49 echo "ERROR: MARKET_DIR does not exist: ${MARKET_DIR}" >&2
50 exit 1
51 fi
52 if [[ -z "${APP_PRIVATE_KEY_INPUT}" ]]; then
53 echo "ERROR: Private key not provided. Pass as 2nd arg or set APP_PRIVATE_KEY or NOSTR_SK env var." >&2
54 exit 1
55 fi
56
57 # ---------- Prerequisites ----------
58 command -v go >/dev/null 2>&1 || { echo "ERROR: 'go' not found in PATH" >&2; exit 1; }
59 command -v bun >/dev/null 2>&1 || { echo "ERROR: 'bun' not found in PATH. Install Bun: https://bun.sh" >&2; exit 1; }
60 command -v curl >/dev/null 2>&1 || { echo "ERROR: 'curl' not found in PATH" >&2; exit 1; }
61
62 # ---------- Cleanup handler ----------
63 RELAY_PID=""
64 TMP_FETCH_DIR=""
65 TMP_FETCH_TS=""
66 cleanup() {
67 set +e
68 if [[ -n "${RELAY_PID}" ]]; then
69 echo "${RELAY_LOG_PREFIX} stopping relay (pid=${RELAY_PID})" >&2
70 kill "${RELAY_PID}" 2>/dev/null || true
71 wait "${RELAY_PID}" 2>/dev/null || true
72 fi
73 if [[ -n "${TMP_FETCH_DIR}" && -d "${TMP_FETCH_DIR}" ]]; then
74 rm -rf "${TMP_FETCH_DIR}" || true
75 fi
76 }
77 trap cleanup EXIT INT TERM
78
79 # ---------- Start relay ----------
80 reset || true
81 rm -rf "${RELAY_DATA_DIR}"
82 (
83 export ORLY_LOG_LEVEL="trace"
84 export ORLY_LISTEN="0.0.0.0"
85 export ORLY_PORT="${RELAY_PORT}"
86 export ORLY_ADMINS="" # ensure no admin ACL
87 export ORLY_ACL_MODE="none" # fully open for test
88 export ORLY_DATA_DIR="${RELAY_DATA_DIR}"
89 cd "${REPO_ROOT}"
90 stdbuf -oL -eL go run . 2>&1 | sed -u "s/^/${RELAY_LOG_PREFIX} /"
91 ) &
92 RELAY_PID=$!
93 echo "${RELAY_LOG_PREFIX} started (pid=${RELAY_PID}), waiting for readiness on ${RELAY_HOST}:${RELAY_PORT} …"
94
95 # ---------- Wait for readiness ----------
96 start_ts=$(date +%s)
97 while true; do
98 if curl -fsS "http://${RELAY_HOST}:${RELAY_PORT}/" >/dev/null 2>&1; then
99 break
100 fi
101 now=$(date +%s)
102 if (( now - start_ts > WAIT_TIMEOUT )); then
103 echo "ERROR: relay did not become ready within ${WAIT_TIMEOUT}s" >&2
104 exit 1
105 fi
106 sleep 1
107 done
108 echo "${RELAY_LOG_PREFIX} ready. Starting Market publisher…"
109
110 # ---------- Publish via Market's startup.ts ----------
111 (
112 export APP_RELAY_URL="ws://${RELAY_HOST}:${RELAY_PORT}"
113 export APP_PRIVATE_KEY="${APP_PRIVATE_KEY_INPUT}"
114 cd "${MARKET_DIR}"
115 # Use bun to run the exact startup.ts the app uses. Expect its dependencies in Market repo.
116 echo "${MARKET_LOG_PREFIX} running scripts/startup.ts against ${APP_RELAY_URL} …"
117 stdbuf -oL -eL bun run scripts/startup.ts 2>&1 | sed -u "s/^/${MARKET_LOG_PREFIX} /"
118 )
119
120 # ---------- Prepare a temporary NDK fetcher workspace ----------
121 TMP_FETCH_DIR=$(mktemp -d /tmp/ndk-fetch-XXXXXX)
122 TMP_FETCH_TS="${TMP_FETCH_DIR}/probe.ts"
123
124 # Write probe script
125 cat >"${TMP_FETCH_TS}" <<'TS'
126 import { config } from 'dotenv'
127 config()
128
129 const RELAY_URL = process.env.APP_RELAY_URL
130 const APP_PRIVATE_KEY = process.env.APP_PRIVATE_KEY
131
132 if (!RELAY_URL || !APP_PRIVATE_KEY) {
133 console.error('[fetch] Missing APP_RELAY_URL or APP_PRIVATE_KEY in env')
134 process.exit(2)
135 }
136
137 // Use NDK like startup.ts does
138 import NDK, { NDKEvent, NDKPrivateKeySigner, NDKFilter } from '@nostr-dev-kit/ndk'
139
140 const relay = RELAY_URL as string
141 const privateKey = APP_PRIVATE_KEY as string
142
143 async function main() {
144 console.log(`[fetch] initializing NDK -> ${relay}`)
145 const ndk = new NDK({ explicitRelayUrls: [relay] })
146 ndk.pool?.on('relay:connect', (r) => console.log('[fetch] relay connected:', r.url))
147 ndk.pool?.on('relay:disconnect', (r) => console.log('[fetch] relay disconnected:', r.url))
148 ndk.pool?.on('relay:notice', (r, msg) => console.log('[fetch] relay notice:', r.url, msg))
149
150 await ndk.connect(8000)
151 console.log('[fetch] connected')
152
153 // Setup signer and derive pubkey
154 const signer = new NDKPrivateKeySigner(privateKey)
155 ndk.signer = signer
156 await signer.blockUntilReady()
157 const pubkey = (await signer.user())?.pubkey
158 console.log('[fetch] signer pubkey:', pubkey)
159
160 // Subscribe to the kinds published by startup.ts authored by pubkey
161 const filters: NDKFilter[] = [
162 { kinds: [31990, 10002, 10000, 30000], authors: pubkey ? [pubkey] : undefined, since: Math.floor(Date.now()/1000) - 3600 },
163 ]
164 console.log('[fetch] subscribing with filters:', JSON.stringify(filters))
165
166 const sub = ndk.subscribe(filters, { closeOnEose: true })
167 let count = 0
168 const received: string[] = []
169
170 sub.on('event', (e: NDKEvent) => {
171 count++
172 received.push(`${e.kind}:${e.tagValue('d') || ''}:${e.id}`)
173 console.log('[fetch] EVENT kind=', e.kind, 'id=', e.id, 'tags=', e.tags)
174 })
175 sub.on('eose', () => {
176 console.log('[fetch] EOSE received; total events:', count)
177 })
178 sub.on('error', (err: any) => {
179 console.error('[fetch] subscription error:', err)
180 })
181
182 // Also try to fetch by kinds one by one to be verbose
183 const kinds = [31990, 10002, 10000, 30000]
184 for (const k of kinds) {
185 try {
186 const e = await ndk.fetchEvent({ kinds: [k], authors: pubkey ? [pubkey] : undefined }, { cacheUsage: 'ONLY_RELAY' })
187 if (e) {
188 console.log(`[fetch] fetchEvent kind=${k} -> id=${e.id}`)
189 } else {
190 console.log(`[fetch] fetchEvent kind=${k} -> not found`)
191 }
192 } catch (err) {
193 console.error(`[fetch] fetchEvent kind=${k} error`, err)
194 }
195 }
196
197 // Wait a bit to allow sub to drain
198 await new Promise((res) => setTimeout(res, 2000))
199 console.log('[fetch] received summary:', received)
200 // Note: NDK v2.14.x does not expose pool.close(); rely on closeOnEose and process exit
201 }
202
203 main().catch((e) => {
204 console.error('[fetch] fatal error:', e)
205 process.exit(3)
206 })
207 TS
208
209 # Write minimal package.json to pin dependencies and satisfy NDK peer deps
210 cat >"${TMP_FETCH_DIR}/package.json" <<'JSON'
211 {
212 "name": "ndk-fetch-probe",
213 "version": "0.0.1",
214 "private": true,
215 "type": "module",
216 "dependencies": {
217 "@nostr-dev-kit/ndk": "^2.14.36",
218 "nostr-tools": "^2.7.0",
219 "dotenv": "^16.4.5"
220 }
221 }
222 JSON
223
224 # ---------- Install probe dependencies explicitly (avoid Bun auto-install pitfalls) ----------
225 (
226 cd "${TMP_FETCH_DIR}"
227 echo "${FETCH_LOG_PREFIX} installing probe deps (@nostr-dev-kit/ndk, nostr-tools, dotenv) …"
228 stdbuf -oL -eL bun install 2>&1 | sed -u "s/^/${FETCH_LOG_PREFIX} [install] /"
229 )
230
231 # ---------- Run the fetcher ----------
232 (
233 export APP_RELAY_URL="ws://${RELAY_HOST}:${RELAY_PORT}"
234 export APP_PRIVATE_KEY="${APP_PRIVATE_KEY_INPUT}"
235 echo "${FETCH_LOG_PREFIX} running fetch probe against ${APP_RELAY_URL} …"
236 (
237 cd "${TMP_FETCH_DIR}"
238 stdbuf -oL -eL bun "${TMP_FETCH_TS}" 2>&1 | sed -u "s/^/${FETCH_LOG_PREFIX} /"
239 )
240 )
241
242 echo "[probe] Completed. Review logs above for publish/subscribe flow."