|
23650
|
17799
|
6
|
5
|
438f3d17a7553218c764ffef33c45904617acd90
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777225326
|
1777225322
|
1777225326
|
|
0
|
|
0
|
Edit
Delete
|
|
23660
|
17800
|
6
|
5
|
438f3d17a7553218c764ffef33c45904617acd90
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
20378
|
4
|
1777225338
|
1777225338
|
1777225327
|
1777225338
|
|
1
|
|
0
|
Edit
Delete
|
|
23785
|
17880
|
6
|
5
|
49ceca46def207082a6f7c5bf4718306d971843a
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777247980
|
1777247900
|
1777247980
|
|
1
|
|
0
|
Edit
Delete
|
|
23799
|
17882
|
6
|
5
|
49ceca46def207082a6f7c5bf4718306d971843a
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777247985
|
1777247982
|
1777247985
|
|
0
|
|
0
|
Edit
Delete
|
|
23809
|
17883
|
6
|
5
|
49ceca46def207082a6f7c5bf4718306d971843a
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777247988
|
1777247987
|
1777247988
|
|
0
|
|
0
|
Edit
Delete
|
|
23819
|
17884
|
6
|
5
|
49ceca46def207082a6f7c5bf4718306d971843a
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
20498
|
4
|
1777248002
|
1777248003
|
1777247990
|
1777248003
|
|
1
|
|
0
|
Edit
Delete
|
|
23882
|
17895
|
6
|
5
|
856fa372e1753ba378446fc58ad6aaf31d589aaa
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777249433
|
1777249348
|
1777249433
|
|
1
|
|
0
|
Edit
Delete
|
|
23900
|
17898
|
6
|
5
|
856fa372e1753ba378446fc58ad6aaf31d589aaa
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777249441
|
1777249434
|
1777249441
|
|
0
|
|
0
|
Edit
Delete
|
|
23910
|
17899
|
6
|
5
|
856fa372e1753ba378446fc58ad6aaf31d589aaa
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
20560
|
4
|
1777249454
|
1777249454
|
1777249443
|
1777249454
|
|
1
|
|
0
|
Edit
Delete
|
|
23961
|
17905
|
6
|
5
|
8b28a0af74c8801b122de4d42a95742e2302646a
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777249825
|
1777249710
|
1777249825
|
|
1
|
|
0
|
Edit
Delete
|
|
23979
|
17908
|
6
|
5
|
8b28a0af74c8801b122de4d42a95742e2302646a
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777249859
|
1777249827
|
1777249859
|
|
0
|
|
0
|
Edit
Delete
|
|
23990
|
17910
|
6
|
5
|
8b28a0af74c8801b122de4d42a95742e2302646a
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
20610
|
4
|
1777249872
|
1777249872
|
1777249861
|
1777249872
|
|
1
|
|
0
|
Edit
Delete
|
|
24141
|
18010
|
6
|
5
|
550a263d501f27c775e72e10c2abba3b0b0d963c
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777277674
|
1777277461
|
1777277674
|
|
1
|
|
0
|
Edit
Delete
|
|
24155
|
18012
|
6
|
5
|
550a263d501f27c775e72e10c2abba3b0b0d963c
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777277710
|
1777277675
|
1777277710
|
|
0
|
|
0
|
Edit
Delete
|
|
24165
|
18013
|
6
|
5
|
550a263d501f27c775e72e10c2abba3b0b0d963c
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
20757
|
4
|
1777277722
|
1777277723
|
1777277712
|
1777277723
|
|
1
|
|
0
|
Edit
Delete
|
|
24319
|
18122
|
6
|
5
|
1f8ccb4bb738cf6ef3a139070f88fc957f2d0ba6
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777309225
|
1777309137
|
1777309225
|
|
1
|
|
0
|
Edit
Delete
|
|
24337
|
18125
|
6
|
5
|
1f8ccb4bb738cf6ef3a139070f88fc957f2d0ba6
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777309230
|
1777309227
|
1777309230
|
|
0
|
|
0
|
Edit
Delete
|
|
24347
|
18126
|
6
|
5
|
1f8ccb4bb738cf6ef3a139070f88fc957f2d0ba6
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
20909
|
4
|
1777309245
|
1777309245
|
1777309232
|
1777309245
|
|
1
|
|
0
|
Edit
Delete
|
|
24511
|
18238
|
6
|
5
|
62f424f94b745efdce16bde3aa2c73e3839a9264
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777340657
|
1777340580
|
1777340657
|
|
1
|
|
0
|
Edit
Delete
|
|
24525
|
18240
|
6
|
5
|
62f424f94b745efdce16bde3aa2c73e3839a9264
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777340664
|
1777340658
|
1777340664
|
|
0
|
|
0
|
Edit
Delete
|
|
24535
|
18241
|
6
|
5
|
62f424f94b745efdce16bde3aa2c73e3839a9264
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21069
|
4
|
1777340679
|
1777340679
|
1777340666
|
1777340679
|
|
1
|
|
0
|
Edit
Delete
|
|
24608
|
18269
|
6
|
5
|
3fa41aea52e84435c16a79dd19c6775f93236a33
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777347673
|
1777347599
|
1777347673
|
|
1
|
|
0
|
Edit
Delete
|
|
24623
|
18272
|
6
|
5
|
3fa41aea52e84435c16a79dd19c6775f93236a33
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777347683
|
1777347675
|
1777347683
|
|
0
|
|
0
|
Edit
Delete
|
|
24633
|
18273
|
6
|
5
|
3fa41aea52e84435c16a79dd19c6775f93236a33
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21139
|
4
|
1777347696
|
1777347696
|
1777347685
|
1777347696
|
|
1
|
|
0
|
Edit
Delete
|
|
24734
|
18321
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777361850
|
1777360564
|
1777361850
|
|
0
|
|
0
|
Edit
Delete
|
|
24779
|
18329
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777361960
|
1777361852
|
1777361960
|
|
1
|
|
0
|
Edit
Delete
|
|
24797
|
18332
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777361965
|
1777361962
|
1777361965
|
|
0
|
|
0
|
Edit
Delete
|
|
24807
|
18333
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21247
|
4
|
1777361978
|
1777361978
|
1777361967
|
1777361978
|
|
1
|
|
0
|
Edit
Delete
|
|
24862
|
18343
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777363342
|
1777363264
|
1777363342
|
|
1
|
|
0
|
Edit
Delete
|
|
24876
|
18345
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777363347
|
1777363344
|
1777363347
|
|
0
|
|
0
|
Edit
Delete
|
|
24886
|
18346
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777363351
|
1777363349
|
1777363351
|
|
0
|
|
0
|
Edit
Delete
|
|
24896
|
18347
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21297
|
4
|
1777363363
|
1777363364
|
1777363353
|
1777363364
|
|
1
|
|
0
|
Edit
Delete
|
|
25170
|
18568
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777427421
|
1777427165
|
1777427421
|
|
1
|
|
0
|
Edit
Delete
|
|
25184
|
18570
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777427426
|
1777427423
|
1777427426
|
|
0
|
|
0
|
Edit
Delete
|
|
25194
|
18571
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
0
|
部署到 Staging
|
0
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1777427429
|
1777427428
|
1777427429
|
|
0
|
|
0
|
Edit
Delete
|
|
25204
|
18572
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21565
|
4
|
1777427442
|
1777427442
|
1777427431
|
1777427442
|
|
1
|
|
0
|
Edit
Delete
|
|
25272
|
18594
|
6
|
5
|
468c7319f39e251cdf8eb96c3aa63f4db200a7db
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21621
|
4
|
1777432395
|
1777432395
|
1777432330
|
1777432396
|
|
1
|
|
0
|
Edit
Delete
|
|
25288
|
18598
|
6
|
5
|
468c7319f39e251cdf8eb96c3aa63f4db200a7db
|
0
|
部署到 Staging
|
1
|
name: CI/CD Deploy
"on":
# test-pipeli name: CI/CD Deploy
"on":
# test-pipeline 通过后自动触发(仅 main 分支)
workflow_run:
workflows: ["Test Pipeline"]
types: [completed]
branches: [main]
# 版本标签触发完整部署
push:
tags: ['v*']
# 手动触发
workflow_dispatch:
inputs:
environment:
description: '部署环境'
required: true
default: 'staging'
type: choice
options:
- staging
- production
- aliyun
- rollback-production
- rollback-aliyun
skip_tests:
description: '跳过测试(紧急修复)'
required: false
default: false
type: boolean
version:
description: '部署版本号(留空使用自动版本)'
required: false
type: string
env:
IMAGE_PREFIX: ${{ github.repository_owner }}/juhi
NODE_VERSION: "20"
PNPM_VERSION: "9"
REGISTRY: ghcr.io
jobs:
deploy-staging:
name: 部署到 Staging
runs-on: ubuntu-latest
if: >-
needs.gate.outputs.target_env == 'staging' || needs.gate.outputs.target_env == 'production'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.STAGING_SSH_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.STAGING_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
docker-compose.prod.yml \
scripts/ \
deploy/ \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署到 Staging
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,API_IMAGE,FRONTEND_IMAGE,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
export API_IMAGE="$API_IMAGE"
export FRONTEND_IMAGE="$FRONTEND_IMAGE"
# 拉取最新镜像
docker compose -f docker-compose.prod.yml pull api frontend
# 数据库迁移
echo "==> 数据库迁移..."
docker compose -f docker-compose.prod.yml --profile migrate run --rm migrate
if [ $? -ne 0 ]; then
echo "数据库迁移失败"
exit 1
fi
# 滚动更新 API
docker compose -f docker-compose.prod.yml up -d --no-deps api
# 健康检查
RETRY=0
MAX_RETRY=12
until curl -sf http://localhost:3000/health > /dev/null 2>&1; do
RETRY=$((RETRY + 1))
if [ $RETRY -ge $MAX_RETRY ]; then
echo "API 健康检查超时"
docker compose -f docker-compose.prod.yml logs --tail=50 api
exit 1
fi
echo " 等待 API 就绪... (${RETRY}/${MAX_RETRY})"
sleep 5
done
# 更新前端 + 重载 Nginx
docker compose -f docker-compose.prod.yml up -d --no-deps frontend
docker compose -f docker-compose.prod.yml exec -T nginx nginx -s reload 2>/dev/null || true
docker image prune -f
echo "$VERSION" > .deployed_version
echo "==> Staging 部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
DEPLOY_PATH: /opt/juhi
API_IMAGE: ${{ needs.build-and-push.outputs.api-image }}
FRONTEND_IMAGE: ${{ needs.build-and-push.outputs.frontend-image }}
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: Staging 部署验证
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
if [ -f "./scripts/post-deploy-verify.sh" ]; then
chmod +x ./scripts/post-deploy-verify.sh
./scripts/post-deploy-verify.sh --quick
else
curl -sf http://localhost:3000/health || exit 1
echo "Staging 健康检查通过"
fi
EOF
env:
HOST: ${{ secrets.STAGING_HOST }}
USER: ${{ secrets.STAGING_USER }}
timeout-minutes: "15"
permissions:
contents: read
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
21635
|
4
|
1777440714
|
1777440714
|
1777440703
|
1777440714
|
|
1
|
|
0
|
Edit
Delete
|
|
41
|
40
|
6
|
5
|
d169207d2a046f90cf861f8e805b45d3855fddc3
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
33
|
3
|
1771862353
|
1771862367
|
1771862352
|
1771862367
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
55
|
41
|
6
|
5
|
abd8cdd0283c96dc4a62fbf753d2aca2e68aa379
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
35
|
3
|
1771862368
|
1771862388
|
1771862367
|
1771862388
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
70
|
42
|
6
|
5
|
89209aba1afe21133399ce0db0a47c2729c9a4b4
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
36
|
3
|
1771862389
|
1771862411
|
1771862388
|
1771862411
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
106
|
46
|
6
|
5
|
a7c22fc0f5e2d113210bb58646dcdbf7381be245
|
0
|
检测代码变更
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1771862494
|
1771862412
|
1771862494
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
141
|
49
|
6
|
5
|
e15e357a819170a89dbcfd25fe33ea58972ae0ff
|
0
|
检测代码变更
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1771862517
|
1771862495
|
1771862517
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
175
|
51
|
6
|
5
|
c8adeb7142f7f1b57a10bfffc3de856524997227
|
0
|
检测代码变更
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1771862651
|
1771862518
|
1771862651
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
209
|
53
|
6
|
5
|
7145a8553a880d8a94356baf7bcb4e2df4631e61
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
40
|
3
|
1771862759
|
1771862764
|
1771862651
|
1771862764
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
244
|
56
|
6
|
5
|
343b38f6bfbbd7d44a1387e1351700df68013555
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
43
|
3
|
1771862768
|
1771862813
|
1771862764
|
1771862813
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
278
|
58
|
6
|
5
|
18a5bb9c67c571fc2cad6ffe61e70633bcec36a8
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
45
|
2
|
1771862815
|
1771862849
|
1771862813
|
1771862849
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
312
|
60
|
6
|
5
|
f8a1588d13917a22d3710214cee7468768c4490e
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
47
|
3
|
1771862915
|
1771862929
|
1771862907
|
1771862929
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
346
|
62
|
6
|
5
|
4e9a26e0e1c15538f2a6f033748340bb62b02af2
|
0
|
检测代码变更
|
0
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1771862945
|
1771862930
|
1771862945
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
380
|
64
|
6
|
5
|
546659ec60e857bdf77619972d1b3e4333b621ac
|
0
|
检测代码变更
|
1
|
name: CI
"on":
push:
branches: name: CI
"on":
push:
branches: [main, develop, 'feature/**', 'claude/**']
pull_request:
branches: [main, develop]
env:
NODE_VERSION: "20"
PNPM_VERSION: "8"
jobs:
detect-changes:
name: 检测代码变更
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- id: filter
uses: dorny/paths-filter@v3
with:
filters: |
backend:
- 'backend/**'
- 'shared/**'
frontend:
- 'frontend/**'
- 'shared/**'
mobile:
- 'mobile/**'
- 'shared/**'
shared:
- 'shared/**'
workflows:
- '.github/workflows/**'
outputs:
backend: ${{ steps.filter.outputs.backend }}
frontend: ${{ steps.filter.outputs.frontend }}
mobile: ${{ steps.filter.outputs.mobile }}
shared: ${{ steps.filter.outputs.shared }}
workflows: ${{ steps.filter.outputs.workflows }}
...
|
detect-changes
|
null
|
["ubuntu-latest"]
|
49
|
2
|
1771862947
|
1771862977
|
1771862945
|
1771862978
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|