|
23821
|
17884
|
6
|
5
|
49ceca46def207082a6f7c5bf4718306d971843a
|
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: "9"
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"]
|
20499
|
4
|
1777248004
|
1777248005
|
1777247990
|
1777248005
|
|
1
|
|
0
|
Edit
Delete
|
|
23884
|
17895
|
6
|
5
|
856fa372e1753ba378446fc58ad6aaf31d589aaa
|
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: "9"
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
|
1777249433
|
1777249348
|
1777249433
|
|
1
|
|
0
|
Edit
Delete
|
|
23902
|
17898
|
6
|
5
|
856fa372e1753ba378446fc58ad6aaf31d589aaa
|
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: "9"
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
|
1777249441
|
1777249434
|
1777249441
|
|
0
|
|
0
|
Edit
Delete
|
|
23912
|
17899
|
6
|
5
|
856fa372e1753ba378446fc58ad6aaf31d589aaa
|
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: "9"
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"]
|
20561
|
4
|
1777249455
|
1777249456
|
1777249443
|
1777249456
|
|
1
|
|
0
|
Edit
Delete
|
|
23963
|
17905
|
6
|
5
|
8b28a0af74c8801b122de4d42a95742e2302646a
|
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: "9"
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
|
1777249826
|
1777249710
|
1777249826
|
|
1
|
|
0
|
Edit
Delete
|
|
23981
|
17908
|
6
|
5
|
8b28a0af74c8801b122de4d42a95742e2302646a
|
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: "9"
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
|
1777249860
|
1777249827
|
1777249860
|
|
0
|
|
0
|
Edit
Delete
|
|
23992
|
17910
|
6
|
5
|
8b28a0af74c8801b122de4d42a95742e2302646a
|
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: "9"
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"]
|
20611
|
4
|
1777249874
|
1777249874
|
1777249861
|
1777249875
|
|
1
|
|
0
|
Edit
Delete
|
|
24143
|
18010
|
6
|
5
|
550a263d501f27c775e72e10c2abba3b0b0d963c
|
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: "9"
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
|
1777277674
|
1777277461
|
1777277674
|
|
1
|
|
0
|
Edit
Delete
|
|
24157
|
18012
|
6
|
5
|
550a263d501f27c775e72e10c2abba3b0b0d963c
|
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: "9"
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
|
1777277711
|
1777277675
|
1777277711
|
|
0
|
|
0
|
Edit
Delete
|
|
24167
|
18013
|
6
|
5
|
550a263d501f27c775e72e10c2abba3b0b0d963c
|
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: "9"
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"]
|
20758
|
4
|
1777277724
|
1777277725
|
1777277712
|
1777277725
|
|
1
|
|
0
|
Edit
Delete
|
|
24321
|
18122
|
6
|
5
|
1f8ccb4bb738cf6ef3a139070f88fc957f2d0ba6
|
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: "9"
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
|
1777309225
|
1777309137
|
1777309225
|
|
1
|
|
0
|
Edit
Delete
|
|
24339
|
18125
|
6
|
5
|
1f8ccb4bb738cf6ef3a139070f88fc957f2d0ba6
|
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: "9"
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
|
1777309231
|
1777309227
|
1777309231
|
|
0
|
|
0
|
Edit
Delete
|
|
24349
|
18126
|
6
|
5
|
1f8ccb4bb738cf6ef3a139070f88fc957f2d0ba6
|
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: "9"
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"]
|
20910
|
4
|
1777309247
|
1777309247
|
1777309232
|
1777309248
|
|
1
|
|
0
|
Edit
Delete
|
|
24513
|
18238
|
6
|
5
|
62f424f94b745efdce16bde3aa2c73e3839a9264
|
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: "9"
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
|
1777340657
|
1777340580
|
1777340657
|
|
1
|
|
0
|
Edit
Delete
|
|
24527
|
18240
|
6
|
5
|
62f424f94b745efdce16bde3aa2c73e3839a9264
|
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: "9"
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
|
1777340665
|
1777340658
|
1777340665
|
|
0
|
|
0
|
Edit
Delete
|
|
24537
|
18241
|
6
|
5
|
62f424f94b745efdce16bde3aa2c73e3839a9264
|
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: "9"
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"]
|
21070
|
4
|
1777340681
|
1777340681
|
1777340666
|
1777340681
|
|
1
|
|
0
|
Edit
Delete
|
|
24610
|
18269
|
6
|
5
|
3fa41aea52e84435c16a79dd19c6775f93236a33
|
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: "9"
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
|
1777347674
|
1777347599
|
1777347674
|
|
1
|
|
0
|
Edit
Delete
|
|
24625
|
18272
|
6
|
5
|
3fa41aea52e84435c16a79dd19c6775f93236a33
|
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: "9"
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
|
1777347684
|
1777347675
|
1777347684
|
|
0
|
|
0
|
Edit
Delete
|
|
24635
|
18273
|
6
|
5
|
3fa41aea52e84435c16a79dd19c6775f93236a33
|
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: "9"
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"]
|
21140
|
4
|
1777347698
|
1777347698
|
1777347685
|
1777347698
|
|
1
|
|
0
|
Edit
Delete
|
|
24736
|
18321
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
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: "9"
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
|
1777361850
|
1777360564
|
1777361850
|
|
0
|
|
0
|
Edit
Delete
|
|
24781
|
18329
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
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: "9"
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
|
1777361960
|
1777361852
|
1777361960
|
|
1
|
|
0
|
Edit
Delete
|
|
24799
|
18332
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
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: "9"
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
|
1777361966
|
1777361962
|
1777361966
|
|
0
|
|
0
|
Edit
Delete
|
|
24809
|
18333
|
6
|
5
|
6086495033147939cdd979bd15fd97d71f6c1ac3
|
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: "9"
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"]
|
21248
|
4
|
1777361980
|
1777361980
|
1777361967
|
1777361980
|
|
1
|
|
0
|
Edit
Delete
|
|
24864
|
18343
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
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: "9"
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
|
1777363343
|
1777363264
|
1777363343
|
|
1
|
|
0
|
Edit
Delete
|
|
24878
|
18345
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
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: "9"
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
|
1777363348
|
1777363344
|
1777363348
|
|
0
|
|
0
|
Edit
Delete
|
|
24888
|
18346
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
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: "9"
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
|
1777363351
|
1777363349
|
1777363351
|
|
0
|
|
0
|
Edit
Delete
|
|
24898
|
18347
|
6
|
5
|
106c45ddee8406e0190ccbba4c3dcf6703cc6117
|
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: "9"
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"]
|
21298
|
4
|
1777363365
|
1777363366
|
1777363353
|
1777363366
|
|
1
|
|
0
|
Edit
Delete
|
|
25172
|
18568
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
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: "9"
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
|
1777427422
|
1777427165
|
1777427422
|
|
1
|
|
0
|
Edit
Delete
|
|
25186
|
18570
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
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: "9"
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
|
1777427426
|
1777427423
|
1777427426
|
|
0
|
|
0
|
Edit
Delete
|
|
25196
|
18571
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
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: "9"
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
|
1777427430
|
1777427428
|
1777427430
|
|
0
|
|
0
|
Edit
Delete
|
|
25206
|
18572
|
6
|
5
|
3852c841f557d197a777c094ed1243a8198cdf8e
|
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: "9"
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"]
|
21566
|
4
|
1777427444
|
1777427444
|
1777427431
|
1777427444
|
|
1
|
|
0
|
Edit
Delete
|
|
25274
|
18594
|
6
|
5
|
468c7319f39e251cdf8eb96c3aa63f4db200a7db
|
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: "9"
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"]
|
21622
|
4
|
1777432397
|
1777432397
|
1777432330
|
1777432398
|
|
1
|
|
0
|
Edit
Delete
|
|
25290
|
18598
|
6
|
5
|
468c7319f39e251cdf8eb96c3aa63f4db200a7db
|
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: "9"
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"]
|
21636
|
4
|
1777440716
|
1777440716
|
1777440703
|
1777440716
|
|
1
|
|
0
|
Edit
Delete
|
|
6
|
5
|
2
|
2
|
7d4d920368182d2222ed4f7dd18933b433f88ec2
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: checkout@v1
- name: Prepare rsync password file
run: |
# 将密码写入文件(rsync 要求权限为 600)
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\n# 构建 rsync 目标地址\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }} # 如: 192.168.1.100
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }} # 如: ahead_rsync_user
RSYNC_MODULE: ftp # 对应 rsyncd.conf 中的 [ftp]
- name: Clean up password file (optional but safe)
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
5
|
2
|
1770800269
|
1770800277
|
1770800268
|
1770800277
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
7
|
6
|
2
|
2
|
ce36f880dce499ef2d3c804c32a1b8ba04ffbcc2
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: checkout@v1
- name: Prepare rsync password file
run: |
# 将密码写入文件(rsync 要求权限为 600)
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\n# 构建 rsync 目标地址\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }} # 如: 192.168.1.100
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }} # 如: ahead_rsync_user
RSYNC_MODULE: ftp # 对应 rsyncd.conf 中的 [ftp]
- name: Clean up password file (optional but safe)
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
6
|
2
|
1770800339
|
1770800343
|
1770800337
|
1770800344
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
8
|
7
|
2
|
2
|
a931e51a2c8c1e80aab44673de58b17e14e7b34e
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: gitea/checkout@v1
- name: Prepare rsync password file
run: |
# 将密码写入文件(rsync 要求权限为 600)
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\n# 构建 rsync 目标地址\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }} # 如: 192.168.1.100
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }} # 如: ahead_rsync_user
RSYNC_MODULE: ftp # 对应 rsyncd.conf 中的 [ftp]
- name: Clean up password file (optional but safe)
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
7
|
2
|
1770800679
|
1770800684
|
1770800679
|
1770800685
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
9
|
8
|
2
|
2
|
265f1b4a557b2a64464bc98e1e418e0e01298d31
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: gitea/checkout@v1
- name: Prepare rsync password file
run: |
# 将密码写入文件(rsync 要求权限为 600)
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\n# 构建 rsync 目标地址\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }} # 如: 192.168.1.100
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }} # 如: ahead_rsync_user
RSYNC_MODULE: ftp # 对应 rsyncd.conf 中的 [ftp]
- name: Clean up password file (optional but safe)
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
8
|
2
|
1770800735
|
1770800740
|
1770800734
|
1770800740
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
10
|
9
|
2
|
2
|
9ea10aada78365404579933ba4df771f312ad652
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/gitea/checkout@v1
- name: Prepare rsync password file
run: |
# 将密码写入文件(rsync 要求权限为 600)
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\n# 构建 rsync 目标地址\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }} # 如: 192.168.1.100
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }} # 如: ahead_rsync_user
RSYNC_MODULE: ftp # 对应 rsyncd.conf 中的 [ftp]
- name: Clean up password file (optional but safe)
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
9
|
2
|
1770801383
|
1770801388
|
1770801383
|
1770801388
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
11
|
10
|
2
|
2
|
bbba1e40297c5378b23d53b0161cc6a10581ccf1
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- uses: https://gitea.com/gitea/checkout@v1
- name: Prepare rsync password file
run: |
# 将密码写入文件(rsync 要求权限为 600)
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\n# 构建 rsync 目标地址\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }} # 如: 192.168.1.100
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }} # 如: ahead_rsync_user
RSYNC_MODULE: ftp # 对应 rsyncd.conf 中的 [ftp]
- name: Clean up password file (optional but safe)
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
10
|
2
|
1770801411
|
1770801416
|
1770801410
|
1770801416
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
12
|
11
|
2
|
2
|
86003a30fcfeb3966084629ff6b4530693a642fa
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
echo "Cloning repository: ${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}"
git init
git remote add origin "${GITHUB_SERVER_URL}/${GITHUB_REPOSITORY}.git"
git fetch --depth=1 origin "${GITHUB_SHA}"
git checkout "${GITHUB_SHA}"
env:
# Gitea Actions 自动提供这些环境变量
GITHUB_SERVER_URL: ${{ env.GITHUB_SERVER_URL }} # 会是 https://gitea.g-hi.com
GITHUB_REPOSITORY: ${{ env.GITHUB_REPOSITORY }}
GITHUB_SHA: ${{ env.GITHUB_SHA }}
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Gitea 自动生成的临时 token
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- name: Clean up password file
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
11
|
2
|
1770801593
|
1770801598
|
1770801592
|
1770801598
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
13
|
12
|
2
|
2
|
4dfa289ffffceaeca9d550f8e594eb56a3c414be
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: "echo \"\U0001F50D Cloning repository from: ${GITEA_SERVER_URL}/${GITEA_REPOSITORY}\"\n\n# 检查关键变量是否存在\nif [ -z \"$GITEA_SERVER_URL\" ] || [ -z \"$GITEA_REPOSITORY\" ] || [ -z \"$GITEA_SHA\" ]; then\n echo \"❌ Error: Missing required Gitea environment variables.\"\n exit 1\nfi\n\n# 移除 URL 中的 https:// 前缀,以便拼接 token\nHOST_AND_PATH=\"${GITEA_SERVER_URL#https://}\"\nREPO_URL=\"https://${GITEA_TOKEN}@${HOST_AND_PATH}/${GITEA_REPOSITORY}.git\"\n\ngit init\ngit remote add origin \"$REPO_URL\"\ngit fetch --depth=1 origin \"$GITEA_SHA\"\ngit checkout \"$GITEA_SHA\"\n"
env:
GITEA_SERVER_URL: ${{ env.GITEA_SERVER_URL }}
GITEA_REPOSITORY: ${{ env.GITEA_REPOSITORY }}
GITEA_SHA: ${{ env.GITEA_SHA }}
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }} # ← 必须在 Secrets 中配置
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested. Syncing all files...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync (changed files only)...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- name: Clean up password file
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
12
|
2
|
1770801845
|
1770801850
|
1770801845
|
1770801850
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
14
|
13
|
2
|
2
|
646aa1dc79636bb90dbfec0b6b9fb9aef11c7556
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 构建完整 URL
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
if [ -z "$GITHUB_REPOSITORY" ] || [ -z "$GITHUB_SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${GITHUB_REPOSITORY}.git"
echo "Cloning from: $REPO_URL (SHA: $GITHUB_SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$GITHUB_SHA"
git checkout "$GITHUB_SHA"
env:
# 从 Secrets 获取你的 Gitea 地址(避免依赖 env.GITEA_SERVER_URL)
GITEA_HOST: ${{ secrets.GITEAS_HOST }} # 例如: gitea.g-hi.com
GITHUB_REPOSITORY: ${{ env.GITHUB_REPOSITORY }}
GITHUB_SHA: ${{ env.GITHUB_SHA }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
13
|
2
|
1770802313
|
1770802318
|
1770802312
|
1770802318
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
15
|
14
|
2
|
2
|
b895cbd8b188037f0f5dbfb0fcfef056dfc36ebc
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
14
|
2
|
1770802531
|
1770802550
|
1770802529
|
1770802550
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
16
|
15
|
2
|
2
|
061edb11a5fd660934c3b57491d585077b3805cf
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: self-hosted
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["self-hosted"]
|
0
|
3
|
0
|
1770875534
|
1770874771
|
1770875534
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
17
|
16
|
2
|
2
|
062acdd73d520de0360ff9d9158e60f202a02578
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770877321
|
1770877295
|
1770877321
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
18
|
17
|
2
|
2
|
5bb99418afc48f8e77d3cdb4edd9fa1390476c57
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770877939
|
1770877350
|
1770877939
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
19
|
18
|
2
|
2
|
fbac0054c597227db514fa1c6581f5e9002a4c84
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770878173
|
1770878076
|
1770878173
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
20
|
19
|
2
|
2
|
b277cb95b0e9e11aded5a72555e5bfe6066bd7b5
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["gitea.g-hi.com/zhangjunnan/gitea-runner:node ["gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770878314
|
1770878195
|
1770878314
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
21
|
20
|
2
|
2
|
22ffb09cf279f85db485e386b24c7d3a1cdda4e6
|
0
|
deploy
|
0
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: docker://gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["docker://gitea.g-hi.com/zhangjunnan/gitea-ru ["docker://gitea.g-hi.com/zhangjunnan/gitea-runner:node20-rsync-v1"]...
|
0
|
3
|
0
|
1770878361
|
1770878332
|
1770878361
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|
|
22
|
21
|
2
|
2
|
ac9571bc40f603c832247b982b1f4181e6a3e297
|
0
|
deploy
|
1
|
name: Smart Deploy via Rsync Daemon
"on": name: Smart Deploy via Rsync Daemon
"on": [push]
jobs:
deploy:
name: deploy
runs-on: ubuntu-latest
steps:
- name: Checkout code manually
run: |
# 调试输出
echo "GITEA_HOST: $GITEA_HOST"
echo "GITHUB_REPOSITORY: ${{ github.repository }}"
echo "GITHUB_SHA: ${{ github.sha }}"
if [ -z "$GITEA_HOST" ]; then
echo "❌ GITEA_HOST secret is missing!"
exit 1
fi
# 使用 github 上下文变量
REPO="${{ github.repository }}"
SHA="${{ github.sha }}"
if [ -z "$REPO" ] || [ -z "$SHA" ]; then
echo "❌ Missing GITHUB_REPOSITORY or GITHUB_SHA"
exit 1
fi
REPO_URL="https://${GITEA_TOKEN}@${GITEA_HOST}/${REPO}.git"
echo "Cloning from: ${REPO_URL//${GITEA_TOKEN}/***REDACTED***} (SHA: $SHA)"
git init
git remote add origin "$REPO_URL"
git fetch --depth=1 origin "$SHA"
git checkout "$SHA"
env:
GITEA_HOST: ${{ secrets.GITEAS_HOST }}
GITEA_TOKEN: ${{ secrets.GITEAS_TOKEN }}
- name: Prepare rsync password file
run: |
echo "$RSYNC_PASSWORD" > /tmp/rsync.pass
chmod 600 /tmp/rsync.pass
env:
RSYNC_PASSWORD: ${{ secrets.TEST_RSYNC_SECRETS }}
- name: Get commit message and decide sync mode
run: "COMMIT_MSG=$(git log -1 --pretty=%B | head -n1)\necho \"Commit message: $COMMIT_MSG\"\n\nRSYNC_TARGET=\"rsync://${RSYNC_USER}@${RSYNC_HOST}/${RSYNC_MODULE}/\"\n\nif [[ \"$COMMIT_MSG\" == *\"全量同步\"* ]] || [[ \"$COMMIT_MSG\" == *\"full sync\"* ]]; then\n echo \"\U0001F50D Full sync requested...\"\n rsync -avz --delete \\\n --exclude='.git' \\\n --exclude='.gitea' \\\n --exclude='node_modules/' \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\nelse\n echo \"\U0001F504 Incremental sync...\"\n\n if git rev-parse HEAD~1 >/dev/null 2>&1; then\n git diff --name-only HEAD~1 HEAD > /tmp/changed.txt\n else\n find . -type f -not -path './.git/*' -not -path './.gitea/*' | sed 's|^\\./||' > /tmp/changed.txt\n fi\n\n if [ -s /tmp/changed.txt ]; then\n echo \"Files to sync:\"\n cat /tmp/changed.txt\n \n rsync -avz --relative \\\n --files-from=/tmp/changed.txt \\\n --password-file=/tmp/rsync.pass \\\n ./ \\\n \"$RSYNC_TARGET\"\n else\n echo \"✅ No files changed.\"\n fi\nfi\n"
env:
RSYNC_HOST: ${{ secrets.TEST_RSYNC_HOST }}
RSYNC_USER: ${{ secrets.TEST_RSYNC_USER }}
RSYNC_MODULE: ftp
- if: always()
name: Clean up
run: rm -f /tmp/rsync.pass
...
|
deploy
|
null
|
["ubuntu-latest"]
|
15
|
2
|
1770878387
|
1770878407
|
1770878387
|
1770878407
|
NULL
|
NULL
|
|
0
|
Edit
Delete
|