#!/usr/bin/env bash # Deploy on the target Linux Docker host with configuration injected by Gitea. # Returns nonzero on configuration/build/migration/health failure. Never removes data. set -Eeuo pipefail umask 077 cd "$(dirname "${BASH_SOURCE[0]}")/.." # Fail before touching the host; never read a checkout's local .env file. for key in WQ_EMAIL WQ_PASSWORD DATABASE_URL ADMIN_PASSWORD ENCRYPTION_KEY DATABASE_NETWORK PUBLIC_ORIGIN; do if [[ -z "${!key:-}" ]]; then echo "Missing required Gitea configuration: $key" >&2 exit 1 fi done state_dir="${DEPLOY_STATE_DIR:-/opt/wq-alpha}" if [[ "$state_dir" != /* || ! -d "$state_dir" || ! -w "$state_dir" ]]; then echo 'DEPLOY_STATE_DIR must be an existing writable absolute directory.' >&2 exit 1 fi command -v flock >/dev/null # Stable across checkouts; stores only a lock and release metadata, never credentials. exec 9>"$state_dir/deploy.lock" flock -n 9 || { echo 'Another production deployment is running.' >&2; exit 1; } export DEPLOY_TAG="${DEPLOY_TAG:-$(git rev-parse HEAD)}" compose=(docker compose --env-file /dev/null -p wq-alpha-production -f compose.production.yaml) trap 'rc=$?; "${compose[@]}" --profile jobs ps -a || true; exit "$rc"' EXIT "${compose[@]}" config --quiet "${compose[@]}" --profile jobs config --quiet "${compose[@]}" build backend web # Validate secrets and DB connectivity before interrupting the running version. "${compose[@]}" run --rm --no-deps backend python -c ' import asyncio from app.config import Settings from sqlalchemy import text from sqlalchemy.ext.asyncio import create_async_engine async def check(): settings = Settings() engine = create_async_engine(settings.database_url) try: async with engine.connect() as connection: await connection.execute(text("SELECT 1")) finally: await engine.dispose() try: asyncio.run(check()) except Exception: raise SystemExit("Production configuration/database preflight failed; check env and database access.") from None ' # Record immutable image references before switching; do not prune old images. previous_images=$("${compose[@]}" images --quiet) if [[ -n "$previous_images" ]]; then docker image inspect --format '{{.Id}} {{json .RepoTags}}' $previous_images > "$state_dir/previous-images.txt" fi "${compose[@]}" stop web backend "${compose[@]}" --profile jobs run --rm --no-deps migrate "${compose[@]}" up -d --no-build --remove-orphans --wait --wait-timeout 180 backend web printf '%s\n' "$DEPLOY_TAG" > "$state_dir/current-release.txt" echo "Production is healthy; release $DEPLOY_TAG"