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."