#!/usr/bin/env bash # Pull the latest code and roll the docker-compose stack (app + PostgreSQL). Run # on the VPS — the GitHub Actions workflow invokes this over SSH, and you can run # it by hand too. # # ssh deploy@vps '/opt/thermograph/deploy/deploy.sh' set -euo pipefail APP_DIR="${APP_DIR:-/opt/thermograph}" BRANCH="${BRANCH:-main}" HEALTH_PORT="${HEALTH_PORT:-8137}" cd "$APP_DIR" # Secrets (POSTGRES_PASSWORD, VAPID keys, AUTH_SECRET, ...) drive compose # interpolation and are also loaded into the app container via env_file. Source # them here so a by-hand run interpolates the same as the systemd unit does. set -a; . /etc/thermograph.env 2>/dev/null || true; set +a # Pre-warm the ~750 city-page archives so /climate pages serve from cache and a # search-engine crawl never bursts the archive API quota. Detached inside the app # container (compose exec -d), idempotent (skips already-cached cells), so it # never blocks the deploy or health check and is cheap on every deploy after the # first full warm. warm_city_archives() { echo "==> Warming city-page archives in the background (app:/app/logs/warm-cities.log)" docker compose exec -d app sh -c \ 'python warm_cities.py --pace 2 >> /app/logs/warm-cities.log 2>&1' || true } # Notify IndexNow (Bing / DuckDuckGo / Yandex) of the site's URLs, but only when # the set of pages actually changed (a new/removed city) — code-only deploys skip. # Best-effort: never fails the deploy. ping_indexnow() { echo "==> Pinging IndexNow (only if the URL set changed)" local base="${THERMOGRAPH_BASE_URL:-https://thermograph.org}" docker compose exec -T app python indexnow.py --if-changed "$base" \ || echo "!! IndexNow ping failed (non-fatal)" >&2 } echo "==> Fetching $BRANCH" git fetch --prune origin "$BRANCH" git reset --hard "origin/$BRANCH" echo "==> Building images" docker compose build # Schema migrations run inside the app container's entrypoint (alembic upgrade # head) before uvicorn starts, so there's no separate migrate step or service # restart here — compose owns the process model. echo "==> Starting stack" docker compose up -d echo "==> Health check" url="http://127.0.0.1:${HEALTH_PORT}/" for i in $(seq 1 30); do if curl -fsS -o /dev/null "$url"; then echo "==> OK: $url is serving" warm_city_archives ping_indexnow exit 0 fi sleep 1 done echo "!! Health check failed for $url" >&2 docker compose ps || true docker compose logs --tail=50 app || true exit 1