feat(deploy): scripts/deploy-api-only.sh + Hetzner doc for manual code-only redeploys
Some checks failed
Some checks failed
Manual deploys had been using a bare `git pull && docker compose up -d
--build api` sequence, which works for the container itself but silently
skipped writing `.build-info`. The stale `.build-info` left
`?v=<commit-sha>` pointing at the previous deploy's SHA on every shared
JS/CSS URL — so browsers happily kept cached pre-fix assets even after
a successful rebuild. Bit us today: ~5 hours of "is this even deployed?"
debugging on the loyalty-dashboard redirect-flicker fix.
deploy.sh wasn't a substitute because it's a CI/CD script: stashes
working tree, runs alembic, restarts every service in the full profile
(db, redis, api, celery-worker, celery-beat, flower), 60s health budget.
Heavy and disruptive for an api-only hotfix.
New scripts/deploy-api-only.sh fills the gap with the narrow path:
- Refuses if working tree is dirty (no silent stash → no pop conflicts).
- git pull --ff-only.
- Writes .build-info (the critical missing step).
- docker compose -f docker-compose.yml --profile full up -d --build api
(only the api service — db/redis/celery untouched).
- Tight 30s health budget since DB doesn't need to come back up.
- Exit codes 0/1/2/3 for clean automation.
docs/deployment/hetzner-server-setup.md §16.5 split into 16.5a
(code-only — points at the new script as the default) and 16.5b
(full deploy fallback — kept the existing deploy.sh path for migrations
/ Dockerfile / docker-compose / requirements changes). §12 footnote on
.build-info refreshed to mention both scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
90
scripts/deploy-api-only.sh
Executable file
90
scripts/deploy-api-only.sh
Executable file
@@ -0,0 +1,90 @@
|
||||
#!/usr/bin/env bash
|
||||
# =============================================================================
|
||||
# Orion Manual API-Only Redeploy
|
||||
# =============================================================================
|
||||
# Usage: cd ~/apps/orion && bash scripts/deploy-api-only.sh
|
||||
#
|
||||
# Narrow, code-only redeploy of the api container for ad-hoc fixes. Compared
|
||||
# to the full scripts/deploy.sh (used by Gitea CI/CD), this script:
|
||||
# - Does NOT stash/pop local changes (fails loudly if working tree is dirty
|
||||
# so you can decide what to do, instead of silently rolling them through).
|
||||
# - Rebuilds and restarts ONLY the api service — leaves db, redis,
|
||||
# celery-worker, celery-beat, flower untouched (no Redis flush, no
|
||||
# in-flight Celery task kills, no DB downtime).
|
||||
# - Skips alembic upgrade (manual redeploys are usually code-only).
|
||||
# - Health-checks with a tighter 30s budget since the DB doesn't need to
|
||||
# come back up.
|
||||
#
|
||||
# Use this when shipping a frontend/template fix or other api-only change.
|
||||
# Use bash scripts/deploy.sh instead when running migrations, changing
|
||||
# Dockerfile / requirements.txt / docker-compose.yml, or hitting any
|
||||
# infrastructure config.
|
||||
#
|
||||
# Exit codes:
|
||||
# 0 — success
|
||||
# 1 — git pull failed (non-fast-forward, or dirty working tree blocking pull)
|
||||
# 2 — docker compose build/up failed
|
||||
# 3 — health check failed
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
COMPOSE="docker compose -f docker-compose.yml --profile full"
|
||||
HEALTH_URL="http://localhost:8001/health"
|
||||
HEALTH_RETRIES=6
|
||||
HEALTH_INTERVAL=5
|
||||
|
||||
log() { echo "[deploy-api] $(date '+%H:%M:%S') $*"; }
|
||||
|
||||
# ── 1. Refuse if working tree is dirty ──────────────────────────────────────
|
||||
# Unlike deploy.sh we do not stash, because stash+pop has a history of
|
||||
# producing conflicts when prod has hand-edited config files. Surface the
|
||||
# state so you can deal with it manually.
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
log "ERROR: working tree has local changes — refusing to deploy."
|
||||
log " Commit, stash, or revert them yourself, then re-run."
|
||||
git status --short
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 2. Pull latest code (fast-forward only) ─────────────────────────────────
|
||||
log "Pulling latest code …"
|
||||
if ! git pull --ff-only; then
|
||||
log "ERROR: git pull failed (non-fast-forward?). Resolve and re-run."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# ── 3. Write build info ─────────────────────────────────────────────────────
|
||||
# This file is bind-mounted into the api container and read by
|
||||
# app/core/build_info.py → templates_config._asset_version(). It drives
|
||||
# the ?v=<sha> cache-bust on every shared JS/CSS URL. If it stays stale,
|
||||
# browsers keep cached pre-fix assets even after a successful rebuild.
|
||||
log "Writing build info …"
|
||||
printf '{"commit":"%s","deployed_at":"%s"}\n' \
|
||||
"$(git rev-parse --short=8 HEAD)" \
|
||||
"$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
|
||||
> .build-info
|
||||
|
||||
# ── 4. Rebuild + recreate api ONLY ───────────────────────────────────────────
|
||||
log "Rebuilding api container …"
|
||||
if ! $COMPOSE up -d --build api; then
|
||||
log "ERROR: docker compose up --build api failed"
|
||||
exit 2
|
||||
fi
|
||||
|
||||
# ── 5. Health check (tight budget — DB/Redis weren't touched) ───────────────
|
||||
log "Waiting for health check ($HEALTH_URL) …"
|
||||
for i in $(seq 1 "$HEALTH_RETRIES"); do
|
||||
if curl -sf "$HEALTH_URL" > /dev/null 2>&1; then
|
||||
log "Health check passed (attempt $i/$HEALTH_RETRIES)"
|
||||
log "Deploy complete. ?v=$(git rev-parse --short=8 HEAD) now live."
|
||||
exit 0
|
||||
fi
|
||||
log "Health check attempt $i/$HEALTH_RETRIES failed, retrying in ${HEALTH_INTERVAL}s …"
|
||||
sleep "$HEALTH_INTERVAL"
|
||||
done
|
||||
|
||||
log "ERROR: health check failed after $HEALTH_RETRIES attempts"
|
||||
log " Container may still be starting — check 'docker compose --profile full ps'"
|
||||
log " and 'docker compose --profile full logs api --tail=50'."
|
||||
exit 3
|
||||
Reference in New Issue
Block a user