From 4f82f18e166c970cdb68ef5454ecb64017491b4d Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Mon, 25 May 2026 20:39:31 -0700 Subject: [PATCH 1/2] =?UTF-8?q?ci:=20=E8=A7=84=E8=8C=83=E5=8C=96TG?= =?UTF-8?q?=E9=80=9A=E7=9F=A5=20+=20=E5=AE=89=E5=85=A8=E5=8A=A0=E5=9B=BA?= =?UTF-8?q?=20+=20=E6=AD=A5=E9=AA=A4=E7=BC=96=E5=8F=B7=E4=BF=AE=E6=AD=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit P01: PR 事件只做构建验证,部署步骤和TG通知仅在 push 时触发 P02: TG_BOT_TOKEN / TG_CHAT_ID 改用 secrets 引用,移除硬编码明文 P03: TG成功通知区分环境(main→生产环境 / internal→测试环境) P04: 修正步骤编号注释(步骤1-9,消除重复的步骤5/步骤6) 注意:需在 Gitea 仓库 Settings → Secrets 中手动添加 TG_BOT_TOKEN 和 TG_CHAT_ID,并轮换已暴露的旧 Bot Token。 Co-authored-by: multica-agent --- .gitea/workflows/docker.yml | 54 ++++++++++++++++++++----------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index a5740a0..982139e 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -21,8 +21,8 @@ env: # SSH私钥(Gitea Secret 名称:AWS) SSH_KEY: ${{ secrets.AWS }} # TG通知 - TG_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0 - TG_CHAT_ID: "-4940243803" + TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }} + TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }} # Go构建变量 SERVICE: vpn SERVICE_STYLE: vpn @@ -43,7 +43,7 @@ jobs: # 步骤1: 下载代码 - name: 📥 下载代码 uses: actions/checkout@v4 - + # 步骤2: 设置动态环境变量 - name: ⚙️ 设置动态环境变量 run: | @@ -51,19 +51,22 @@ jobs: echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV echo "DEPLOY_PATH=/opt/ppanel" >> $GITHUB_ENV + echo "DEPLOY_ENV_LABEL=🚀 服务已成功部署到生产环境" >> $GITHUB_ENV echo "为 main 分支设置生产环境变量" elif [ "${{ github.ref_name }}" = "internal" ]; then echo "DOCKER_TAG_SUFFIX=internal" >> $GITHUB_ENV echo "CONTAINER_NAME=ppanel-server-internal" >> $GITHUB_ENV echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV + echo "DEPLOY_ENV_LABEL=🧪 服务已成功部署到测试环境" >> $GITHUB_ENV echo "为 internal 分支设置开发环境变量" else echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV echo "CONTAINER_NAME=ppanel-server-${{ github.ref_name }}" >> $GITHUB_ENV echo "DEPLOY_PATH=/root/vpn_server_other" >> $GITHUB_ENV + echo "DEPLOY_ENV_LABEL=🔧 服务已成功部署到其他环境" >> $GITHUB_ENV echo "为其他分支 (${{ github.ref_name }}) 设置环境变量" fi - + # 步骤3: 安装系统工具 (curl, jq) 并升级 Docker CLI 到 1.44+ - name: 🔧 安装系统工具并升级 Docker CLI run: | @@ -111,7 +114,7 @@ jobs: docker --version || true docker version || true echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}') - + # 步骤4: 构建并发布到镜像仓库 - name: 📤 构建并发布到镜像仓库 run: | @@ -129,16 +132,16 @@ jobs: -t ${{ env.REPO }}:${{ env.VERSION }} \ -t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }} \ . - + echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}" docker push ${{ env.REPO }}:${{ env.VERSION }} - + echo "推送分支标签镜像: ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}" docker push ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }} - + echo "镜像推送完成" - - # 调试: 打印部署目标(不输出敏感信息) + + # 步骤5: 调试 - 打印部署目标(不输出敏感信息) - name: 🔍 调试 - 打印部署目标 run: | echo "========== 部署目标调试 ==========" @@ -150,8 +153,9 @@ jobs: echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}" echo "=====================================" - # 步骤5: 传输配置文件 + # 步骤6: 传输配置文件 - name: 📂 传输配置文件 + if: github.event_name == 'push' uses: appleboy/scp-action@v0.1.7 with: host: ${{ env.SSH_HOST }} @@ -161,8 +165,9 @@ jobs: source: "docker-compose.cloud.yml" target: "/tmp/ppanel-deploy/" - # 步骤6: 连接服务器更新并启动 + # 步骤7: 连接服务器更新并启动 - name: 🚀 连接服务器更新并启动 + if: github.event_name == 'push' uses: appleboy/ssh-action@v1.0.3 with: host: ${{ env.SSH_HOST }} @@ -196,44 +201,43 @@ jobs: docker-compose -f docker-compose.cloud.yml up -d ppanel-server docker image prune -f || true fi - + echo "✅ 部署命令执行完成" - - # 步骤6: TG通知 (成功) + + # 步骤8: TG通知 (成功) - name: 📱 发送成功通知到Telegram - if: success() + if: success() && github.event_name == 'push' uses: appleboy/telegram-action@master with: token: ${{ env.TG_BOT_TOKEN }} to: ${{ env.TG_CHAT_ID }} message: | ✅ 部署成功! - + 📦 项目: ${{ github.repository }} 🌿 分支: ${{ github.ref_name }} 📝 提交: ${{ github.sha }} 👤 提交者: ${{ github.actor }} 🕐 时间: ${{ github.event.head_commit.timestamp }} - - 🚀 服务已成功部署到生产环境 + + ${{ env.DEPLOY_ENV_LABEL }} parse_mode: Markdown - - # 步骤5: TG通知 (失败) + + # 步骤9: TG通知 (失败) - name: 📱 发送失败通知到Telegram - if: failure() + if: failure() && github.event_name == 'push' uses: appleboy/telegram-action@master with: token: ${{ env.TG_BOT_TOKEN }} to: ${{ env.TG_CHAT_ID }} message: | ❌ 部署失败! - + 📦 项目: ${{ github.repository }} 🌿 分支: ${{ github.ref_name }} 📝 提交: ${{ github.sha }} 👤 提交者: ${{ github.actor }} 🕐 时间: ${{ github.event.head_commit.timestamp }} - + ⚠️ 请检查构建日志获取详细信息 parse_mode: Markdown - -- 2.52.0 From ea1de711467f13665c1504f2a2fcfaf3307cfc29 Mon Sep 17 00:00:00 2001 From: shanshanzhong Date: Mon, 25 May 2026 21:16:48 -0700 Subject: [PATCH 2/2] ci: gate image push and add deploy health rollback Co-authored-by: multica-agent --- .gitea/workflows/docker.yml | 142 ++++++++++++++++++++++++++++++------ 1 file changed, 118 insertions(+), 24 deletions(-) diff --git a/.gitea/workflows/docker.yml b/.gitea/workflows/docker.yml index 982139e..6057447 100644 --- a/.gitea/workflows/docker.yml +++ b/.gitea/workflows/docker.yml @@ -115,24 +115,37 @@ jobs: docker version || true echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}') - # 步骤4: 构建并发布到镜像仓库 - - name: 📤 构建并发布到镜像仓库 + # 步骤4: 构建镜像 + - name: 🏗️ 构建镜像 run: | - echo "开始构建并推送镜像..." + echo "开始构建镜像..." echo "仓库: ${{ env.REPO }}" echo "版本标签: ${{ env.VERSION }}" echo "分支标签: ${{ env.DOCKER_TAG_SUFFIX }}" - # 构建镜像,同时打上版本和分支两个标签 + BUILD_TAG_ARGS="-t ${{ env.REPO }}:${{ env.VERSION }}" + if [ "${{ github.event_name }}" = "push" ]; then + BUILD_TAG_ARGS="$BUILD_TAG_ARGS -t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }}" + else + echo "PR事件仅构建版本标签,不推送镜像、不部署" + fi + docker build -f Dockerfile \ --platform linux/amd64 \ --build-arg TARGETARCH=amd64 \ --build-arg VERSION=${{ env.VERSION }} \ --build-arg BUILDTIME=${{ env.BUILDTIME }} \ - -t ${{ env.REPO }}:${{ env.VERSION }} \ - -t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }} \ + $BUILD_TAG_ARGS \ . + echo "镜像构建完成" + + # 步骤5: 发布到镜像仓库 + - name: 📤 发布到镜像仓库 + if: github.event_name == 'push' + run: | + echo "开始推送镜像..." + echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}" docker push ${{ env.REPO }}:${{ env.VERSION }} @@ -141,8 +154,9 @@ jobs: echo "镜像推送完成" - # 步骤5: 调试 - 打印部署目标(不输出敏感信息) + # 步骤6: 调试 - 打印部署目标(不输出敏感信息) - name: 🔍 调试 - 打印部署目标 + if: github.event_name == 'push' run: | echo "========== 部署目标调试 ==========" echo "当前分支: ${{ github.ref_name }}" @@ -153,7 +167,7 @@ jobs: echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}" echo "=====================================" - # 步骤6: 传输配置文件 + # 步骤7: 传输配置文件 - name: 📂 传输配置文件 if: github.event_name == 'push' uses: appleboy/scp-action@v0.1.7 @@ -165,7 +179,7 @@ jobs: source: "docker-compose.cloud.yml" target: "/tmp/ppanel-deploy/" - # 步骤7: 连接服务器更新并启动 + # 步骤8: 连接服务器更新、健康检查并按需回滚 - name: 🚀 连接服务器更新并启动 if: github.event_name == 'push' uses: appleboy/ssh-action@v1.0.3 @@ -177,34 +191,112 @@ jobs: timeout: 300s command_timeout: 600s script: | + set -e + echo "连接服务器成功,开始部署..." echo "部署目录: ${{ env.DEPLOY_PATH }}" echo "部署标签: ${{ env.DOCKER_TAG_SUFFIX }}" echo "登录用户: ${{ env.SSH_USER }}" + HEALTHCHECK_URL="http://127.0.0.1:8080/v1/common/heartbeat" + NEW_TAG="${{ env.DOCKER_TAG_SUFFIX }}" + ROLLBACK_TAG="rollback-${{ env.VERSION }}" + SUDO="" + if [ "${{ github.ref_name }}" = "main" ]; then + SUDO="sudo" + fi + + docker_cmd() { + if [ -n "$SUDO" ]; then + sudo docker "$@" + else + docker "$@" + fi + } + + compose_with_tag() { + tag="$1" + shift + if [ -n "$SUDO" ]; then + sudo env PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@" + else + PPANEL_SERVER_TAG="$tag" docker-compose -f docker-compose.cloud.yml "$@" + fi + } + + write_previous_tag() { + if [ -n "$SUDO" ]; then + printf '%s\n' "${PREVIOUS_IMAGE_TAG:-}" | sudo tee .previous-ppanel-image-tag >/dev/null + else + printf '%s\n' "${PREVIOUS_IMAGE_TAG:-}" > .previous-ppanel-image-tag + fi + } + + health_check() { + attempt=1 + while [ "$attempt" -le 3 ]; do + if curl -sf "$HEALTHCHECK_URL"; then + echo + return 0 + fi + + echo "健康检查第 ${attempt}/3 次失败,10s 后重试..." + attempt=$((attempt + 1)) + sleep 10 + done + + return 1 + } + if [ "${{ github.ref_name }}" = "main" ]; then sudo mkdir -p ${{ env.DEPLOY_PATH }} sudo cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml - cd ${{ env.DEPLOY_PATH }} - echo "📥 拉取镜像..." - sudo docker-compose -f docker-compose.cloud.yml pull ppanel-server - echo "🚀 启动服务..." - sudo docker-compose -f docker-compose.cloud.yml up -d ppanel-server - sudo docker image prune -f || true else mkdir -p ${{ env.DEPLOY_PATH }} cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml - cd ${{ env.DEPLOY_PATH }} - echo "📥 拉取镜像..." - docker-compose -f docker-compose.cloud.yml pull ppanel-server - echo "🚀 启动服务..." - docker-compose -f docker-compose.cloud.yml up -d ppanel-server - docker image prune -f || true fi - echo "✅ 部署命令执行完成" + cd ${{ env.DEPLOY_PATH }} - # 步骤8: TG通知 (成功) + PREVIOUS_IMAGE_TAG="$(docker_cmd inspect --format '{{.Config.Image}}' ppanel-server 2>/dev/null || true)" + PREVIOUS_IMAGE_ID="$(docker_cmd inspect --format '{{.Image}}' ppanel-server 2>/dev/null || true)" + echo "上一版本镜像tag: ${PREVIOUS_IMAGE_TAG:-未发现}" + echo "上一版本镜像ID: ${PREVIOUS_IMAGE_ID:-未发现}" + write_previous_tag + + echo "📥 拉取镜像: ${{ env.REPO }}:${NEW_TAG}" + compose_with_tag "$NEW_TAG" pull ppanel-server + echo "🚀 启动服务..." + compose_with_tag "$NEW_TAG" up -d ppanel-server + + echo "🩺 部署后健康检查: ${HEALTHCHECK_URL}" + if health_check; then + docker_cmd image prune -f || true + echo "✅ 部署后健康检查通过" + echo "✅ 部署命令执行完成" + exit 0 + fi + + echo "❌ 部署后健康检查连续 3 次失败,开始回滚..." + if [ -n "$PREVIOUS_IMAGE_ID" ]; then + docker_cmd tag "$PREVIOUS_IMAGE_ID" "${{ env.REPO }}:${ROLLBACK_TAG}" + echo "回滚镜像tag: ${{ env.REPO }}:${ROLLBACK_TAG}" + compose_with_tag "$ROLLBACK_TAG" up -d ppanel-server + + echo "🩺 回滚后健康检查: ${HEALTHCHECK_URL}" + if health_check; then + echo "✅ 回滚后健康检查通过" + else + echo "❌ 回滚后健康检查仍失败" + fi + else + echo "未找到上一版本镜像ID,无法自动回滚" + fi + + docker_cmd image prune -f || true + exit 1 + + # 步骤9: TG通知 (成功) - name: 📱 发送成功通知到Telegram if: success() && github.event_name == 'push' uses: appleboy/telegram-action@master @@ -221,9 +313,10 @@ jobs: 🕐 时间: ${{ github.event.head_commit.timestamp }} ${{ env.DEPLOY_ENV_LABEL }} + 🩺 健康检查: 通过 (http://127.0.0.1:8080/v1/common/heartbeat) parse_mode: Markdown - # 步骤9: TG通知 (失败) + # 步骤10: TG通知 (失败) - name: 📱 发送失败通知到Telegram if: failure() && github.event_name == 'push' uses: appleboy/telegram-action@master @@ -239,5 +332,6 @@ jobs: 👤 提交者: ${{ github.actor }} 🕐 时间: ${{ github.event.head_commit.timestamp }} + 🩺 健康检查: 失败或未完成;若新版本健康检查连续 3 次失败,已自动尝试回滚并重新检查 ⚠️ 请检查构建日志获取详细信息 parse_mode: Markdown -- 2.52.0