|
12058
|
8970
|
6
|
5
|
03181d96b7830a1e1ec141a36a19c7a59c681262
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774682654
|
1774682196
|
1774682654
|
|
0
|
|
0
|
Edit
Delete
|
|
12099
|
8975
|
6
|
5
|
03181d96b7830a1e1ec141a36a19c7a59c681262
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774683474
|
1774682655
|
1774683474
|
|
0
|
|
0
|
Edit
Delete
|
|
12120
|
8981
|
6
|
5
|
03181d96b7830a1e1ec141a36a19c7a59c681262
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774683482
|
1774683475
|
1774683482
|
|
0
|
|
0
|
Edit
Delete
|
|
12130
|
8982
|
6
|
5
|
03181d96b7830a1e1ec141a36a19c7a59c681262
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
10234
|
4
|
1774683491
|
1774683491
|
1774683483
|
1774683492
|
|
1
|
|
0
|
Edit
Delete
|
|
12198
|
9010
|
6
|
5
|
b27f9d478a1cc664b9ca612e54c2ba37bc57b152
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774690760
|
1774690540
|
1774690760
|
|
1
|
|
0
|
Edit
Delete
|
|
12212
|
9012
|
6
|
5
|
b27f9d478a1cc664b9ca612e54c2ba37bc57b152
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774690763
|
1774690761
|
1774690763
|
|
0
|
|
0
|
Edit
Delete
|
|
12222
|
9013
|
6
|
5
|
b27f9d478a1cc664b9ca612e54c2ba37bc57b152
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
10299
|
4
|
1774690774
|
1774690775
|
1774690764
|
1774690775
|
|
1
|
|
0
|
Edit
Delete
|
|
12271
|
9022
|
6
|
5
|
f60415723c7e4450e3f307a54e65df5fbb4d38d2
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774692620
|
1774692272
|
1774692620
|
|
1
|
|
0
|
Edit
Delete
|
|
12287
|
9026
|
6
|
5
|
f60415723c7e4450e3f307a54e65df5fbb4d38d2
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774692656
|
1774692620
|
1774692656
|
|
0
|
|
0
|
Edit
Delete
|
|
12297
|
9027
|
6
|
5
|
f60415723c7e4450e3f307a54e65df5fbb4d38d2
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
10348
|
4
|
1774692667
|
1774692667
|
1774692657
|
1774692668
|
|
1
|
|
0
|
Edit
Delete
|
|
12350
|
9040
|
6
|
5
|
0c8f4a138b823e8d615f93850a64c3365e5aed15
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774695547
|
1774695320
|
1774695547
|
|
1
|
|
0
|
Edit
Delete
|
|
12364
|
9042
|
6
|
5
|
0c8f4a138b823e8d615f93850a64c3365e5aed15
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774695581
|
1774695547
|
1774695581
|
|
0
|
|
0
|
Edit
Delete
|
|
12374
|
9043
|
6
|
5
|
0c8f4a138b823e8d615f93850a64c3365e5aed15
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
10399
|
4
|
1774695592
|
1774695592
|
1774695582
|
1774695593
|
|
1
|
|
0
|
Edit
Delete
|
|
12621
|
9243
|
6
|
5
|
58e5152a38868b8f3832d9573c340a3eb60051c8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774754097
|
1774753878
|
1774754097
|
|
0
|
|
0
|
Edit
Delete
|
|
12635
|
9245
|
6
|
5
|
58e5152a38868b8f3832d9573c340a3eb60051c8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774754136
|
1774754098
|
1774754136
|
|
0
|
|
0
|
Edit
Delete
|
|
12646
|
9247
|
6
|
5
|
58e5152a38868b8f3832d9573c340a3eb60051c8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
10644
|
4
|
1774754147
|
1774754147
|
1774754136
|
1774754147
|
|
1
|
|
0
|
Edit
Delete
|
|
12997
|
9545
|
6
|
5
|
54bd92949aefc1f23668a2a27f1cf7d315589eb3
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774841760
|
1774841544
|
1774841760
|
|
1
|
|
0
|
Edit
Delete
|
|
13012
|
9548
|
6
|
5
|
54bd92949aefc1f23668a2a27f1cf7d315589eb3
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774841767
|
1774841760
|
1774841767
|
|
0
|
|
0
|
Edit
Delete
|
|
13022
|
9549
|
6
|
5
|
54bd92949aefc1f23668a2a27f1cf7d315589eb3
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
10994
|
4
|
1774841778
|
1774841778
|
1774841768
|
1774841778
|
|
1
|
|
0
|
Edit
Delete
|
|
13070
|
9557
|
6
|
5
|
1e14b41edac86496d70ac83d1cb3292fa0513af3
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774843514
|
1774843161
|
1774843514
|
|
1
|
|
0
|
Edit
Delete
|
|
13085
|
9560
|
6
|
5
|
1e14b41edac86496d70ac83d1cb3292fa0513af3
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774843519
|
1774843515
|
1774843519
|
|
0
|
|
0
|
Edit
Delete
|
|
13096
|
9562
|
6
|
5
|
1e14b41edac86496d70ac83d1cb3292fa0513af3
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11042
|
4
|
1774843530
|
1774843530
|
1774843520
|
1774843530
|
|
1
|
|
0
|
Edit
Delete
|
|
13144
|
9570
|
6
|
5
|
3ccdb3e3d8a8734cc11324a89259df948523a812
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774845147
|
1774844928
|
1774845147
|
|
0
|
|
0
|
Edit
Delete
|
|
13159
|
9573
|
6
|
5
|
3ccdb3e3d8a8734cc11324a89259df948523a812
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774845152
|
1774845148
|
1774845152
|
|
0
|
|
0
|
Edit
Delete
|
|
13169
|
9574
|
6
|
5
|
3ccdb3e3d8a8734cc11324a89259df948523a812
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11087
|
4
|
1774845162
|
1774845162
|
1774845153
|
1774845162
|
|
1
|
|
0
|
Edit
Delete
|
|
13226
|
9587
|
6
|
5
|
a8f1896ed41a547563a11d929465e7a92cdec4e9
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774847979
|
1774847842
|
1774847979
|
|
0
|
|
0
|
Edit
Delete
|
|
13266
|
9590
|
6
|
5
|
f2d6f50d81972cf5e93d657140a9a89b6e6189fb
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774848152
|
1774847980
|
1774848152
|
|
0
|
|
0
|
Edit
Delete
|
|
13307
|
9594
|
6
|
5
|
d85951e777b951a8957f68cdbf1d581dc9c91479
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774851434
|
1774848153
|
1774851434
|
|
0
|
|
0
|
Edit
Delete
|
|
13366
|
9611
|
6
|
5
|
d85951e777b951a8957f68cdbf1d581dc9c91479
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774851723
|
1774851435
|
1774851723
|
|
1
|
|
0
|
Edit
Delete
|
|
13397
|
9618
|
6
|
5
|
d85951e777b951a8957f68cdbf1d581dc9c91479
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774851817
|
1774851724
|
1774851817
|
|
0
|
|
0
|
Edit
Delete
|
|
13407
|
9619
|
6
|
5
|
d85951e777b951a8957f68cdbf1d581dc9c91479
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11196
|
4
|
1774851828
|
1774851829
|
1774851818
|
1774851829
|
|
1
|
|
0
|
Edit
Delete
|
|
13490
|
9662
|
6
|
5
|
1b7720d8bdc8f3778e6ad14715be746db29627af
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774863622
|
1774863327
|
1774863622
|
|
1
|
|
0
|
Edit
Delete
|
|
13505
|
9665
|
6
|
5
|
1b7720d8bdc8f3778e6ad14715be746db29627af
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774863627
|
1774863623
|
1774863627
|
|
0
|
|
0
|
Edit
Delete
|
|
13515
|
9666
|
6
|
5
|
1b7720d8bdc8f3778e6ad14715be746db29627af
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11277
|
4
|
1774863638
|
1774863639
|
1774863628
|
1774863639
|
|
1
|
|
0
|
Edit
Delete
|
|
13580
|
9684
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774867944
|
1774867692
|
1774867944
|
|
0
|
|
0
|
Edit
Delete
|
|
13621
|
9689
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774868195
|
1774867945
|
1774868195
|
|
1
|
|
0
|
Edit
Delete
|
|
13636
|
9692
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774868200
|
1774868196
|
1774868200
|
|
0
|
|
0
|
Edit
Delete
|
|
13646
|
9693
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11350
|
4
|
1774868209
|
1774868210
|
1774868201
|
1774868210
|
|
1
|
|
0
|
Edit
Delete
|
|
13703
|
9703
|
6
|
5
|
12913d4ccde98eafffea39e5821bd5d4c15aa868
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774870953
|
1774869701
|
1774870953
|
|
0
|
|
0
|
Edit
Delete
|
|
13751
|
9711
|
6
|
5
|
6794a93c73e1f0426557604cd3ba8f93a10e82f1
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774871768
|
1774870954
|
1774871768
|
|
0
|
|
0
|
Edit
Delete
|
|
13794
|
9717
|
6
|
5
|
8a7978e163a401654f636377fc9ca790391d8779
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774875471
|
1774871769
|
1774875471
|
|
0
|
|
0
|
Edit
Delete
|
|
13854
|
9735
|
6
|
5
|
8a7978e163a401654f636377fc9ca790391d8779
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774875575
|
1774875472
|
1774875575
|
|
0
|
|
0
|
Edit
Delete
|
|
13872
|
9737
|
6
|
5
|
1ce3f3ee5a8099af3c2b00d92ab83aa31d4d803a
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774876260
|
1774875576
|
1774876260
|
|
0
|
|
0
|
Edit
Delete
|
|
13915
|
9743
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774879228
|
1774876261
|
1774879228
|
|
0
|
|
0
|
Edit
Delete
|
|
13977
|
9760
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774879495
|
1774879229
|
1774879495
|
|
0
|
|
0
|
Edit
Delete
|
|
13995
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
14009
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11510
|
4
|
1774880539
|
1774880539
|
1774880521
|
1774880540
|
|
1
|
|
0
|
Edit
Delete
|
|
14210
|
9922
|
6
|
5
|
9901c454467cf62d4e127620f218a97bcca01629
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774927430
|
1774925151
|
1774927430
|
|
1
|
|
0
|
Edit
Delete
|
|
14231
|
9931
|
6
|
5
|
9901c454467cf62d4e127620f218a97bcca01629
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774927462
|
1774927431
|
1774927462
|
|
0
|
|
0
|
Edit
Delete
|
|
14241
|
9932
|
6
|
5
|
9901c454467cf62d4e127620f218a97bcca01629
|
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: "8"
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
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 $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"
...
|
deploy-staging
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11715
|
4
|
1774927472
|
1774927472
|
1774927463
|
1774927472
|
|
1
|
|
0
|
Edit
Delete
|