Files
worldquant-alpha-system/scripts/deploy-production.sh
T
yuxuanhui 99bc36439e
Deploy production / deploy (push) Failing after 8s
Refactor Gitea production deployment process to utilize SSH for remote operations
- Updated deployment specification to reflect the new architecture involving servers A, B, and C.
- Revised README to describe the new deployment method using Gitea Runner and SSH.
- Modified `compose.production.yaml` to remove build context and use image tags directly.
- Enhanced deployment documentation to clarify configuration steps and environment variable requirements.
- Introduced `deploy-remote.sh` script for handling remote deployment tasks over SSH.
- Added unit tests for deployment scripts to ensure robustness and error handling.
- Updated `deploy-production.sh` to streamline image pulling and deployment processes.
2026-09-24 23:47:07 +08:00

81 lines
3.2 KiB
Bash

#!/usr/bin/env bash
# Pull and deploy on server B with configuration injected over SSH by Gitea.
# Returns nonzero on configuration/pull/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 IMAGE_PREFIX DEPLOY_TAG REGISTRY_USERNAME REGISTRY_PASSWORD WQ_EMAIL WQ_PASSWORD DATABASE_URL ADMIN_PASSWORD ENCRYPTION_KEY PUBLIC_ORIGIN; do
if [[ -z "${!key:-}" ]]; then
echo "Missing required Gitea configuration: $key" >&2
exit 1
fi
done
compose=(docker compose --env-file /dev/null -p wq-alpha-production -f compose.production.yaml)
lock_id=""
auth_dir=""
cleanup() {
local rc=$?
"${compose[@]}" --profile jobs ps -a || true
# Remove only the lock acquired by this process, never another deployment's lock.
if [[ -n "$lock_id" ]]; then
docker rm "$lock_id" >/dev/null || true
fi
if [[ -n "$auth_dir" ]]; then
rm -rf -- "$auth_dir"
fi
exit "$rc"
}
trap cleanup EXIT
trap 'exit 130' INT
trap 'exit 143' TERM
trap 'exit 129' HUP
"${compose[@]}" config --quiet
"${compose[@]}" --profile jobs config --quiet
auth_dir=$(mktemp -d)
export DOCKER_CONFIG="$auth_dir"
printf '%s' "$REGISTRY_PASSWORD" | docker login "${IMAGE_PREFIX%%/*}" --username "$REGISTRY_USERNAME" --password-stdin
unset REGISTRY_PASSWORD
# Download both images before stopping anything; migration uses the backend image.
"${compose[@]}" pull --policy always backend web
# Docker enforces unique container names across runner jobs and checkout paths.
# The lock container is never started and receives no deployment credentials.
if ! lock_id=$(docker create --name wq-alpha-production-deploy-lock \
--label "wq.deploy.commit=$DEPLOY_TAG" \
--network none --entrypoint /bin/true "$IMAGE_PREFIX-backend:$DEPLOY_TAG"); then
echo 'Cannot acquire deployment lock; check Docker and other active deployments.' >&2
exit 1
fi
# Validate secrets and DB connectivity before interrupting the running version.
"${compose[@]}" run --rm --no-deps --pull never -T 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
echo "Previous deployment images (retain for rollback):"
docker image inspect --format '{{.Id}} {{json .RepoTags}}' $previous_images
fi
"${compose[@]}" stop web backend
"${compose[@]}" --profile jobs run --rm --no-deps --pull never -T migrate
"${compose[@]}" up -d --no-build --pull never --remove-orphans --wait --wait-timeout 180 backend web
echo "Production is healthy; release $DEPLOY_TAG"