a5f1d9685d
Deploy / Staging E2E Tests (push) Has been skipped
Deploy / Build Production Runtime Images (push) Has been skipped
Deploy / Deploy Production (push) Has been skipped
Deploy / Production Browser E2E (push) Has been skipped
Deploy / Deploy Staging (push) Failing after 141h44m9s
CI/CD Pipeline / Frontend Lint (push) Failing after 141h44m18s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 141h44m18s
- compose.yml: 网络名根据 ENV 变量区分 (xiaoxia-net-staging/xiaoxia-net-production) - infra-production.yml: 使用 xiaoxia-net-production 网络 - infra.yml: 使用 xiaoxia-net-staging 网络 - 新增 nginx-staging.conf (proxy_pass -> xiaoxia-api-staging:8000) - 新增 nginx-production.conf (proxy_pass -> xiaoxia-api-production:8000) - nginx.conf 更新为 production 默认配置 - web-artifact.Dockerfile: 添加 ARG NGINX_CONF 支持构建时选择 nginx 配置 - deploy-staging.sh: 添加 ENV=staging、网络创建、nginx 配置选择 - deploy-production.sh: 添加 ENV=production、网络创建 - deploy.yml: CI 构建传入 NGINX_CONF 参数、远程部署脚本添加网络创建 - deploy.yml: 冒烟测试添加网络隔离验证
115 lines
4.7 KiB
Bash
Executable File
115 lines
4.7 KiB
Bash
Executable File
#!/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:-}"
|
|
RUNTIME_IMAGE_TAR="${RUNTIME_IMAGE_TAR:-}"
|
|
WEB_IMAGE_TAR="${WEB_IMAGE_TAR:-}"
|
|
|
|
if [ -n "$RELEASE_VERSION" ] && [ -z "$RUNTIME_IMAGE_TAR" ]; then
|
|
RUNTIME_IMAGE_TAR="$HOST_PREFIX/var/lib/xiaoxia-saas-production/runtime-images-$RELEASE_VERSION.tar"
|
|
fi
|
|
if [ -n "$RELEASE_VERSION" ] && [ -z "$WEB_IMAGE_TAR" ]; then
|
|
WEB_IMAGE_TAR="$HOST_PREFIX/var/lib/xiaoxia-saas-production/web-$RELEASE_VERSION.tar"
|
|
fi
|
|
|
|
ensure_container_running() {
|
|
name="$1"
|
|
if ! docker inspect "$name" >/dev/null 2>&1; then
|
|
echo "Required infrastructure container not found: $name"
|
|
echo "Run infra/docker/infra-production.yml first before deploying production applications."
|
|
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. Run npm run build before packaging the release artifact."
|
|
exit 1
|
|
fi
|
|
|
|
ensure_container_running xiaoxia-postgres-production
|
|
ensure_container_running xiaoxia-redis-production
|
|
|
|
cd "$COMPOSE_DIR"
|
|
if [ -n "$RELEASE_VERSION" ]; then
|
|
if [ ! -f "$RUNTIME_IMAGE_TAR" ]; then
|
|
echo "Missing production runtime image artifact: $RUNTIME_IMAGE_TAR"
|
|
echo "Build it on a dedicated build host with scripts/build_release_images.sh $RELEASE_VERSION, then upload it before production deploy."
|
|
exit 1
|
|
fi
|
|
docker load -i "$RUNTIME_IMAGE_TAR"
|
|
if [ ! -f "$WEB_IMAGE_TAR" ]; then
|
|
echo "Missing production web image artifact: $WEB_IMAGE_TAR"
|
|
echo "Build it on a dedicated build host, then upload it before production deploy."
|
|
exit 1
|
|
fi
|
|
docker load -i "$WEB_IMAGE_TAR"
|
|
export API_IMAGE="xiaoxia-saas-api:$RELEASE_VERSION"
|
|
export WORKER_IMAGE="xiaoxia-saas-worker:$RELEASE_VERSION"
|
|
export WEB_IMAGE="${WEB_IMAGE:-xiaoxia-saas-web:$RELEASE_VERSION}"
|
|
export APP_VERSION="$RELEASE_VERSION"
|
|
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
|
|
|
|
# Ensure isolated production network exists
|
|
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 API/worker/web image builds. Set ALLOW_PRODUCTION_BUILDS=true only on a dedicated build host."
|
|
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
|
|
# Recreate web after API so nginx resolves the current API container IP.
|
|
# Docker's embedded DNS is resolved by nginx at startup for this static upstream.
|
|
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
|