#!/bin/sh set -eu HOST_PREFIX="${HOST_PREFIX-/host}" ROOT_DIR="$HOST_PREFIX/var/lib/xiaoxia-saas-production/repo" COMPOSE_DIR="$ROOT_DIR/infra/docker" ENV_FILE="$HOST_PREFIX/var/lib/xiaoxia-saas-production/.env" RELEASE_VERSION="${RELEASE_VERSION:-}" # Registry 配置 REGISTRY="${REGISTRY:-git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas}" REGISTRY_USER="${REGISTRY_USER:-xiaoxia}" REGISTRY_TOKEN="${REGISTRY_TOKEN:-}" ensure_container_running() { name="$1" if ! docker inspect "$name" >/dev/null 2>&1; then echo "Required infrastructure container not found: $name" exit 1 fi state="$(docker inspect -f "{{.State.Status}}" "$name")" if [ "$state" != "running" ]; then echo "Required infrastructure container is not running: $name ($state)" exit 1 fi } if [ ! -d "$ROOT_DIR" ] && [ -n "${PRODUCTION_REPO_FALLBACK:-}" ] && [ -d "$PRODUCTION_REPO_FALLBACK" ]; then ROOT_DIR="$PRODUCTION_REPO_FALLBACK" COMPOSE_DIR="$ROOT_DIR/infra/docker" fi cd "$ROOT_DIR" cp "$ENV_FILE" "$ROOT_DIR/.env" mkdir -p "$ROOT_DIR/apps/web/public" "$HOST_PREFIX/var/lib/xiaoxia-saas-production/generated" [ -f "$ROOT_DIR/apps/web/public/.keep" ] || printf "placeholder" > "$ROOT_DIR/apps/web/public/.keep" if [ ! -f "$ROOT_DIR/apps/web/dist/index.html" ]; then echo "Missing prebuilt web artifact: $ROOT_DIR/apps/web/dist/index.html" echo "Production deploy must not build frontend assets on the server." exit 1 fi ensure_container_running xiaoxia-postgres-production ensure_container_running xiaoxia-redis-production cd "$COMPOSE_DIR" if [ -n "$RELEASE_VERSION" ]; then export API_IMAGE="xiaoxia-saas-api:$RELEASE_VERSION" export WORKER_IMAGE="xiaoxia-saas-worker:$RELEASE_VERSION" export WEB_IMAGE="xiaoxia-saas-web:$RELEASE_VERSION" export APP_VERSION="$RELEASE_VERSION" # ---- 从 Registry Pull 镜像 ---- echo "Pulling images from registry: $REGISTRY" if [ -n "$REGISTRY_TOKEN" ]; then printf "%s" "$REGISTRY_TOKEN" | docker login git.xiaoxiajianji.com -u "$REGISTRY_USER" --password-stdin 2>/dev/null || true fi REGISTRY_API="${REGISTRY}/xiaoxia-saas-api:$RELEASE_VERSION" REGISTRY_WORKER="${REGISTRY}/xiaoxia-saas-worker:$RELEASE_VERSION" REGISTRY_WEB="${REGISTRY}/xiaoxia-saas-web:$RELEASE_VERSION" docker pull "$REGISTRY_API" docker pull "$REGISTRY_WORKER" docker pull "$REGISTRY_WEB" # Re-tag 为本地镜像名,保持 compose 兼容 docker tag "$REGISTRY_API" "$API_IMAGE" docker tag "$REGISTRY_WORKER" "$WORKER_IMAGE" docker tag "$REGISTRY_WEB" "$WEB_IMAGE" echo "All images pulled and tagged." fi export DOCKER_BUILDKIT=0 export COMPOSE_DOCKER_CLI_BUILD=0 export COMPOSE_PROJECT_NAME=xiaoxia-production-app export ENV=production export WEB_DOCKERFILE=infra/docker/web-artifact.Dockerfile export WEB_NGINX_CONF=infra/docker/nginx-production.conf docker network create xiaoxia-net-production 2>/dev/null || true export WORKER_CONCURRENCY="${WORKER_CONCURRENCY:-1}" export WORKER_MAX_TASKS_PER_CHILD="${WORKER_MAX_TASKS_PER_CHILD:-100}" if [ "${ALLOW_PRODUCTION_BUILDS:-false}" = "true" ]; then docker compose --env-file "$ENV_FILE" build --pull=false api docker compose --env-file "$ENV_FILE" build --pull=false worker docker compose --env-file "$ENV_FILE" build --pull=false web else echo "Skipping production image builds (using prebuilt images from registry)." docker image inspect "${API_IMAGE:-xiaoxia-saas-api:dev}" >/dev/null docker image inspect "${WORKER_IMAGE:-xiaoxia-saas-worker:dev}" >/dev/null docker image inspect "${WEB_IMAGE:-xiaoxia-saas-web:dev}" >/dev/null fi docker compose --env-file "$ENV_FILE" run --rm --no-deps api sh -c " cd /app && python /app/scripts/validate_release_env.py --from-environ --strict-external && alembic upgrade head " docker compose --env-file "$ENV_FILE" up -d api worker docker compose --env-file "$ENV_FILE" up -d --force-recreate web docker compose --env-file "$ENV_FILE" ps if [ -n "$RELEASE_VERSION" ] && [ -d "$HOST_PREFIX/etc/cron.d" ]; then cat > "$HOST_PREFIX/etc/cron.d/xiaoxia-production-resource-check" << EOF */5 * * * * root cd /var/lib/xiaoxia-saas-production/repo && EXPECTED_VERSION=$RELEASE_VERSION sh scripts/production_resource_check.sh >/var/log/xiaoxia-resource-check.log 2>&1 EOF fi if [ "${PRUNE_UNUSED_DOCKER_AFTER_DEPLOY:-true}" = "true" ]; then docker image prune -af --filter "until=168h" || true docker builder prune -af --filter "until=168h" || true fi