Files
xiaoxia-saas/.gitea/workflows/deploy.yml
T
xiaoxia bcb1663de4
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 138h40m13s
CI/CD Pipeline / Frontend Lint (push) Failing after 138h40m24s
CI/CD Pipeline / Validate Code Quality And Tests (push) Failing after 138h40m24s
ci: 启用BuildKit分布式缓存+Gitea Registry,优化构建速度
- 拆分 requirements.txt: 稳定大包放 requirements-base.txt(numpy/scipy/opencv/Pillow/sqlalchemy 等),业务依赖留 requirements.txt
- 优化 Dockerfile: 分两层安装依赖,基础层缓存命中率高
- CI 改用 docker buildx build,缓存存 Gitea Container Registry
- 启用 Gitea Packages/Container Registry(git.xiaoxiajianji.com)
- 镜像仍推内网 Registry(172.30.18.198:5000),缓存独立存 Gitea Registry
2026-07-03 18:13:49 +08:00

477 lines
24 KiB
YAML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Deploy
on:
push:
branches: [ main, develop, "feature/**" ]
tags:
- 'v*'
jobs:
deploy-staging:
name: Deploy Staging
runs-on: saas
if: github.ref_name == 'main' || github.ref_name == 'develop' || startsWith(github.ref_name, 'feature/')
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -eu
python3 - <<'PY'
import io, os, tarfile, urllib.request
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
with urllib.request.urlopen(request, timeout=120) as response:
archive = response.read()
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
for member in tar.getmembers():
name = member.name
if name == root_prefix[:-1]:
continue
if name.startswith(root_prefix):
member.name = name[len(root_prefix):]
if member.name:
tar.extract(member, '.')
PY
- name: Install SSH client
shell: sh
run: |
set -eu
apt-get update -qq && apt-get install -y -qq openssh-client >/dev/null 2>&1
echo "openssh-client installed"
- 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 \
--build-arg NGINX_CONF=infra/docker/nginx-staging.conf \
-t "xiaoxia-saas-web:staging-${GITHUB_SHA}" \
.
test -f apps/web/dist/index.html
- name: Build and push staging API/Worker images
shell: sh
env:
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
run: |
set -eu
REGISTRY="172.30.18.198:5000"
CACHE_REGISTRY="git.xiaoxiajianji.com/xiaoxia/xiaoxia-saas"
CACHE_BRANCH="${GITHUB_REF_NAME//\//-}"
# 登录 Gitea Registry(用于构建缓存)
echo "${GITHUB_TOKEN}" | docker login git.xiaoxiajianji.com -u xiaoxia --password-stdin
# 确保 buildx builder 可用
docker buildx use default || docker buildx create --use --name default-builder 2>/dev/null || true
# 构建 API(带分布式缓存,缓存存 Gitea Registry,镜像推内网 Registry
docker buildx build \
--cache-from "type=registry,ref=${CACHE_REGISTRY}/api-cache:${CACHE_BRANCH}" \
--cache-to "type=registry,ref=${CACHE_REGISTRY}/api-cache:${CACHE_BRANCH},mode=max" \
-f infra/docker/api.Dockerfile \
-t "${REGISTRY}/xiaoxia-saas-api:dev" \
--push \
.
# 构建 Worker(带分布式缓存)
docker buildx build \
--cache-from "type=registry,ref=${CACHE_REGISTRY}/worker-cache:${CACHE_BRANCH}" \
--cache-to "type=registry,ref=${CACHE_REGISTRY}/worker-cache:${CACHE_BRANCH},mode=max" \
-f infra/docker/worker.Dockerfile \
-t "${REGISTRY}/xiaoxia-saas-worker:dev" \
--push \
.
- 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
# Prefer host key if available (more reliable), fallback to secrets
if [ -f /root/.ssh/xiaoxia_runtime_builder ]; then
key_path="/root/.ssh/xiaoxia_runtime_builder"
echo "Using host SSH key: $key_path"
elif [ -n "${STAGING_SSH_KEY:-}" ]; then
key_path="$HOME/.ssh/id_ed25519"
printf '%s\n' "$STAGING_SSH_KEY" > "$key_path"
chmod 600 "$key_path"
echo "Using secret SSH key: $key_path"
else
echo "ERROR: No SSH key available"
exit 1
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 [ -f /root/.ssh/xiaoxia_runtime_builder ]; then
key_path="/root/.ssh/xiaoxia_runtime_builder"
elif [ -n "${STAGING_SSH_KEY:-}" ]; then
key_path="$HOME/.ssh/id_ed25519"
else
echo "ERROR: No SSH key available"
exit 1
fi
echo 'c2V0IC1ldQphcnRpZmFjdD0iL3Zhci9saWIveGlhb3hpYS1zYWFzLXN0YWdpbmcvYXJ0aWZhY3RzL3hpYW94aWEtc3RhZ2luZy0ke0dJVEhVQl9TSEF9LnRhci5neiIKaW1hZ2VfdGFyPSIvdmFyL2xpYi94aWFveGlhLXNhYXMtc3RhZ2luZy9hcnRpZmFjdHMveGlhb3hpYS13ZWItc3RhZ2luZy0ke0dJVEhVQl9TSEF9LnRhciIKdGVzdCAtZiAiJGFydGlmYWN0Igp0ZXN0IC1mICIkaW1hZ2VfdGFyIgp0ZXN0IC1mIC92YXIvbGliL3hpYW94aWEtc2Fhcy1zdGFnaW5nLy5lbnYKZG9ja2VyIGxvYWQgLWkgIiRpbWFnZV90YXIiCnJtIC1yZiAvdmFyL2xpYi94aWFveGlhLXNhYXMtc3RhZ2luZy9yZXBvCm1rZGlyIC1wIC92YXIvbGliL3hpYW94aWEtc2Fhcy1zdGFnaW5nL3JlcG8KdGFyIC14emYgIiRhcnRpZmFjdCIgLUMgL3Zhci9saWIveGlhb3hpYS1zYWFzLXN0YWdpbmcvcmVwbwp0ZXN0IC1mIC92YXIvbGliL3hpYW94aWEtc2Fhcy1zdGFnaW5nL3JlcG8vYXBwcy93ZWIvZGlzdC9pbmRleC5odG1sCmNwIC92YXIvbGliL3hpYW94aWEtc2Fhcy1zdGFnaW5nLy5lbnYgL3Zhci9saWIveGlhb3hpYS1zYWFzLXN0YWdpbmcvcmVwby8uZW52CmNobW9kICt4IC92YXIvbGliL3hpYW94aWEtc2Fhcy1zdGFnaW5nL3JlcG8vaW5mcmEvZG9ja2VyL2RlcGxveS1zdGFnaW5nLnNoCiMgRW5zdXJlIGlzb2xhdGVkIHN0YWdpbmcgbmV0d29yayBleGlzdHMgYmVmb3JlIGRlcGxveQpkb2NrZXIgbmV0d29yayBjcmVhdGUgeGlhb3hpYS1uZXQtc3RhZ2luZyAyPi9kZXYvbnVsbCB8fCB0cnVlClJFR0lTVFJZPSIxNzIuMzAuMTguMTk4OjUwMDAiIEFQSV9JTUFHRT0iJHtSRUdJU1RSWX0veGlhb3hpYS1zYWFzLWFwaTpkZXYiIFdPUktFUl9JTUFHRT0iJHtSRUdJU1RSWX0veGlhb3hpYS1zYWFzLXdvcmtlcjpkZXYiIFdFQl9JTUFHRT0ieGlhb3hpYS1zYWFzLXdlYjpzdGFnaW5nLSR7R0lUSFVCX1NIQX0iIEhPU1RfUFJFRklYPSBFTlY9c3RhZ2luZyBXRUJfUE9SVD0zMDAxIFJFQlVJTERfQkFDS0VORD0wIEJVSUxEX1dFQj0wIFJVTl9NSUdSQVRJT05TPTAgL3Zhci9saWIveGlhb3hpYS1zYWFzLXN0YWdpbmcvcmVwby9pbmZyYS9kb2NrZXIvZGVwbG95LXN0YWdpbmcuc2gKaT0wCndoaWxlIFsgIiRpIiAtbHQgMzAgXTsgZG8KICBpZiB3Z2V0IC1xTy0gaHR0cDovLzEyNy4wLjAuMTo4MDAwL2hlYWx0aDsgdGhlbgogICAgZXhpdCAwCiAgZmkKICBpPSQoKGkgKyAxKSkKICBzbGVlcCAyCmRvbmUKZXhpdCAxCg==' | base64 -d | ssh -i "$key_path" "$staging_user@$staging_host" "GITHUB_SHA='${GITHUB_SHA}' sh"
- name: Post-deploy smoke test
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 [ -f /root/.ssh/xiaoxia_runtime_builder ]; then
key_path="/root/.ssh/xiaoxia_runtime_builder"
elif [ -n "${STAGING_SSH_KEY:-}" ]; then
key_path="$HOME/.ssh/id_ed25519"
else
echo "ERROR: No SSH key available"
exit 1
fi
echo "Running post-deploy smoke tests on staging..."
# Wait for service to fully start
sleep 5
# Run smoke tests via SSH on the business host
ssh -i "$key_path" "$staging_user@$staging_host" '
echo "--- Smoke test 1: Health check ---"
HEALTH=$(curl -sf --max-time 10 http://127.0.0.1:8000/health) || {
echo "FAIL: health endpoint unreachable"
exit 1
}
echo "Health OK: $HEALTH"
echo "--- Smoke test 2: Login API (expect 401) ---"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 -X POST \
http://127.0.0.1:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d "{\"email\":\"smoke@test.com\",\"password\":\"wrong\"}")
if [ "$HTTP_CODE" != "401" ] && [ "$HTTP_CODE" != "422" ]; then
echo "FAIL: login returned HTTP $HTTP_CODE (expected 401 or 422)"
exit 1
fi
echo "Login API OK: HTTP $HTTP_CODE"
echo "--- Smoke test 3: API docs endpoint ---"
HTTP_CODE=$(curl -s -o /dev/null -w "%{http_code}" --max-time 10 http://127.0.0.1:8000/docs)
if [ "$HTTP_CODE" != "200" ]; then
echo "FAIL: /docs returned HTTP $HTTP_CODE (expected 200)"
exit 1
fi
echo "Docs endpoint OK: HTTP $HTTP_CODE"
echo "--- Smoke test 4: Network isolation verification ---"
# Verify staging containers are on the staging network
STAGING_NET=$(docker inspect xiaoxia-api-staging --format="{{json .NetworkSettings.Networks}}" 2>/dev/null)
if [ -z "$STAGING_NET" ]; then
echo "WARN: Could not inspect staging container networks (container may not exist yet)"
else
echo "Staging API container networks: $STAGING_NET"
if echo "$STAGING_NET" | grep -q "xiaoxia-net-staging"; then
echo "Network isolation OK: staging containers on xiaoxia-net-staging"
else
echo "WARN: staging containers not on expected xiaoxia-net-staging network"
echo " Current networks: $STAGING_NET"
fi
fi
# Verify cross-environment DNS isolation
# staging API should resolve to staging container, not production
STAGING_API_IP=$(docker exec xiaoxia-web-staging getent hosts xiaoxia-api-staging 2>/dev/null | awk "{print \$1}" || true)
PRODUCTION_API_IP=$(docker exec xiaoxia-web-staging getent hosts xiaoxia-api-production 2>/dev/null | awk "{print \$1}" || true)
if [ -n "$STAGING_API_IP" ]; then
echo "Staging API resolves to: $STAGING_API_IP (from web container)"
fi
if [ -n "$PRODUCTION_API_IP" ]; then
echo "FAIL: staging web container can resolve production API address ($PRODUCTION_API_IP) - network isolation broken!"
exit 1
else
echo "Network isolation OK: staging web cannot resolve xiaoxia-api-production"
fi
echo ""
echo "=== All smoke tests passed! ==="
'
staging-e2e:
name: Staging E2E Tests
runs-on: saas
if: github.ref_name == 'develop' || github.ref_name == 'main'
needs: deploy-staging
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -eu
python3 - <<'PY'
import io, os, tarfile, urllib.request
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
with urllib.request.urlopen(request, timeout=120) as response:
archive = response.read()
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
for member in tar.getmembers():
name = member.name
if name == root_prefix[:-1]:
continue
if name.startswith(root_prefix):
member.name = name[len(root_prefix):]
if member.name:
tar.extract(member, '.')
PY
- name: Run Playwright E2E against staging
shell: sh
run: |
set -eu
docker run --rm -e E2E_BASE_URL=http://127.0.0.1:3001 -e E2E_API_BASE=http://127.0.0.1:8000/api/v1 -e E2E_BROWSER_CHANNEL=chromium -v "$PWD:/workspace" -w /workspace/apps/web --network host mcr.microsoft.com/playwright:v1.45.0-jammy sh -lc 'npm ci && npx playwright test --reporter=line --project=chromium'
build-production-runtime-images:
name: Build Production Runtime Images
runs-on: saas
if: startsWith(github.ref, 'refs/tags/v')
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -eu
python3 - <<'PY'
import io, os, tarfile, urllib.request
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
with urllib.request.urlopen(request, timeout=120) as response:
archive = response.read()
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
for member in tar.getmembers():
name = member.name
if name == root_prefix[:-1]:
continue
if name.startswith(root_prefix):
member.name = name[len(root_prefix):]
if member.name:
tar.extract(member, '.')
PY
- 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 \
--build-arg NGINX_CONF=infra/docker/nginx-production.conf \
-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
# Prefer host key if available (more reliable), fallback to secrets
if [ -f /root/.ssh/xiaoxia_runtime_builder ]; then
key_path="/root/.ssh/xiaoxia_runtime_builder"
echo "Using host SSH key: $key_path"
elif [ -n "${PRODUCTION_SSH_KEY:-}" ]; then
key_path="$HOME/.ssh/id_ed25519"
printf '%s\n' "$PRODUCTION_SSH_KEY" > "$key_path"
chmod 600 "$key_path"
echo "Using secret SSH key: $key_path"
else
echo "ERROR: No SSH key available"
exit 1
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"
- name: Cleanup old Docker images
if: always()
shell: sh
run: |
set -eu
if [ -f scripts/cleanup_old_images.sh ]; then
chmod +x scripts/cleanup_old_images.sh
scripts/cleanup_old_images.sh
else
echo "Cleanup script not found, doing basic prune..."
docker image prune -f 2>/dev/null || true
fi
echo "Disk usage after cleanup:"
df -h / | tail -1
deploy-production:
name: Deploy Production
runs-on: saas
if: startsWith(github.ref, 'refs/tags/v')
needs: build-production-runtime-images
steps:
- name: Install SSH client
shell: sh
run: |
set -eu
apt-get update -qq && apt-get install -y -qq openssh-client >/dev/null 2>&1
echo "openssh-client installed"
- 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
# Prefer host key if available (more reliable), fallback to secrets
if [ -f /root/.ssh/xiaoxia_runtime_builder ]; then
key_path="/root/.ssh/xiaoxia_runtime_builder"
echo "Using host SSH key: $key_path"
elif [ -n "${PRODUCTION_SSH_KEY:-}" ]; then
key_path="$HOME/.ssh/id_ed25519"
printf '%s\n' "$PRODUCTION_SSH_KEY" > "$key_path"
chmod 600 "$key_path"
echo "Using secret SSH key: $key_path"
else
echo "ERROR: No SSH key available"
exit 1
fi
ssh-keyscan -H "$production_host" >> ~/.ssh/known_hosts
echo 'c2V0IC1ldQpyZWxlYXNlX3Rhcj0iL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vcmVsZWFzZS0ke1JFTEVBU0VfVkVSU0lPTn0udGFyLmd6Igp0ZXN0IC1mICIkcmVsZWFzZV90YXIiCnRlc3QgLWYgIi92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3J1bnRpbWUtaW1hZ2VzLSR7UkVMRUFTRV9WRVJTSU9OfS50YXIiCnRlc3QgLWYgIi92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3dlYi0ke1JFTEVBU0VfVkVSU0lPTn0udGFyIgpta2RpciAtcCAvdmFyL2xpYi94aWFveGlhLXNhYXMtcHJvZHVjdGlvbgpvbGRfYXNzZXRzX2Rpcj0iL3RtcC94aWFveGlhLXByZXZpb3VzLXdlYi1hc3NldHMtJHtSRUxFQVNFX1ZFUlNJT059IgpybSAtcmYgIiRvbGRfYXNzZXRzX2RpciIKbWtkaXIgLXAgIiRvbGRfYXNzZXRzX2RpciIKaWYgZG9ja2VyIGluc3BlY3QgeGlhb3hpYS13ZWItcHJvZHVjdGlvbiA+L2Rldi9udWxsIDI+JjE7IHRoZW4KICBkb2NrZXIgY3AgeGlhb3hpYS13ZWItcHJvZHVjdGlvbjovdXNyL3NoYXJlL25naW54L2h0bWwvYXNzZXRzLy4gIiRvbGRfYXNzZXRzX2RpciIvIDI+L2Rldi9udWxsIHx8IHRydWUKZmkKaWYgWyAtZCAvdmFyL2xpYi94aWFveGlhLXNhYXMtcHJvZHVjdGlvbi9yZXBvL2FwcHMvd2ViL2Rpc3QvYXNzZXRzIF07IHRoZW4KICBjcCAtYSAvdmFyL2xpYi94aWFveGlhLXNhYXMtcHJvZHVjdGlvbi9yZXBvL2FwcHMvd2ViL2Rpc3QvYXNzZXRzLy4gIiRvbGRfYXNzZXRzX2RpciIvCmZpCnJtIC1yZiAvdmFyL2xpYi94aWFveGlhLXNhYXMtcHJvZHVjdGlvbi9yZXBvCm1rZGlyIC1wIC92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3JlcG8KdGFyIC14emYgIiRyZWxlYXNlX3RhciIgLUMgL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vcmVwbwp0ZXN0IC1mIC92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3JlcG8vYXBwcy93ZWIvZGlzdC9pbmRleC5odG1sCmlmIFsgLWQgIiRvbGRfYXNzZXRzX2RpciIgXTsgdGhlbgogIG1rZGlyIC1wIC92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3JlcG8vYXBwcy93ZWIvZGlzdC9hc3NldHMKICBmb3IgYXNzZXQgaW4gIiRvbGRfYXNzZXRzX2RpciIvKjsgZG8KICAgIFsgLWUgIiRhc3NldCIgXSB8fCBjb250aW51ZQogICAgbmFtZT0iJChiYXNlbmFtZSAiJGFzc2V0IikiCiAgICBpZiBbICEgLWUgIi92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3JlcG8vYXBwcy93ZWIvZGlzdC9hc3NldHMvJG5hbWUiIF07IHRoZW4KICAgICAgY3AgLWEgIiRhc3NldCIgIi92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uL3JlcG8vYXBwcy93ZWIvZGlzdC9hc3NldHMvJG5hbWUiCiAgICBmaQogIGRvbmUKICBybSAtcmYgIiRvbGRfYXNzZXRzX2RpciIKZmkKdGVzdCAtZiAvdmFyL2xpYi94aWFveGlhLXNhYXMtcHJvZHVjdGlvbi8uZW52CmNwIC92YXIvbGliL3hpYW94aWEtc2Fhcy1wcm9kdWN0aW9uLy5lbnYgL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vcmVwby8uZW52CiMgRW5zdXJlIGlzb2xhdGVkIHByb2R1Y3Rpb24gbmV0d29yayBleGlzdHMgYmVmb3JlIGRlcGxveQpkb2NrZXIgbmV0d29yayBjcmVhdGUgeGlhb3hpYS1uZXQtcHJvZHVjdGlvbiAyPi9kZXYvbnVsbCB8fCB0cnVlCkhPU1RfUFJFRklYPSBFTlY9cHJvZHVjdGlvbiBXRUJfSU1BR0U9InhpYW94aWEtc2Fhcy13ZWI6JHtSRUxFQVNFX1ZFUlNJT059IiBXRUJfSU1BR0VfVEFSPSIvdmFyL2xpYi94aWFveGlhLXNhYXMtcHJvZHVjdGlvbi93ZWItJHtSRUxFQVNFX1ZFUlNJT059LnRhciIgc2ggL3Zhci9saWIveGlhb3hpYS1zYWFzLXByb2R1Y3Rpb24vcmVwby9pbmZyYS9kb2NrZXIvZGVwbG95LXByb2R1Y3Rpb24uc2gKaT0wCndoaWxlIFsgIiRpIiAtbHQgMzAgXTsgZG8KICBpZiB3Z2V0IC1xTy0gaHR0cDovLzEyNy4wLjAuMTo4MDAxL2hlYWx0aDsgdGhlbgogICAgZXhpdCAwCiAgZmkKICBpPSQoKGkgKyAxKSkKICBzbGVlcCAyCmRvbmUKZXhpdCAxCg==' | base64 -d | ssh -i "$key_path" "$production_user@$production_host" "RELEASE_VERSION='${GITHUB_REF_NAME}' sh"
production-e2e:
name: Production Browser E2E
runs-on: saas
if: startsWith(github.ref, 'refs/tags/v')
needs: deploy-production
steps:
- name: Checkout code
shell: sh
env:
GITHUB_TOKEN: ${{ github.token }}
run: |
set -eu
python3 - <<'PY'
import io, os, tarfile, urllib.request
url = f"{os.environ['GITHUB_API_URL']}/repos/{os.environ['GITHUB_REPOSITORY']}/archive/{os.environ['GITHUB_SHA']}.tar.gz"
request = urllib.request.Request(url, headers={"Authorization": f"token {os.environ['GITHUB_TOKEN']}"})
with urllib.request.urlopen(request, timeout=120) as response:
archive = response.read()
with tarfile.open(fileobj=io.BytesIO(archive), mode='r:gz') as tar:
root_prefix = tar.getmembers()[0].name.split('/', 1)[0] + '/'
for member in tar.getmembers():
name = member.name
if name == root_prefix[:-1]:
continue
if name.startswith(root_prefix):
member.name = name[len(root_prefix):]
if member.name:
tar.extract(member, '.')
PY
- 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'