← back to Rentv 2026
deploy.sh
95 lines
#!/bin/zsh
# ─────────────────────────────────────────────────────────────────────────────
# deploy.sh — one-command RENTV deploy to the LIVE Kamatera box.
#
# Does the three things a bare file-copy can't:
# 1. rsync CODE local→prod (excludes prod-canonical data/ + .env + junk)
# 2. apply any PENDING DB migrations as the postgres SUPERUSER (pr_app can't DDL)
# 3. pm2 reload rentv + rentv-pr-worker so new routes/services load
# then a health check. Idempotent + forward-only; safe to re-run.
#
# Usage: ./deploy.sh # full: code + migrations + reload
# ./deploy.sh --no-migrate # skip the migration step
# ./deploy.sh --code-only # rsync only (no migrate, no reload)
# Cost: $0 (rsync/ssh/psql). Called automatically by .git/hooks/post-commit on
# code changes; can also be run by hand.
# ─────────────────────────────────────────────────────────────────────────────
set -euo pipefail
HOST="root@45.61.58.125"
RDIR="/root/public-projects/rentv"
KEY="$HOME/.ssh/id_ed25519_wallco_20260530"
SSHOPTS=(-i "$KEY" -o ConnectTimeout=15 -o StrictHostKeyChecking=accept-new -o BatchMode=yes)
SRC="$HOME/Projects/rentv"
PORT=9704
LOG="$SRC/logs/deploy.log"
mkdir -p "$SRC/logs"
stamp() { date -u +%FT%TZ }
say() { echo "$(stamp) $*" | tee -a "$LOG"; }
DO_MIGRATE=1; DO_RELOAD=1
for a in "$@"; do
case "$a" in
--no-migrate) DO_MIGRATE=0 ;;
--code-only) DO_MIGRATE=0; DO_RELOAD=0 ;;
esac
done
say "DEPLOY start → $HOST:$RDIR"
# ── 1. CODE rsync ────────────────────────────────────────────────────────────
# Exclude prod-canonical data/ (deals.json etc. are grown BY prod crons), prod's
# own .env, and all junk. --delete is intentionally OFF so we never remove a
# prod-only file. Code only.
rsync -az --checksum -e "ssh ${SSHOPTS[*]}" \
--exclude '.git/' --exclude 'node_modules/' --exclude 'data/' \
--exclude '.env' --exclude '.env.*' --exclude 'logs/' --exclude 'tmp/' \
--exclude '*.log' --exclude '*.jar' --exclude '.DS_Store' \
"$SRC/" "$HOST:$RDIR/" >> "$LOG" 2>&1
say " ✓ code synced"
# ── 2. Pending migrations (superuser) ────────────────────────────────────────
# Apply, IN FILENAME ORDER, any *.sql in src/pr/migrations/ not yet in
# pr_migrations — as the postgres superuser (root reads the file via stdin, the
# postgres role executes the DDL). Records each on success. Forward-only.
if [ "$DO_MIGRATE" = 1 ]; then
ssh "${SSHOPTS[@]}" "$HOST" bash -s <<'REMOTE' >> "$LOG" 2>&1
set -euo pipefail
cd /root/public-projects/rentv/src/pr/migrations
for f in $(ls *.sql | sort); do
applied=$(sudo -u postgres psql -d rentv_pr -tAc "SELECT 1 FROM pr_migrations WHERE name='$f'" 2>/dev/null || echo)
if [ "$applied" != "1" ]; then
echo " applying migration $f"
sudo -u postgres psql -d rentv_pr -v ON_ERROR_STOP=1 < "$f"
sudo -u postgres psql -d rentv_pr -c "INSERT INTO pr_migrations(name) VALUES ('$f') ON CONFLICT DO NOTHING" >/dev/null
fi
done
echo " ✓ migrations up to date"
REMOTE
say " ✓ migrations applied (if any)"
fi
# ── 3. pm2 reload ────────────────────────────────────────────────────────────
# Reload each proc SEPARATELY — a combined `pm2 reload a b` was observed to skip
# the second app. rentv is fork-mode, so reload = a brief restart (handled by the
# retrying health check below).
if [ "$DO_RELOAD" = 1 ]; then
ssh "${SSHOPTS[@]}" "$HOST" 'pm2 reload rentv --update-env >/dev/null 2>&1; pm2 reload rentv-pr-worker --update-env >/dev/null 2>&1; echo reloaded' >> "$LOG" 2>&1
say " ✓ pm2 reloaded (rentv + rentv-pr-worker)"
fi
# ── 4. Health check (retry through the fork-mode reload window) ──────────────
# Anon hit is expected to 401 (the auth gate) — that means the app is up + gated.
CODE=000
for i in 1 2 3 4 5 6; do
sleep 2
CODE=$(ssh "${SSHOPTS[@]}" "$HOST" "curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:$PORT/api/pr/health" 2>/dev/null || echo 000)
{ [ "$CODE" = 401 ] || [ "$CODE" = 200 ]; } && break
done
if [ "$CODE" = 401 ] || [ "$CODE" = 200 ]; then
say "DEPLOY ok — app healthy (health=$CODE)"
else
say "DEPLOY WARN — health=$CODE (expected 401/200); check pm2 logs rentv"
exit 1
fi