#!/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= 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