From 12b2eeb0509cfd15bb497321bc69ef97cf532f0f Mon Sep 17 00:00:00 2001 From: Xiaoxia AI Date: Thu, 25 Jun 2026 01:51:17 +0800 Subject: [PATCH] fix(deploy): load production web artifact on business host --- .gitea/workflows/deploy.yml | 14 ++++++++++++-- .learnings/ERRORS.md | 7 +++++++ infra/docker/deploy-production.sh | 16 ++++++++++++++-- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/.gitea/workflows/deploy.yml b/.gitea/workflows/deploy.yml index 03ab5f65e..7124d5bac 100644 --- a/.gitea/workflows/deploy.yml +++ b/.gitea/workflows/deploy.yml @@ -141,15 +141,21 @@ jobs: chmod +x scripts/build_release_images.sh scripts/build_release_images.sh "${GITHUB_REF_NAME}" - - name: Build web artifact + - name: Build production web artifact shell: sh run: | set -eu docker run --rm \ + --pull=never \ -v "$PWD:/workspace" \ -w /workspace/apps/web \ docker.m.daocloud.io/library/node:20 \ sh -lc 'npm ci && npm run build' + docker build --pull=false \ + -f infra/docker/web-artifact.Dockerfile \ + -t "xiaoxia-saas-web:${GITHUB_REF_NAME}" \ + . + test -f apps/web/dist/index.html - name: Package release source artifact shell: sh @@ -158,6 +164,7 @@ jobs: mkdir -p dist/release-artifacts tar --exclude=.git --exclude=apps/web/node_modules --exclude=./dist \ -czf "dist/release-artifacts/xiaoxia-release-${GITHUB_REF_NAME}.tar.gz" . + docker save -o "dist/release-artifacts/xiaoxia-web-${GITHUB_REF_NAME}.tar" "xiaoxia-saas-web:${GITHUB_REF_NAME}" - name: Upload runtime image and release artifacts shell: sh @@ -183,6 +190,8 @@ jobs: "$production_user@$production_host:/var/lib/xiaoxia-saas-production/runtime-images-${GITHUB_REF_NAME}.tar" scp -i "$key_path" "dist/release-artifacts/xiaoxia-release-${GITHUB_REF_NAME}.tar.gz" \ "$production_user@$production_host:/var/lib/xiaoxia-saas-production/release-${GITHUB_REF_NAME}.tar.gz" + scp -i "$key_path" "dist/release-artifacts/xiaoxia-web-${GITHUB_REF_NAME}.tar" \ + "$production_user@$production_host:/var/lib/xiaoxia-saas-production/web-${GITHUB_REF_NAME}.tar" deploy-production: name: Deploy Production @@ -217,6 +226,7 @@ jobs: release_tar="/var/lib/xiaoxia-saas-production/release-${RELEASE_VERSION}.tar.gz" test -f "$release_tar" test -f "/var/lib/xiaoxia-saas-production/runtime-images-${RELEASE_VERSION}.tar" + test -f "/var/lib/xiaoxia-saas-production/web-${RELEASE_VERSION}.tar" mkdir -p /var/lib/xiaoxia-saas-production old_assets_dir="/tmp/xiaoxia-previous-web-assets-${RELEASE_VERSION}" rm -rf "$old_assets_dir" @@ -244,7 +254,7 @@ jobs: fi test -f /var/lib/xiaoxia-saas-production/.env cp /var/lib/xiaoxia-saas-production/.env /var/lib/xiaoxia-saas-production/repo/.env - HOST_PREFIX= sh /var/lib/xiaoxia-saas-production/repo/infra/docker/deploy-production.sh + HOST_PREFIX= WEB_IMAGE="xiaoxia-saas-web:${RELEASE_VERSION}" WEB_IMAGE_TAR="/var/lib/xiaoxia-saas-production/web-${RELEASE_VERSION}.tar" sh /var/lib/xiaoxia-saas-production/repo/infra/docker/deploy-production.sh i=0 while [ "$i" -lt 30 ]; do if wget -qO- http://127.0.0.1:8001/health; then diff --git a/.learnings/ERRORS.md b/.learnings/ERRORS.md index 469e5ea3b..6349d9e2d 100644 --- a/.learnings/ERRORS.md +++ b/.learnings/ERRORS.md @@ -59,3 +59,10 @@ Recover host first, stop residual build/runner tasks, verify production/staging - Error: No config file alembic.ini found because the API container workdir is /app/apps/api while alembic.ini is /app/alembic.ini. - Fix: run docker exec -w /app xiaoxia-api-staging alembic -c alembic.ini upgrade head for lightweight staging migrations. + +## 2026-06-25 - Windows workspace has no local sh/bash + +- Failed command: sh -n infra/docker/deploy-production.sh / bash -n infra/docker/deploy-production.sh on Windows host. +- Error: sh/bash command not found in the PowerShell runtime. +- Fix: run POSIX shell syntax checks via an available Linux host/container, e.g. scp to xiaoxia-server and run sh -n on a temporary file. + diff --git a/infra/docker/deploy-production.sh b/infra/docker/deploy-production.sh index fabc73832..52a146588 100755 --- a/infra/docker/deploy-production.sh +++ b/infra/docker/deploy-production.sh @@ -7,10 +7,14 @@ 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" @@ -53,8 +57,15 @@ if [ -n "$RELEASE_VERSION" ]; then 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 @@ -69,12 +80,13 @@ 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 image builds. Set ALLOW_PRODUCTION_BUILDS=true only on a dedicated build host." + 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" build --pull=false web 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 &&