|
13409
|
9619
|
6
|
5
|
d85951e777b951a8957f68cdbf1d581dc9c91479
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11197
|
4
|
1774851830
|
1774851831
|
1774851818
|
1774851831
|
|
1
|
|
0
|
Edit
Delete
|
|
13492
|
9662
|
6
|
5
|
1b7720d8bdc8f3778e6ad14715be746db29627af
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774863622
|
1774863327
|
1774863622
|
|
1
|
|
0
|
Edit
Delete
|
|
13507
|
9665
|
6
|
5
|
1b7720d8bdc8f3778e6ad14715be746db29627af
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774863627
|
1774863623
|
1774863627
|
|
0
|
|
0
|
Edit
Delete
|
|
13517
|
9666
|
6
|
5
|
1b7720d8bdc8f3778e6ad14715be746db29627af
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11278
|
4
|
1774863640
|
1774863641
|
1774863628
|
1774863641
|
|
1
|
|
0
|
Edit
Delete
|
|
13582
|
9684
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774867944
|
1774867692
|
1774867944
|
|
0
|
|
0
|
Edit
Delete
|
|
13623
|
9689
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774868195
|
1774867945
|
1774868195
|
|
1
|
|
0
|
Edit
Delete
|
|
13638
|
9692
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774868200
|
1774868196
|
1774868200
|
|
0
|
|
0
|
Edit
Delete
|
|
13648
|
9693
|
6
|
5
|
2aeab72a37f15a2a4572f7cf32d2c5e4f430a2e8
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11351
|
4
|
1774868211
|
1774868212
|
1774868201
|
1774868212
|
|
1
|
|
0
|
Edit
Delete
|
|
13705
|
9703
|
6
|
5
|
12913d4ccde98eafffea39e5821bd5d4c15aa868
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774870953
|
1774869701
|
1774870953
|
|
0
|
|
0
|
Edit
Delete
|
|
13753
|
9711
|
6
|
5
|
6794a93c73e1f0426557604cd3ba8f93a10e82f1
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774871768
|
1774870954
|
1774871768
|
|
0
|
|
0
|
Edit
Delete
|
|
13796
|
9717
|
6
|
5
|
8a7978e163a401654f636377fc9ca790391d8779
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774875472
|
1774871769
|
1774875472
|
|
0
|
|
0
|
Edit
Delete
|
|
13856
|
9735
|
6
|
5
|
8a7978e163a401654f636377fc9ca790391d8779
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774875575
|
1774875472
|
1774875575
|
|
0
|
|
0
|
Edit
Delete
|
|
13874
|
9737
|
6
|
5
|
1ce3f3ee5a8099af3c2b00d92ab83aa31d4d803a
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774876260
|
1774875576
|
1774876260
|
|
0
|
|
0
|
Edit
Delete
|
|
13917
|
9743
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774879228
|
1774876261
|
1774879228
|
|
0
|
|
0
|
Edit
Delete
|
|
13979
|
9760
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774879495
|
1774879229
|
1774879495
|
|
0
|
|
0
|
Edit
Delete
|
|
13997
|
9763
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774880520
|
1774879496
|
1774880520
|
|
0
|
|
0
|
Edit
Delete
|
|
14011
|
9768
|
6
|
5
|
fb960500246fc412cf02a20cc0b316e9100eeb7f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11511
|
4
|
1774880541
|
1774880541
|
1774880521
|
1774880542
|
|
1
|
|
0
|
Edit
Delete
|
|
14212
|
9922
|
6
|
5
|
9901c454467cf62d4e127620f218a97bcca01629
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774927430
|
1774925151
|
1774927430
|
|
1
|
|
0
|
Edit
Delete
|
|
14233
|
9931
|
6
|
5
|
9901c454467cf62d4e127620f218a97bcca01629
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774927463
|
1774927431
|
1774927463
|
|
0
|
|
0
|
Edit
Delete
|
|
14243
|
9932
|
6
|
5
|
9901c454467cf62d4e127620f218a97bcca01629
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11716
|
4
|
1774927474
|
1774927474
|
1774927463
|
1774927474
|
|
1
|
|
0
|
Edit
Delete
|
|
14316
|
9965
|
6
|
5
|
dffb3332733fb56fd51632938c4379422125381c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774936260
|
1774936017
|
1774936260
|
|
1
|
|
0
|
Edit
Delete
|
|
14335
|
9969
|
6
|
5
|
dffb3332733fb56fd51632938c4379422125381c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774936266
|
1774936261
|
1774936266
|
|
0
|
|
0
|
Edit
Delete
|
|
14345
|
9970
|
6
|
5
|
dffb3332733fb56fd51632938c4379422125381c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11790
|
4
|
1774936277
|
1774936277
|
1774936266
|
1774936277
|
|
1
|
|
0
|
Edit
Delete
|
|
14509
|
10053
|
6
|
5
|
46635b50050ba09e31518824f56b1e2176e7b0b7
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774960750
|
1774958442
|
1774960750
|
|
1
|
|
0
|
Edit
Delete
|
|
14530
|
10062
|
6
|
5
|
46635b50050ba09e31518824f56b1e2176e7b0b7
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1774960754
|
1774960751
|
1774960754
|
|
0
|
|
0
|
Edit
Delete
|
|
14540
|
10063
|
6
|
5
|
46635b50050ba09e31518824f56b1e2176e7b0b7
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
11947
|
4
|
1774960765
|
1774960766
|
1774960755
|
1774960766
|
|
1
|
|
0
|
Edit
Delete
|
|
14947
|
10423
|
6
|
5
|
7212eb23c82b2ecae9ea1f22fa928fc6382f842d
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775067280
|
1775067123
|
1775067280
|
|
0
|
|
0
|
Edit
Delete
|
|
14961
|
10425
|
6
|
5
|
7212eb23c82b2ecae9ea1f22fa928fc6382f842d
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775068309
|
1775067280
|
1775068309
|
|
0
|
|
0
|
Edit
Delete
|
|
14975
|
10430
|
6
|
5
|
7212eb23c82b2ecae9ea1f22fa928fc6382f842d
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
12355
|
4
|
1775068328
|
1775068329
|
1775068310
|
1775068329
|
|
1
|
|
0
|
Edit
Delete
|
|
15124
|
10532
|
6
|
5
|
624893ef324e57874ecb721dfd5539eb58d49b8e
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775098641
|
1775097365
|
1775098641
|
|
1
|
|
0
|
Edit
Delete
|
|
15142
|
10538
|
6
|
5
|
624893ef324e57874ecb721dfd5539eb58d49b8e
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775098675
|
1775098642
|
1775098675
|
|
0
|
|
0
|
Edit
Delete
|
|
15152
|
10539
|
6
|
5
|
624893ef324e57874ecb721dfd5539eb58d49b8e
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
12506
|
4
|
1775098686
|
1775098686
|
1775098676
|
1775098687
|
|
1
|
|
0
|
Edit
Delete
|
|
15480
|
10816
|
6
|
5
|
9d69e1960ec649a49c0c6f307c0fc197f47ee4c4
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775184731
|
1775180541
|
1775184731
|
|
0
|
|
0
|
Edit
Delete
|
|
15546
|
10836
|
6
|
5
|
9d69e1960ec649a49c0c6f307c0fc197f47ee4c4
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775186243
|
1775184732
|
1775186243
|
|
0
|
|
0
|
Edit
Delete
|
|
15569
|
10844
|
6
|
5
|
9d69e1960ec649a49c0c6f307c0fc197f47ee4c4
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1775186342
|
1775186244
|
1775186342
|
|
0
|
|
0
|
Edit
Delete
|
|
15579
|
10845
|
6
|
5
|
9d69e1960ec649a49c0c6f307c0fc197f47ee4c4
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
ssh $USER@$HOST << EOF
cd $DEPLOY_PATH
CURRENT_VERSION=\$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "${VERSION}" > .deployed_version
echo "\$(date -Iseconds)|${VERSION}|\${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: ${VERSION}"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
12872
|
4
|
1775186357
|
1775186357
|
1775186343
|
1775186358
|
|
1
|
|
0
|
Edit
Delete
|
|
18444
|
13598
|
6
|
5
|
2e3188c85a6cfc38ac7d3643b1cbbfc2e3e850d0
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776007334
|
1776007225
|
1776007334
|
|
1
|
|
0
|
Edit
Delete
|
|
18459
|
13601
|
6
|
5
|
2e3188c85a6cfc38ac7d3643b1cbbfc2e3e850d0
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776007338
|
1776007335
|
1776007338
|
|
0
|
|
0
|
Edit
Delete
|
|
18469
|
13602
|
6
|
5
|
2e3188c85a6cfc38ac7d3643b1cbbfc2e3e850d0
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
15734
|
4
|
1776007351
|
1776007352
|
1776007339
|
1776007352
|
|
1
|
|
0
|
Edit
Delete
|
|
18531
|
13621
|
6
|
5
|
98cc8da660b8d4dba9887432490471d976c03f5f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776011890
|
1776011733
|
1776011890
|
|
1
|
|
0
|
Edit
Delete
|
|
18546
|
13624
|
6
|
5
|
98cc8da660b8d4dba9887432490471d976c03f5f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776011897
|
1776011891
|
1776011897
|
|
0
|
|
0
|
Edit
Delete
|
|
18556
|
13625
|
6
|
5
|
98cc8da660b8d4dba9887432490471d976c03f5f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
15794
|
4
|
1776011908
|
1776011909
|
1776011898
|
1776011909
|
|
1
|
|
0
|
Edit
Delete
|
|
18804
|
13817
|
6
|
5
|
d2c68b13960de626f7a8d496bf1977d263eb7931
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776067830
|
1776066558
|
1776067830
|
|
1
|
|
0
|
Edit
Delete
|
|
18822
|
13823
|
6
|
5
|
d2c68b13960de626f7a8d496bf1977d263eb7931
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776067929
|
1776067831
|
1776067929
|
|
0
|
|
0
|
Edit
Delete
|
|
18833
|
13825
|
6
|
5
|
d2c68b13960de626f7a8d496bf1977d263eb7931
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
16043
|
4
|
1776067945
|
1776067945
|
1776067930
|
1776067945
|
|
1
|
|
0
|
Edit
Delete
|
|
19241
|
14176
|
6
|
5
|
204e3356f50776130b4976cf96f4deedfe36ab5f
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776170158
|
1776170082
|
1776170158
|
|
0
|
|
0
|
Edit
Delete
|
|
19286
|
14181
|
6
|
5
|
551c4d2e9b42cd14481ec48c3b2e2526cab4d58c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776173357
|
1776170159
|
1776173357
|
|
0
|
|
0
|
Edit
Delete
|
|
19340
|
14196
|
6
|
5
|
551c4d2e9b42cd14481ec48c3b2e2526cab4d58c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776173486
|
1776173358
|
1776173486
|
|
1
|
|
0
|
Edit
Delete
|
|
19363
|
14201
|
6
|
5
|
551c4d2e9b42cd14481ec48c3b2e2526cab4d58c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
0
|
3
|
0
|
1776173495
|
1776173487
|
1776173495
|
|
0
|
|
0
|
Edit
Delete
|
|
19373
|
14202
|
6
|
5
|
551c4d2e9b42cd14481ec48c3b2e2526cab4d58c
|
0
|
部署到阿里云
|
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-aliyun:
name: 部署到阿里云
runs-on: ubuntu-latest
if: needs.gate.outputs.target_env == 'aliyun'
steps:
- uses: actions/checkout@v4
- name: 配置 SSH
uses: webfactory/ssh-agent@v0.8.0
with:
ssh-private-key: ${{ secrets.ALIYUN_SSH_PRIVATE_KEY }}
- name: 配置 SSH Known Hosts
run: |
mkdir -p ~/.ssh
chmod 700 ~/.ssh
echo "${{ secrets.ALIYUN_SSH_KNOWN_HOSTS }}" >> ~/.ssh/known_hosts
chmod 644 ~/.ssh/known_hosts
- name: 同步部署文件
run: |
rsync -avz --delete \
--exclude 'node_modules' --exclude '.git' \
--exclude 'backups' --exclude 'data' --exclude 'logs' \
docker-compose.prod.yml \
scripts/ \
deploy/ \
.env.production.example \
$USER@$HOST:$DEPLOY_PATH/
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
- name: 部署前检查
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
command -v docker &> /dev/null || { echo "Docker 未安装"; exit 1; }
docker compose version &> /dev/null || docker-compose version &> /dev/null || { echo "Docker Compose 未安装"; exit 1; }
DISK_USAGE=$(df / | tail -1 | awk '{print $5}' | sed 's/%//')
if [ "$DISK_USAGE" -gt 90 ]; then
echo "磁盘使用率过高: ${DISK_USAGE}%"
exit 1
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 数据库备份
run: |
ssh $USER@$HOST << 'EOF'
cd /opt/juhi
BACKUP_DIR="/opt/juhi/backups"
mkdir -p "$BACKUP_DIR"
TIMESTAMP=$(date +%Y%m%d_%H%M%S)
BACKUP_FILE="$BACKUP_DIR/pre_deploy_${TIMESTAMP}.sql.gz"
docker exec juhi-postgres pg_dump -U "${DB_USER:-juhi_user}" -d "${DB_NAME:-juhi_db}" | gzip > "$BACKUP_FILE"
if [ $? -eq 0 ]; then
echo "备份完成: $(du -h "$BACKUP_FILE" | cut -f1)"
else
echo "备份失败"
exit 1
fi
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete 2>/dev/null || true
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- name: 部署到阿里云
run: |
# 通过 ssh 环境变量传递,避免 heredoc 展开泄露 secret
ssh -o SendEnv=DEPLOY_PATH,VERSION $USER@$HOST << 'EOF'
cd "$DEPLOY_PATH"
CURRENT_VERSION=$(cat .deployed_version 2>/dev/null || echo "unknown")
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
docker compose -f docker-compose.prod.yml up -d --no-deps --remove-orphans api
RETRY=0
MAX_RETRY=15
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
sleep 5
done
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 --filter "until=24h"
echo "$VERSION" > .deployed_version
echo "$(date -Iseconds)|${VERSION}|${CURRENT_VERSION}" >> .deploy-history
tail -20 .deploy-history > .deploy-history.tmp && mv .deploy-history.tmp .deploy-history
echo "==> 阿里云部署完成: $VERSION"
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
DEPLOY_PATH: /opt/juhi
VERSION: ${{ needs.build-and-push.outputs.version }}
- name: 部署验证
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
curl -sf http://localhost/ || echo "前端检查跳过"
UNHEALTHY=$(docker compose -f docker-compose.prod.yml ps --format json | grep -c '"unhealthy"' || true)
if [ "$UNHEALTHY" -gt 0 ]; then
docker compose -f docker-compose.prod.yml ps
exit 1
fi
echo "阿里云部署验证通过"
fi
EOF
env:
HOST: ${{ secrets.ALIYUN_HOST }}
USER: ${{ secrets.ALIYUN_USER }}
- if: startsWith(github.ref, 'refs/tags/v')
name: 创建 GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: "true"
make_latest: "true"
timeout-minutes: "20"
permissions:
contents: write # 创建 GitHub Release 需要 write 权限
...
|
deploy-aliyun
|
["gate","build-and-push"]
|
["ubuntu-latest"]
|
16482
|
4
|
1776173506
|
1776173506
|
1776173496
|
1776173507
|
|
1
|
|
0
|
Edit
Delete
|