9dcb6231f4
CI/CD Pipeline / Validate Code Quality And Tests (pull_request) Waiting to run
Tests / test (pull_request) Waiting to run
Tests / lint (pull_request) Waiting to run
CI/CD Pipeline / Validate Code Quality And Tests (push) Has been cancelled
Deploy / Deploy Staging (push) Has been cancelled
Deploy / Build Production Runtime Images (push) Has been cancelled
Deploy / Deploy Production (push) Has been cancelled
Deploy / Production Browser E2E (push) Has been cancelled
299 lines
12 KiB
YAML
299 lines
12 KiB
YAML
name: Deploy
|
|
|
|
on:
|
|
push:
|
|
branches: [ main, develop ]
|
|
tags:
|
|
- 'v*'
|
|
|
|
jobs:
|
|
deploy-staging:
|
|
name: Deploy Staging
|
|
runs-on: runtime-builder:host
|
|
if: github.ref_name == 'main' || github.ref_name == 'develop'
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
shell: sh
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -eu
|
|
archive_url="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/archive/${GITHUB_SHA}.tar.gz"
|
|
wget --header="Authorization: token ${GITHUB_TOKEN}" -O /tmp/repo.tar.gz "$archive_url"
|
|
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C .
|
|
rm -f /tmp/repo.tar.gz
|
|
|
|
- name: Build staging 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:staging-${GITHUB_SHA}" \
|
|
.
|
|
test -f apps/web/dist/index.html
|
|
|
|
- name: Package staging release artifact
|
|
shell: sh
|
|
run: |
|
|
set -eu
|
|
rm -rf dist/staging-artifacts
|
|
mkdir -p dist/staging-artifacts
|
|
tar --exclude=.git --exclude=apps/web/node_modules --exclude=./dist \
|
|
-czf dist/staging-artifacts/xiaoxia-staging-${GITHUB_SHA}.tar.gz .
|
|
docker save -o "dist/staging-artifacts/xiaoxia-web-staging-${GITHUB_SHA}.tar" "xiaoxia-saas-web:staging-${GITHUB_SHA}"
|
|
|
|
- name: Upload staging artifact to business host
|
|
shell: sh
|
|
env:
|
|
STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
|
|
STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }}
|
|
STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
|
run: |
|
|
set -eu
|
|
staging_host="${STAGING_SSH_HOST:-47.98.113.167}"
|
|
staging_user="${STAGING_SSH_USER:-root}"
|
|
mkdir -p ~/.ssh
|
|
if [ -n "${STAGING_SSH_KEY:-}" ]; then
|
|
key_path="$HOME/.ssh/id_ed25519"
|
|
printf '%s\n' "$STAGING_SSH_KEY" > "$key_path"
|
|
chmod 600 "$key_path"
|
|
else
|
|
key_path="/root/.ssh/xiaoxia_runtime_builder"
|
|
test -f "$key_path"
|
|
fi
|
|
ssh-keyscan -H "$staging_host" >> ~/.ssh/known_hosts
|
|
ssh -i "$key_path" "$staging_user@$staging_host" "mkdir -p /var/lib/xiaoxia-saas-staging/artifacts"
|
|
scp -i "$key_path" "dist/staging-artifacts/xiaoxia-staging-${GITHUB_SHA}.tar.gz" \
|
|
"$staging_user@$staging_host:/var/lib/xiaoxia-saas-staging/artifacts/xiaoxia-staging-${GITHUB_SHA}.tar.gz"
|
|
scp -i "$key_path" "dist/staging-artifacts/xiaoxia-web-staging-${GITHUB_SHA}.tar" \
|
|
"$staging_user@$staging_host:/var/lib/xiaoxia-saas-staging/artifacts/xiaoxia-web-staging-${GITHUB_SHA}.tar"
|
|
|
|
- name: Deploy staging stack on business host
|
|
shell: sh
|
|
env:
|
|
STAGING_SSH_HOST: ${{ secrets.STAGING_SSH_HOST }}
|
|
STAGING_SSH_USER: ${{ secrets.STAGING_SSH_USER }}
|
|
STAGING_SSH_KEY: ${{ secrets.STAGING_SSH_KEY }}
|
|
run: |
|
|
set -eu
|
|
staging_host="${STAGING_SSH_HOST:-47.98.113.167}"
|
|
staging_user="${STAGING_SSH_USER:-root}"
|
|
if [ -n "${STAGING_SSH_KEY:-}" ]; then
|
|
key_path="$HOME/.ssh/id_ed25519"
|
|
else
|
|
key_path="/root/.ssh/xiaoxia_runtime_builder"
|
|
fi
|
|
ssh -i "$key_path" "$staging_user@$staging_host" "GITHUB_SHA='${GITHUB_SHA}' sh -s" <<'REMOTE_DEPLOY'
|
|
set -eu
|
|
artifact="/var/lib/xiaoxia-saas-staging/artifacts/xiaoxia-staging-${GITHUB_SHA}.tar.gz"
|
|
image_tar="/var/lib/xiaoxia-saas-staging/artifacts/xiaoxia-web-staging-${GITHUB_SHA}.tar"
|
|
test -f "$artifact"
|
|
test -f "$image_tar"
|
|
test -f /var/lib/xiaoxia-saas-staging/.env
|
|
docker load -i "$image_tar"
|
|
rm -rf /var/lib/xiaoxia-saas-staging/repo
|
|
mkdir -p /var/lib/xiaoxia-saas-staging/repo
|
|
tar -xzf "$artifact" -C /var/lib/xiaoxia-saas-staging/repo
|
|
test -f /var/lib/xiaoxia-saas-staging/repo/apps/web/dist/index.html
|
|
cp /var/lib/xiaoxia-saas-staging/.env /var/lib/xiaoxia-saas-staging/repo/.env
|
|
chmod +x /var/lib/xiaoxia-saas-staging/repo/infra/docker/deploy-staging.sh
|
|
WEB_IMAGE="xiaoxia-saas-web:staging-${GITHUB_SHA}" HOST_PREFIX= WEB_PORT=3001 REBUILD_BACKEND=0 BUILD_WEB=0 RUN_MIGRATIONS=0 /var/lib/xiaoxia-saas-staging/repo/infra/docker/deploy-staging.sh
|
|
i=0
|
|
while [ "$i" -lt 30 ]; do
|
|
if wget -qO- http://127.0.0.1:8000/health; then
|
|
exit 0
|
|
fi
|
|
i=$((i + 1))
|
|
sleep 2
|
|
done
|
|
exit 1
|
|
REMOTE_DEPLOY
|
|
|
|
build-production-runtime-images:
|
|
name: Build Production Runtime Images
|
|
runs-on: runtime-builder:host
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
shell: sh
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -eu
|
|
archive_url="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/archive/${GITHUB_SHA}.tar.gz"
|
|
wget --header="Authorization: token ${GITHUB_TOKEN}" -O /tmp/repo.tar.gz "$archive_url"
|
|
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C .
|
|
rm -f /tmp/repo.tar.gz
|
|
|
|
- name: Build runtime image artifact
|
|
shell: sh
|
|
run: |
|
|
set -eu
|
|
chmod +x scripts/build_release_images.sh
|
|
scripts/build_release_images.sh "${GITHUB_REF_NAME}"
|
|
|
|
- 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
|
|
run: |
|
|
set -eu
|
|
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
|
|
env:
|
|
PRODUCTION_SSH_HOST: ${{ secrets.PRODUCTION_SSH_HOST }}
|
|
PRODUCTION_SSH_USER: ${{ secrets.PRODUCTION_SSH_USER }}
|
|
PRODUCTION_SSH_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
|
|
run: |
|
|
set -eu
|
|
production_host="${PRODUCTION_SSH_HOST:-47.98.113.167}"
|
|
production_user="${PRODUCTION_SSH_USER:-root}"
|
|
mkdir -p ~/.ssh
|
|
if [ -n "${PRODUCTION_SSH_KEY:-}" ]; then
|
|
key_path="$HOME/.ssh/id_ed25519"
|
|
printf '%s\n' "$PRODUCTION_SSH_KEY" > "$key_path"
|
|
chmod 600 "$key_path"
|
|
else
|
|
key_path="/root/.ssh/xiaoxia_runtime_builder"
|
|
test -f "$key_path"
|
|
fi
|
|
ssh-keyscan -H "$production_host" >> ~/.ssh/known_hosts
|
|
scp -i "$key_path" "dist/release-images/xiaoxia-runtime-images-${GITHUB_REF_NAME}.tar" \
|
|
"$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
|
|
runs-on: runtime-builder:host
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
needs: build-production-runtime-images
|
|
|
|
steps:
|
|
- name: Deploy production over SSH
|
|
shell: sh
|
|
env:
|
|
PRODUCTION_SSH_HOST: ${{ secrets.PRODUCTION_SSH_HOST }}
|
|
PRODUCTION_SSH_USER: ${{ secrets.PRODUCTION_SSH_USER }}
|
|
PRODUCTION_SSH_KEY: ${{ secrets.PRODUCTION_SSH_KEY }}
|
|
run: |
|
|
set -eu
|
|
production_host="${PRODUCTION_SSH_HOST:-47.98.113.167}"
|
|
production_user="${PRODUCTION_SSH_USER:-root}"
|
|
mkdir -p ~/.ssh
|
|
if [ -n "${PRODUCTION_SSH_KEY:-}" ]; then
|
|
key_path="$HOME/.ssh/id_ed25519"
|
|
printf '%s\n' "$PRODUCTION_SSH_KEY" > "$key_path"
|
|
chmod 600 "$key_path"
|
|
else
|
|
key_path="/root/.ssh/xiaoxia_runtime_builder"
|
|
test -f "$key_path"
|
|
fi
|
|
ssh-keyscan -H "$production_host" >> ~/.ssh/known_hosts
|
|
ssh -i "$key_path" "$production_user@$production_host" \
|
|
"RELEASE_VERSION='${GITHUB_REF_NAME}' sh -s" <<'REMOTE_DEPLOY'
|
|
set -eu
|
|
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"
|
|
mkdir -p "$old_assets_dir"
|
|
if docker inspect xiaoxia-web-production >/dev/null 2>&1; then
|
|
docker cp xiaoxia-web-production:/usr/share/nginx/html/assets/. "$old_assets_dir"/ 2>/dev/null || true
|
|
fi
|
|
if [ -d /var/lib/xiaoxia-saas-production/repo/apps/web/dist/assets ]; then
|
|
cp -a /var/lib/xiaoxia-saas-production/repo/apps/web/dist/assets/. "$old_assets_dir"/
|
|
fi
|
|
rm -rf /var/lib/xiaoxia-saas-production/repo
|
|
mkdir -p /var/lib/xiaoxia-saas-production/repo
|
|
tar -xzf "$release_tar" -C /var/lib/xiaoxia-saas-production/repo
|
|
test -f /var/lib/xiaoxia-saas-production/repo/apps/web/dist/index.html
|
|
if [ -d "$old_assets_dir" ]; then
|
|
mkdir -p /var/lib/xiaoxia-saas-production/repo/apps/web/dist/assets
|
|
for asset in "$old_assets_dir"/*; do
|
|
[ -e "$asset" ] || continue
|
|
name="$(basename "$asset")"
|
|
if [ ! -e "/var/lib/xiaoxia-saas-production/repo/apps/web/dist/assets/$name" ]; then
|
|
cp -a "$asset" "/var/lib/xiaoxia-saas-production/repo/apps/web/dist/assets/$name"
|
|
fi
|
|
done
|
|
rm -rf "$old_assets_dir"
|
|
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= 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
|
|
exit 0
|
|
fi
|
|
i=$((i + 1))
|
|
sleep 2
|
|
done
|
|
exit 1
|
|
REMOTE_DEPLOY
|
|
|
|
production-e2e:
|
|
name: Production Browser E2E
|
|
runs-on: runtime-builder:host
|
|
if: startsWith(github.ref, 'refs/tags/v')
|
|
needs: deploy-production
|
|
|
|
steps:
|
|
- name: Checkout code
|
|
shell: sh
|
|
env:
|
|
GITHUB_TOKEN: ${{ github.token }}
|
|
run: |
|
|
set -eu
|
|
archive_url="${GITHUB_API_URL}/repos/${GITHUB_REPOSITORY}/archive/${GITHUB_SHA}.tar.gz"
|
|
wget --header="Authorization: token ${GITHUB_TOKEN}" -O /tmp/repo.tar.gz "$archive_url"
|
|
tar -xzf /tmp/repo.tar.gz --strip-components=1 -C .
|
|
rm -f /tmp/repo.tar.gz
|
|
|
|
- name: Run production browser E2E
|
|
shell: sh
|
|
run: |
|
|
set -eu
|
|
docker run --rm \
|
|
-e E2E_BASE_URL=https://saas.xiaoxiajianji.com \
|
|
-e E2E_API_BASE=https://api.xiaoxiajianji.com/api/v1 \
|
|
-e E2E_BROWSER_CHANNEL=chromium \
|
|
-v "$PWD:/workspace" \
|
|
-w /workspace/apps/web \
|
|
mcr.microsoft.com/playwright:v1.45.0-jammy \
|
|
sh -lc 'npm ci && npx playwright test --reporter=line --project=chromium e2e/core-upload.spec.ts e2e/core-generation.spec.ts e2e/core-titles.spec.ts'
|