thermograph/deploy/deploy.sh

81 lines
3.2 KiB
Bash
Executable file

#!/usr/bin/env bash
# Pull the latest code and roll the docker-compose stack (backend + frontend +
# PostgreSQL). Run on the VPS — the Forgejo 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 backend 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
# backend 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 (backend:/app/logs/warm-cities.log)"
docker compose exec -d backend 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 backend 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"
# Registry-pull cutover (repo-split Stage 6): pull the tag build-push.yml
# already pushed for this exact commit instead of building in place. Mirrors
# build-push.yml's own tag computation (sha-<12 hex>) so this always resolves
# to "whatever HEAD of this branch built to" -- no separate promotion step.
REGISTRY_HOST="${REGISTRY_HOST:-git.thermograph.org}"
IMAGE_PATH="${IMAGE_PATH:-emi/thermograph/app}"
export IMAGE_TAG="sha-$(git rev-parse --short=12 HEAD)"
export REGISTRY_HOST IMAGE_PATH
echo "==> Logging in to the registry ($REGISTRY_HOST)"
echo "$REGISTRY_TOKEN" | docker login "$REGISTRY_HOST" --username emi --password-stdin
echo "==> Pulling images ($IMAGE_TAG)"
docker compose pull backend frontend
# Schema migrations run inside the backend 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. frontend has no
# migrations (stateless).
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 backend || true
docker compose logs --tail=50 frontend || true
exit 1