#!/usr/bin/env bash # earth-now.co # Author: Simon-Pierre Boucher # Contact: contact@spboucher.ai # File: infra/deploy-m3u96b.sh # Purpose: Deploy to production node m3u96b via PM2 + ngrok (node's standard pattern; Docker variant in deploy-m3u96b-docker.sh), then MANDATORY health checks set -euo pipefail REMOTE="M3U96b" REMOTE_DIR="apps/earth-now" BASE_URL="https://www.earth-now.co" API_PORT=4000 WEB_PORT=4100 PROXY_PORT=8080 ROOT="$(cd "$(dirname "$0")/.." && pwd)" echo "==> rsync source to ${REMOTE}:~/${REMOTE_DIR}" rsync -az --delete \ --exclude node_modules --exclude .next --exclude dist --exclude .turbo \ --exclude .git --exclude data --exclude .env \ "${ROOT}/" "${REMOTE}:${REMOTE_DIR}/" echo "==> install + build on ${REMOTE}" # NEXT_PUBLIC_API_URL="" is inlined at build time → client fetches stay same-origin # (routed by infra/proxy.mjs behind the single ngrok domain). ssh "${REMOTE}" "cd ${REMOTE_DIR} && pnpm install --frozen-lockfile=false && NEXT_PUBLIC_API_URL='' pnpm build" echo "==> (re)start PM2 processes" ssh "${REMOTE}" bash -s </dev/null || true PORT=${API_PORT} pm2 start /opt/homebrew/bin/node --name earthnow-api --interpreter none -- apps/api/dist/server.js cd apps/web && API_URL=http://127.0.0.1:${API_PORT} pm2 start ./node_modules/.bin/next --name earthnow-web --interpreter none -- start -H 127.0.0.1 -p ${WEB_PORT} && cd ../.. PROXY_PORT=${PROXY_PORT} API_PORT=${API_PORT} WEB_PORT=${WEB_PORT} pm2 start /opt/homebrew/bin/node --name earthnow-proxy --interpreter none -- infra/proxy.mjs pm2 start /opt/homebrew/bin/ngrok --name earthnow-ngrok --interpreter none -- http --url=www.earth-now.co ${PROXY_PORT} pm2 save EOF echo "==> health checks (deploy is NOT done until both pass)" sleep 8 ok=0 if curl -fsS --max-time 15 "${BASE_URL}/api/health" | grep -q '"status":"ok"'; then echo " ✓ ${BASE_URL}/api/health" else echo " ✗ ${BASE_URL}/api/health FAILED" ok=1 fi # /sse/health must hold an SSE connection open >= 10 s (CLAUDE.md requirement). # curl exiting 28 (max-time reached) is the EXPECTED outcome — the stream never ends. sse_bytes=$( (curl -sN --max-time 12 "${BASE_URL}/sse/health" || true) | head -c 200 | wc -c | tr -d ' ') if [ "${sse_bytes}" -gt 0 ]; then echo " ✓ ${BASE_URL}/sse/health held an SSE stream (received ${sse_bytes} bytes over ~12 s)" else echo " ✗ ${BASE_URL}/sse/health FAILED (no SSE bytes received)" ok=1 fi if [ "${ok}" -ne 0 ]; then echo "DEPLOY FAILED — health checks did not pass." exit 1 fi echo "==> DEPLOY OK — ${BASE_URL} is live."