← back to Govarbitrage
scripts/deploy-auctions.sh
122 lines
#!/bin/bash
# One-shot deploy of GovArbitrage → auctions.agentabrams.com (Kamatera).
# Updates the existing stub in place: rsync code, install, migrate, build,
# populate with the FREE feeds ($0), restart pm2. Preserves the server .env
# (keeps DATABASE_URL); only appends missing auth secrets.
set -euo pipefail
HOST=root@45.61.58.125
DIR=/root/public-projects/govarbitrage
LOCAL=/Users/macstudio3/Projects/govarbitrage
echo "▶ rsync app → $HOST:$DIR"
cd "$LOCAL"
# --delete makes the server match this repo exactly, removing stale files from
# the prior stub build (its root app/ dir shadowed our src/app/ and broke the
# build). Excludes below are protected from deletion (.env, node_modules, etc.).
rsync -az --delete \
--exclude node_modules --exclude .next --exclude .git --exclude '.env' \
--exclude logs --exclude 'prisma/*.db' --exclude '.DS_Store' \
./ "$HOST:$DIR/"
echo "▶ remote: env, install, migrate, build, populate, restart"
ssh "$HOST" bash -s <<'REMOTE'
set -euo pipefail
cd /root/public-projects/govarbitrage
# Ensure required auth secrets exist without touching DATABASE_URL.
touch .env
grep -q '^AUTH_SECRET=' .env || echo "AUTH_SECRET=\"$(openssl rand -hex 24)\"" >> .env
grep -q '^ENCRYPTION_KEY=' .env || echo "ENCRYPTION_KEY=\"$(openssl rand -hex 32)\"" >> .env
grep -q '^GSA_API_KEY=' .env || echo 'GSA_API_KEY="DEMO_KEY"' >> .env
grep -q '^NODE_ENV=' .env || echo 'NODE_ENV="production"' >> .env
# Fleet single-sign-on: copy the shared HMAC secret so a valid *.agentabrams.com
# "aafleet" cookie is accepted by the middleware (see src/lib/fleet-sso.ts).
# Read at BUILD time by the Edge middleware, so it must exist before `npm run build`.
if ! grep -q '^FLEET_SSO_SECRET=' .env && [ -r /var/www/fleet-sso/.secret ]; then
echo "FLEET_SSO_SECRET=\"$(tr -d '\n' < /var/www/fleet-sso/.secret)\"" >> .env
fi
# Google Places API key — REQUIRED by src/lib/places.ts (searchCommercialAgents
# throws "GOOGLE_PLACES_API_KEY is not configured" when absent, which makes the
# /api/agents/search route return HTTP 503). Provision it NON-destructively:
# preserve an existing key, else copy it from the first readable shared on-host
# secret file (same pattern as FLEET_SSO_SECRET above). If it's absent AND no
# secret source can be found, DON'T invent a value (a placeholder would make
# places.ts think it's configured and 503 at request time instead) — emit a
# LOUD warning so the operator sees it in the deploy output.
if ! grep -q '^GOOGLE_PLACES_API_KEY=' .env; then
PLACES_SECRET=""
for cand in /var/www/fleet-sso/google-places.secret /root/.secrets/google-places.secret /root/.secrets/GOOGLE_PLACES_API_KEY; do
if [ -r "$cand" ]; then PLACES_SECRET="$cand"; break; fi
done
if [ -n "$PLACES_SECRET" ]; then
echo "GOOGLE_PLACES_API_KEY=\"$(tr -d '\n' < "$PLACES_SECRET")\"" >> .env
echo " provisioned GOOGLE_PLACES_API_KEY from $PLACES_SECRET"
else
echo "⚠ GOOGLE_PLACES_API_KEY not set and no secret source found — /agents live search will 503 until it is added"
fi
fi
echo " npm install"
npm install --no-audit --no-fund
echo " prisma generate + db push"
npx prisma generate
# NON-destructive push: our schema changes are additive (new enum values, new
# ListingStatus enum, new nullable/defaulted columns), so they apply in place
# WITHOUT wiping the live listings. (The old --force-reset was only needed for
# the very first deploy over the incompatible stub schema.) The free-feed
# imports below UPSERT by (source, sourceAuctionId), so re-running just refreshes.
npx prisma db push --skip-generate
echo " build"
npm run build
echo " seed users (+ demo) then import FREE feeds (\$0)"
npx tsx prisma/seed.ts || true
npx tsx scripts/import-govdeals-free.ts 120 || true
npx tsx scripts/import-gsa.ts 200 || true
npx tsx scripts/import-grays.ts 100 || true
npx tsx scripts/import-municibid-free.ts 100 || true
npx tsx scripts/import-publicsurplus-free.ts 100 || true
npx tsx scripts/import-govplanet-free.ts 120 || true
echo " restart pm2"
pm2 restart govarbitrage --update-env
pm2 save
echo " install prod cron (auto-refresh + liveness) — self-maintaining live site"
mkdir -p /root/public-projects/govarbitrage/logs
# cron runs with a minimal PATH; bake in node's bin dir resolved right now.
NODE_BIN="$(dirname "$(command -v node)")"
cat > /etc/cron.d/govarbitrage <<CRON
SHELL=/bin/bash
PATH=$NODE_BIN:/usr/local/bin:/usr/bin:/bin
# Refresh all 6 free feeds daily at 12:00 (UPSERT — keeps the live site fresh).
0 12 * * * root cd /root/public-projects/govarbitrage && ( npx tsx scripts/import-govdeals-free.ts 120; npx tsx scripts/import-gsa.ts 200; npx tsx scripts/import-grays.ts 100; npx tsx scripts/import-municibid-free.ts 100; npx tsx scripts/import-publicsurplus-free.ts 100; npx tsx scripts/import-govplanet-free.ts 120 ) >> logs/refresh.log 2>&1
# Fast dead-listing sweep every 15 min (Tier1 instant-expire + Tier2 verify).
*/15 * * * * root cd /root/public-projects/govarbitrage && npx tsx scripts/liveness-sweep.ts 40 >> logs/liveness.log 2>&1
CRON
chmod 0644 /etc/cron.d/govarbitrage
echo " cron installed: refresh @12:00, liveness every 15m (PATH node=$NODE_BIN)"
REMOTE
echo "▶ smoke test"
curl -s -o /dev/null -w " auctions.agentabrams.com/login -> %{http_code}\n" https://auctions.agentabrams.com/login
# Retry /login until it genuinely serves (200) or we exhaust the attempts, so a
# transient 502 boot-race (pm2 mid-restart) isn't misreported as a failed deploy.
LOGIN_URL="https://auctions.agentabrams.com/login"
SMOKE_OK=""
for attempt in $(seq 1 15); do
# Guarded so a non-2xx curl (and pipefail) can't abort the script mid-check.
code="$(curl -s -o /dev/null -w '%{http_code}' "$LOGIN_URL" || echo 000)"
if [ "$code" = "200" ]; then
echo " ✔ /login serving 200 (attempt $attempt/15)"
SMOKE_OK=1
break
fi
echo " … /login -> $code, retrying ($attempt/15)"
sleep 2
done
if [ -z "$SMOKE_OK" ]; then
echo " ⚠ /login never returned 200 after 15 attempts — check pm2 logs (may be a real boot failure, not just a race)"
fi
echo "✔ deploy complete — https://auctions.agentabrams.com (login: admin@govarbitrage.local / changeme)"