#!/bin/sh set -eu # ============================================ # Staging 部署脚本 - Registry 方式 # 用法:IMAGE_TAG= REGISTRY_TOKEN= sh deploy-staging.sh # ============================================ IMAGE_TAG="${IMAGE_TAG:-}" REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}" REGISTRY_USER="${REGISTRY_USER:-xiaoxia}" REGISTRY_TOKEN="${REGISTRY_TOKEN:-}" ENV_FILE="${ENV_FILE:-/var/lib/xiaoxia-saas-staging/.env}" COMPOSE_DIR="${COMPOSE_DIR:-/var/lib/xiaoxia-saas-staging/repo/infra/docker}" GENERATED_DIR="${GENERATED_DIR:-/var/lib/xiaoxia-saas-staging/generated}" if [ -z "$IMAGE_TAG" ]; then echo "ERROR: IMAGE_TAG is required" exit 1 fi test -f "$ENV_FILE" mkdir -p "$GENERATED_DIR" # ---- 登录 Registry ---- if [ -n "$REGISTRY_TOKEN" ]; then echo "Logging in to registry: $REGISTRY" printf %s "$REGISTRY_TOKEN" | docker login "$(echo $REGISTRY | cut -d/ -f1)" -u "$REGISTRY_USER" --password-stdin 2>/dev/null || { echo "WARN: docker login failed, will try to pull anyway" } fi # ---- Pull 三镜像 ---- REGISTRY_API="${REGISTRY}/xiaoxia-saas-api:${IMAGE_TAG}" REGISTRY_WORKER="${REGISTRY}/xiaoxia-saas-worker:${IMAGE_TAG}" REGISTRY_WEB="${REGISTRY}/xiaoxia-saas-web:${IMAGE_TAG}" LOCAL_API="${REGISTRY}/xiaoxia-saas-api:staging" LOCAL_WORKER="${REGISTRY}/xiaoxia-saas-worker:staging" LOCAL_WEB="${REGISTRY}/xiaoxia-saas-web:staging" echo "Pulling API image..." docker pull "$REGISTRY_API" echo "Pulling Worker image..." docker pull "$REGISTRY_WORKER" echo "Pulling Web image..." docker pull "$REGISTRY_WEB" # ---- Re-tag 成本地名 ---- docker tag "$REGISTRY_API" "$LOCAL_API" docker tag "$REGISTRY_WORKER" "$LOCAL_WORKER" docker tag "$REGISTRY_WEB" "$LOCAL_WEB" echo "All images pulled and tagged." # ---- 确保基础设施容器在运行 ---- for c in xiaoxia-postgres-staging xiaoxia-redis-staging; do if ! docker inspect "$c" >/dev/null 2>&1; then echo "ERROR: Required container not found: $c" exit 1 fi state=$(docker inspect -f {{.State.Status}} "$c") if [ "$state" != "running" ]; then echo "ERROR: Container not running: $c ($state)" exit 1 fi done # ---- 确保 staging 网络存在 ---- docker network create xiaoxia-net-staging 2>/dev/null || true # ---- 执行数据库 Migration ---- echo "Running database migrations..." docker run --rm --env-file "$ENV_FILE" --network xiaoxia-net-staging "$LOCAL_API" sh -c "cd /app && alembic upgrade head" echo "Migrations completed." # ---- 停止旧容器 ---- docker rm -f xiaoxia-api-staging 2>/dev/null || true docker rm -f xiaoxia-worker-staging 2>/dev/null || true docker rm -f xiaoxia-web-staging 2>/dev/null || true # ---- 启动 API ---- echo "Starting API container..." docker run -d \ --name xiaoxia-api-staging \ --env-file "$ENV_FILE" \ --network xiaoxia-net-staging \ -p 127.0.0.1:8000:8000 \ -e APP_ENV=staging \ -e APP_VERSION="$IMAGE_TAG" \ -e GENERATED_FILES_DIR=/app/generated \ -e GENERATED_FILES_URL_PREFIX=/generated-files \ -v "$GENERATED_DIR:/app/generated" \ --restart unless-stopped \ --label com.centurylinklabs.watchtower.enable=true \ --health-cmd "python -c \"import urllib.request; urllib.request.urlopen('http://localhost:8000/health', timeout=5)\"" \ --health-interval 30s \ --health-timeout 10s \ --health-retries 3 \ --health-start-period 40s \ "$LOCAL_API" # ---- 启动 Worker ---- echo "Starting Worker container..." docker run -d \ --name xiaoxia-worker-staging \ --env-file "$ENV_FILE" \ --network xiaoxia-net-staging \ -e APP_ENV=staging \ -e APP_VERSION="$IMAGE_TAG" \ -e WORKER_CONCURRENCY=1 \ -e WORKER_MAX_TASKS_PER_CHILD=100 \ -e GENERATED_FILES_DIR=/app/generated \ -e GENERATED_FILES_URL_PREFIX=/generated-files \ -v "$GENERATED_DIR:/app/generated" \ --restart unless-stopped \ --label com.centurylinklabs.watchtower.enable=true \ --health-cmd "sh -c \"grep -q celery /proc/1/cmdline || exit 1\"" \ --health-interval 30s \ --health-timeout 10s \ --health-retries 3 \ --health-start-period 30s \ "$LOCAL_WORKER" # ---- 启动 Web ---- # Web 镜像默认打包 production nginx.conf,staging 需要挂载 staging 配置 NGINX_CONF="${NGINX_CONF:-${COMPOSE_DIR}/nginx-staging.conf}" if [ ! -f "$NGINX_CONF" ]; then echo "WARN: nginx config not found at $NGINX_CONF, using image default" NGINX_VOLUME="" else NGINX_VOLUME="-v ${NGINX_CONF}:/etc/nginx/conf.d/default.conf:ro" fi echo "Starting Web container..." docker run -d \ --name xiaoxia-web-staging \ --network xiaoxia-net-staging \ -p 127.0.0.1:3001:80 \ --restart unless-stopped \ --label com.centurylinklabs.watchtower.enable=true \ $NGINX_VOLUME \ --health-cmd "wget --spider -q http://127.0.0.1:80" \ --health-interval 30s \ --health-timeout 5s \ --health-retries 3 \ "$LOCAL_WEB" # ---- 等待 API 健康 ---- echo "Waiting for API to become healthy..." i=0 while [ "$i" -lt 30 ]; do if curl -sf --max-time 5 http://127.0.0.1:8000/health >/dev/null 2>&1; then echo "API is healthy!" break fi i=$((i + 1)) echo " Waiting... ($i/30)" sleep 2 done if [ "$i" -ge 30 ]; then echo "ERROR: API did not become healthy within 60s" docker logs --tail 30 xiaoxia-api-staging exit 1 fi # ---- 清理旧镜像 ---- docker image prune -af --filter "until=72h" 2>/dev/null || true echo "" echo "=== Staging deployment complete ===" echo "API: http://127.0.0.1:8000" echo "Web: http://127.0.0.1:3001" echo "Version: $IMAGE_TAG" docker ps --format "table {{.Names}}\t{{.Status}}" | grep staging # Watchtower auto-update: 容器加com.centurylinklabs.watchtower.enable=true标签,用:staging tag启动