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:
@@ -559,7 +559,7 @@ docker stats --no-stream --format "table {{.Name}}\t{{.MemUsage}}\t{{.MemPerc}}"
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! note "After the reset"
|
!!! note "After the reset"
|
||||||
`init_production.py` re-creates the four admin users with their **default** passwords (see `init_production.py:280-300`). Any admin-side configuration that lives in the `admin_settings` table (e.g. the manual SMTP overrides under `/admin/settings`) is wiped and must be re-applied. The `/health` endpoint reads `.build-info` which is only regenerated by `scripts/deploy.sh`, so after a manual reset it will report the **previous** commit; harmless but worth knowing.
|
`init_production.py` re-creates the four admin users with their **default** passwords (see `init_production.py:280-300`). Any admin-side configuration that lives in the `admin_settings` table (e.g. the manual SMTP overrides under `/admin/settings`) is wiped and must be re-applied. The `/health` endpoint reads `.build-info` which is only regenerated by `scripts/deploy.sh` or `scripts/deploy-api-only.sh` (see [Step 16.5](#165-manual-deploy)), so after a manual reset it will report the **previous** commit; harmless but worth knowing.
|
||||||
|
|
||||||
### Seeded Data Summary
|
### Seeded Data Summary
|
||||||
|
|
||||||
@@ -1197,14 +1197,51 @@ deploy:
|
|||||||
script: cd ${{ secrets.DEPLOY_PATH }} && bash scripts/deploy.sh
|
script: cd ${{ secrets.DEPLOY_PATH }} && bash scripts/deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
### 16.5 Manual Fallback
|
### 16.5 Manual Deploy
|
||||||
|
|
||||||
If CI is down, deploy manually:
|
Two manual paths, pick the right one for the change you're shipping.
|
||||||
|
|
||||||
|
#### 16.5a — Code-only fix (default for ad-hoc manual deploys)
|
||||||
|
|
||||||
|
For frontend / template / api-only changes that don't touch the Dockerfile,
|
||||||
|
requirements.txt, docker-compose.yml, or alembic migrations. Rebuilds and
|
||||||
|
restarts **only** the api container — db, redis, celery-worker, celery-beat,
|
||||||
|
flower stay running.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd ~/apps/orion && bash scripts/deploy-api-only.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
What it does (`scripts/deploy-api-only.sh`):
|
||||||
|
|
||||||
|
1. Refuses if working tree is dirty (no silent stash → no risk of pop conflicts).
|
||||||
|
2. `git pull --ff-only`.
|
||||||
|
3. **Writes `.build-info`** — this is the critical step that ensures the
|
||||||
|
`?v=<commit-sha>` cache-bust query on every shared JS/CSS URL flips to the
|
||||||
|
new SHA. Without this, browsers happily keep serving the previous
|
||||||
|
deploy's cached assets even though the new code is in the image.
|
||||||
|
4. `docker compose -f docker-compose.yml --profile full up -d --build api`.
|
||||||
|
5. Health-check with a 30s budget (tight, since the DB/Redis weren't touched).
|
||||||
|
|
||||||
|
Exit codes: `0` success, `1` git pull / dirty tree, `2` docker build/up
|
||||||
|
failed, `3` health check failed.
|
||||||
|
|
||||||
|
#### 16.5b — Full deploy (use when CI is down)
|
||||||
|
|
||||||
|
Use this when you've also changed migrations, the Dockerfile,
|
||||||
|
requirements.txt, or docker-compose.yml itself — anything that needs the
|
||||||
|
full restart-everything + migrate cycle that the CI runs. Restarts EVERY
|
||||||
|
service in the `full` profile (db, redis, api, celery-worker, celery-beat,
|
||||||
|
flower) and runs `alembic upgrade heads`.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd ~/apps/orion && bash scripts/deploy.sh
|
cd ~/apps/orion && bash scripts/deploy.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Heavier — brief DB downtime, Redis is blown away (sessions / rate-limit
|
||||||
|
counters / cached anything), in-flight Celery tasks killed — so don't use
|
||||||
|
it for code-only fixes.
|
||||||
|
|
||||||
### 16.6 Verify
|
### 16.6 Verify
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
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