f6f2ca9a29
owner 反馈 GitHub Actions UI 上 'Build image and deploy to staging' 等英文 job 名、'Docker Build summary / Build inputs / Build records include...' 等英文 summary 文本不符合中文开发者团队约定。 ## 汉化(全部显示字段) - ci.yml: workflow name '持续集成'、job '构建/Vet/测试' 和 'golangci-lint'、 所有 step name 中文(保留 golangci-lint / Go 等工具名) - deploy-staging.yml: workflow name '测试环境部署'、job '构建镜像并部署到 测试环境'、11 个 step name 中文 - doc/development-workflow-zh.md: 同步 4 处对 'Deploy Staging' 显示名的 引用,改为 '测试环境部署 (deploy-staging.yml)' 形式,以文件名锚定 ## 关闭 docker/build-push-action 英文 summary deploy-staging.yml workflow env 加 DOCKER_BUILD_SUMMARY=false。原来跑完 build 那个英文 'Docker Build summary / Build inputs / ...' 块在 GitHub Actions UI 上不再出现。部署结果靠 Telegram 通知传递。 ## actions 升级到支持 Node 24 的版本 GitHub Runner 报 Node 20 deprecated 警告,9 月 16 日强制移除。本次一次 性升级到当前 latest: - actions/checkout v4 -> v6 - actions/setup-go v5 -> v6 - docker/setup-buildx-action v3 -> v4 - docker/build-push-action v6 -> v7 - appleboy/scp-action v0.1.7 -> v1.0.0 (正式版) - appleboy/ssh-action v1.0.3 -> v1.2.5 - golangci/golangci-lint-action v6 -> v9 (Node 24,仍支持 version: latest 和 only-new-issues: true) 不改: - workflow .yml 文件名(gh CLI / 文档引用都用文件名锚定,不动) - secret 名 / env var 名(约定俗成全大写英文) - Telegram 通知正文(本来就是中文) ## 后续 agent prompt(架构师/QA/devops)里引用 'Build, vet, test' / 'Deploy Staging' 显示名的部分,PR merge 后另外 multica agent update 同步。 不在本 PR 范围。
254 lines
8.6 KiB
YAML
254 lines
8.6 KiB
YAML
name: 测试环境部署
|
|
|
|
on:
|
|
push:
|
|
branches:
|
|
- main
|
|
- internal
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
concurrency:
|
|
group: deploy-staging-${{ github.ref }}
|
|
cancel-in-progress: false
|
|
|
|
env:
|
|
REGISTRY_HOST: ${{ vars.REGISTRY_HOST || 'registry.kxsw.us' }}
|
|
IMAGE_NAME: ${{ vars.REGISTRY_IMAGE || 'registry.kxsw.us/vpn-server' }}
|
|
STAGING_HOST: ${{ vars.STAGING_HOST || '154.12.35.103' }}
|
|
STAGING_DEPLOY_PATH: ${{ vars.STAGING_DEPLOY_PATH || '/opt/hifast-server' }}
|
|
STAGING_HEALTHCHECK_URL: ${{ vars.STAGING_HEALTHCHECK_URL || 'http://127.0.0.1:8080/v1/common/heartbeat' }}
|
|
# 关闭 docker/build-push-action 自动生成的英文 job summary
|
|
# 我们用 Telegram 通知传递部署结果,不需要 GitHub Actions 页面上那段英文
|
|
DOCKER_BUILD_SUMMARY: false
|
|
|
|
jobs:
|
|
build-and-deploy:
|
|
name: 构建镜像并部署到测试环境
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 30
|
|
|
|
steps:
|
|
- name: 检出代码
|
|
uses: actions/checkout@v6
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: 收集发布说明
|
|
id: release-notes
|
|
run: |
|
|
set -euo pipefail
|
|
|
|
if [ "${{ github.event_name }}" = "push" ] && [ "${{ github.event.before }}" != "0000000000000000000000000000000000000000" ]; then
|
|
RANGE="${{ github.event.before }}..${{ github.sha }}"
|
|
else
|
|
RANGE="-5"
|
|
fi
|
|
|
|
NOTES="$(git log "$RANGE" --pretty=format:'- %h %s' --no-merges | head -20)"
|
|
if [ -z "$NOTES" ]; then
|
|
NOTES="$(git log -1 --pretty=format:'- %h %s')"
|
|
fi
|
|
|
|
{
|
|
echo "notes<<EOF"
|
|
echo "$NOTES"
|
|
echo "EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: 配置 Go 环境
|
|
uses: actions/setup-go@v6
|
|
with:
|
|
go-version-file: go.mod
|
|
cache: true
|
|
|
|
- name: 运行测试
|
|
run: go test ./...
|
|
|
|
- name: 配置 Docker Buildx
|
|
uses: docker/setup-buildx-action@v4
|
|
|
|
- name: 构建并推送镜像
|
|
uses: docker/build-push-action@v7
|
|
with:
|
|
context: .
|
|
file: ./Dockerfile
|
|
platforms: linux/amd64
|
|
push: true
|
|
build-args: |
|
|
TARGETARCH=amd64
|
|
VERSION=${{ github.sha }}
|
|
tags: |
|
|
${{ env.IMAGE_NAME }}:${{ github.sha }}
|
|
${{ env.IMAGE_NAME }}:staging
|
|
|
|
- name: 上传 compose 配置
|
|
uses: appleboy/scp-action@v1.0.0
|
|
with:
|
|
host: ${{ env.STAGING_HOST }}
|
|
username: ${{ secrets.STAGING_SSH_USER }}
|
|
password: ${{ secrets.STAGING_SSH_PASSWORD }}
|
|
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
|
source: docker-compose.cloud.yml
|
|
target: /tmp/hifast-server-deploy/
|
|
|
|
- name: 在测试服务器上部署
|
|
uses: appleboy/ssh-action@v1.2.5
|
|
with:
|
|
host: ${{ env.STAGING_HOST }}
|
|
username: ${{ secrets.STAGING_SSH_USER }}
|
|
password: ${{ secrets.STAGING_SSH_PASSWORD }}
|
|
port: ${{ secrets.STAGING_SSH_PORT || 22 }}
|
|
timeout: 300s
|
|
command_timeout: 600s
|
|
script: |
|
|
set -euo pipefail
|
|
|
|
IMAGE_NAME="${{ env.IMAGE_NAME }}"
|
|
NEW_TAG="${{ github.sha }}"
|
|
DEPLOY_PATH="${{ env.STAGING_DEPLOY_PATH }}"
|
|
HEALTHCHECK_URL="${{ env.STAGING_HEALTHCHECK_URL }}"
|
|
ROLLBACK_TAG="rollback-${NEW_TAG}"
|
|
|
|
if command -v sudo >/dev/null 2>&1 && ! docker ps >/dev/null 2>&1; then
|
|
SUDO="sudo"
|
|
else
|
|
SUDO=""
|
|
fi
|
|
|
|
docker_cmd() {
|
|
if [ -n "$SUDO" ]; then
|
|
sudo docker "$@"
|
|
else
|
|
docker "$@"
|
|
fi
|
|
}
|
|
|
|
compose_cmd() {
|
|
tag="$1"
|
|
shift
|
|
if docker compose version >/dev/null 2>&1; then
|
|
if [ -n "$SUDO" ]; then
|
|
sudo env PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker compose -f docker-compose.cloud.yml "$@"
|
|
else
|
|
PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker compose -f docker-compose.cloud.yml "$@"
|
|
fi
|
|
else
|
|
if [ -n "$SUDO" ]; then
|
|
sudo env PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
|
else
|
|
PPANEL_SERVER_IMAGE="$IMAGE_NAME" PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@"
|
|
fi
|
|
fi
|
|
}
|
|
|
|
health_check() {
|
|
attempt=1
|
|
while [ "$attempt" -le 6 ]; do
|
|
if curl -fsS "$HEALTHCHECK_URL"; then
|
|
echo
|
|
return 0
|
|
fi
|
|
echo "Health check ${attempt}/6 failed; retrying in 10s..."
|
|
attempt=$((attempt + 1))
|
|
sleep 10
|
|
done
|
|
return 1
|
|
}
|
|
|
|
if [ -n "$SUDO" ]; then
|
|
sudo mkdir -p "$DEPLOY_PATH"
|
|
sudo cp /tmp/hifast-server-deploy/docker-compose.cloud.yml "$DEPLOY_PATH/docker-compose.cloud.yml"
|
|
else
|
|
mkdir -p "$DEPLOY_PATH"
|
|
cp /tmp/hifast-server-deploy/docker-compose.cloud.yml "$DEPLOY_PATH/docker-compose.cloud.yml"
|
|
fi
|
|
|
|
cd "$DEPLOY_PATH"
|
|
|
|
PREVIOUS_IMAGE_ID="$(docker_cmd inspect --format '{{.Image}}' ppanel-server 2>/dev/null || true)"
|
|
echo "Previous image ID: ${PREVIOUS_IMAGE_ID:-none}"
|
|
|
|
echo "Pulling ${IMAGE_NAME}:${NEW_TAG}"
|
|
compose_cmd "$NEW_TAG" pull ppanel-server
|
|
|
|
echo "Starting ppanel-server"
|
|
compose_cmd "$NEW_TAG" up -d ppanel-server
|
|
|
|
echo "Checking ${HEALTHCHECK_URL}"
|
|
if health_check; then
|
|
docker_cmd image prune -f || true
|
|
echo "Staging deployment succeeded"
|
|
exit 0
|
|
fi
|
|
|
|
echo "Health check failed; attempting rollback"
|
|
if [ -n "$PREVIOUS_IMAGE_ID" ]; then
|
|
docker_cmd tag "$PREVIOUS_IMAGE_ID" "${IMAGE_NAME}:${ROLLBACK_TAG}"
|
|
compose_cmd "$ROLLBACK_TAG" up -d ppanel-server
|
|
health_check || true
|
|
else
|
|
echo "No previous image found; rollback skipped"
|
|
fi
|
|
|
|
docker_cmd image prune -f || true
|
|
exit 1
|
|
|
|
- name: Telegram 成功通知
|
|
if: success()
|
|
env:
|
|
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
|
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
|
run: |
|
|
if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then
|
|
echo "Telegram 密钥未配置,跳过通知。"
|
|
exit 0
|
|
fi
|
|
|
|
MESSAGE="$(cat <<'EOF'
|
|
✅ 测试环境发布成功
|
|
仓库:${{ github.repository }}
|
|
分支:${{ github.ref_name }}
|
|
提交:${{ github.sha }}
|
|
操作人:${{ github.actor }}
|
|
镜像:${{ env.IMAGE_NAME }}:${{ github.sha }}
|
|
本次更新:
|
|
${{ steps.release-notes.outputs.notes }}
|
|
运行记录:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
EOF
|
|
)"
|
|
|
|
curl -fsS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
|
|
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
|
--data-urlencode "text=${MESSAGE}" || echo "Telegram 通知发送失败,但发布结果不受影响。"
|
|
|
|
- name: Telegram 失败通知
|
|
if: failure()
|
|
env:
|
|
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
|
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
|
run: |
|
|
if [ -z "${TG_BOT_TOKEN:-}" ] || [ -z "${TG_CHAT_ID:-}" ]; then
|
|
echo "Telegram 密钥未配置,跳过通知。"
|
|
exit 0
|
|
fi
|
|
|
|
MESSAGE="$(cat <<'EOF'
|
|
❌ 测试环境发布失败
|
|
仓库:${{ github.repository }}
|
|
分支:${{ github.ref_name }}
|
|
提交:${{ github.sha }}
|
|
操作人:${{ github.actor }}
|
|
镜像:${{ env.IMAGE_NAME }}:${{ github.sha }}
|
|
本次更新:
|
|
${{ steps.release-notes.outputs.notes }}
|
|
运行记录:${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
|
|
EOF
|
|
)"
|
|
|
|
curl -fsS -X POST "https://api.telegram.org/bot${TG_BOT_TOKEN}/sendMessage" \
|
|
--data-urlencode "chat_id=${TG_CHAT_ID}" \
|
|
--data-urlencode "text=${MESSAGE}" || echo "Telegram 通知发送失败,工作流失败状态已记录。"
|