配置(#47,#51): CI/CD 安全加固 + TG 通知限制 + 部署健康检查
Build docker and publish / build (20.15.1) (push) Failing after 8m35s
Build docker and publish / build (20.15.1) (push) Failing after 8m35s
- TG Bot Token/Chat ID 改用 secrets,移除硬编码 - PR 事件只构建不推镜像、不部署、不发通知 - 部署后增加健康检查,失败自动回滚 - 增加环境标签区分生产/测试/其他 Co-authored-by: multica-agent <github@multica.ai>
This commit is contained in:
+128
-30
@@ -21,8 +21,8 @@ env:
|
|||||||
# SSH私钥(Gitea Secret 名称:AWS)
|
# SSH私钥(Gitea Secret 名称:AWS)
|
||||||
SSH_KEY: ${{ secrets.AWS }}
|
SSH_KEY: ${{ secrets.AWS }}
|
||||||
# TG通知
|
# TG通知
|
||||||
TG_BOT_TOKEN: 8114337882:AAHkEx03HSu7RxN4IHBJJEnsK9aPPzNLIk0
|
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
|
||||||
TG_CHAT_ID: "-4940243803"
|
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
|
||||||
# Go构建变量
|
# Go构建变量
|
||||||
SERVICE: vpn
|
SERVICE: vpn
|
||||||
SERVICE_STYLE: vpn
|
SERVICE_STYLE: vpn
|
||||||
@@ -51,16 +51,19 @@ jobs:
|
|||||||
echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV
|
echo "DOCKER_TAG_SUFFIX=latest" >> $GITHUB_ENV
|
||||||
echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV
|
echo "CONTAINER_NAME=ppanel-server" >> $GITHUB_ENV
|
||||||
echo "DEPLOY_PATH=/opt/ppanel" >> $GITHUB_ENV
|
echo "DEPLOY_PATH=/opt/ppanel" >> $GITHUB_ENV
|
||||||
|
echo "DEPLOY_ENV_LABEL=🚀 服务已成功部署到生产环境" >> $GITHUB_ENV
|
||||||
echo "为 main 分支设置生产环境变量"
|
echo "为 main 分支设置生产环境变量"
|
||||||
elif [ "${{ github.ref_name }}" = "internal" ]; then
|
elif [ "${{ github.ref_name }}" = "internal" ]; then
|
||||||
echo "DOCKER_TAG_SUFFIX=internal" >> $GITHUB_ENV
|
echo "DOCKER_TAG_SUFFIX=internal" >> $GITHUB_ENV
|
||||||
echo "CONTAINER_NAME=ppanel-server-internal" >> $GITHUB_ENV
|
echo "CONTAINER_NAME=ppanel-server-internal" >> $GITHUB_ENV
|
||||||
echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV
|
echo "DEPLOY_PATH=/root/bindbox" >> $GITHUB_ENV
|
||||||
|
echo "DEPLOY_ENV_LABEL=🧪 服务已成功部署到测试环境" >> $GITHUB_ENV
|
||||||
echo "为 internal 分支设置开发环境变量"
|
echo "为 internal 分支设置开发环境变量"
|
||||||
else
|
else
|
||||||
echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV
|
echo "DOCKER_TAG_SUFFIX=${{ github.ref_name }}" >> $GITHUB_ENV
|
||||||
echo "CONTAINER_NAME=ppanel-server-${{ 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_PATH=/root/vpn_server_other" >> $GITHUB_ENV
|
||||||
|
echo "DEPLOY_ENV_LABEL=🔧 服务已成功部署到其他环境" >> $GITHUB_ENV
|
||||||
echo "为其他分支 (${{ github.ref_name }}) 设置环境变量"
|
echo "为其他分支 (${{ github.ref_name }}) 设置环境变量"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
@@ -112,24 +115,37 @@ jobs:
|
|||||||
docker version || true
|
docker version || true
|
||||||
echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}')
|
echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}')
|
||||||
|
|
||||||
# 步骤4: 构建并发布到镜像仓库
|
# 步骤4: 构建镜像
|
||||||
- name: 📤 构建并发布到镜像仓库
|
- name: 🏗️ 构建镜像
|
||||||
run: |
|
run: |
|
||||||
echo "开始构建并推送镜像..."
|
echo "开始构建镜像..."
|
||||||
echo "仓库: ${{ env.REPO }}"
|
echo "仓库: ${{ env.REPO }}"
|
||||||
echo "版本标签: ${{ env.VERSION }}"
|
echo "版本标签: ${{ env.VERSION }}"
|
||||||
echo "分支标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
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 \
|
docker build -f Dockerfile \
|
||||||
--platform linux/amd64 \
|
--platform linux/amd64 \
|
||||||
--build-arg TARGETARCH=amd64 \
|
--build-arg TARGETARCH=amd64 \
|
||||||
--build-arg VERSION=${{ env.VERSION }} \
|
--build-arg VERSION=${{ env.VERSION }} \
|
||||||
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
|
--build-arg BUILDTIME=${{ env.BUILDTIME }} \
|
||||||
-t ${{ env.REPO }}:${{ env.VERSION }} \
|
$BUILD_TAG_ARGS \
|
||||||
-t ${{ env.REPO }}:${{ env.DOCKER_TAG_SUFFIX }} \
|
|
||||||
.
|
.
|
||||||
|
|
||||||
|
echo "镜像构建完成"
|
||||||
|
|
||||||
|
# 步骤5: 发布到镜像仓库
|
||||||
|
- name: 📤 发布到镜像仓库
|
||||||
|
if: github.event_name == 'push'
|
||||||
|
run: |
|
||||||
|
echo "开始推送镜像..."
|
||||||
|
|
||||||
echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}"
|
echo "推送版本标签镜像: ${{ env.REPO }}:${{ env.VERSION }}"
|
||||||
docker push ${{ env.REPO }}:${{ env.VERSION }}
|
docker push ${{ env.REPO }}:${{ env.VERSION }}
|
||||||
|
|
||||||
@@ -138,8 +154,9 @@ jobs:
|
|||||||
|
|
||||||
echo "镜像推送完成"
|
echo "镜像推送完成"
|
||||||
|
|
||||||
# 调试: 打印部署目标(不输出敏感信息)
|
# 步骤6: 调试 - 打印部署目标(不输出敏感信息)
|
||||||
- name: 🔍 调试 - 打印部署目标
|
- name: 🔍 调试 - 打印部署目标
|
||||||
|
if: github.event_name == 'push'
|
||||||
run: |
|
run: |
|
||||||
echo "========== 部署目标调试 =========="
|
echo "========== 部署目标调试 =========="
|
||||||
echo "当前分支: ${{ github.ref_name }}"
|
echo "当前分支: ${{ github.ref_name }}"
|
||||||
@@ -150,8 +167,9 @@ jobs:
|
|||||||
echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}"
|
echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}"
|
||||||
echo "====================================="
|
echo "====================================="
|
||||||
|
|
||||||
# 步骤5: 传输配置文件
|
# 步骤7: 传输配置文件
|
||||||
- name: 📂 传输配置文件
|
- name: 📂 传输配置文件
|
||||||
|
if: github.event_name == 'push'
|
||||||
uses: appleboy/scp-action@v0.1.7
|
uses: appleboy/scp-action@v0.1.7
|
||||||
with:
|
with:
|
||||||
host: ${{ env.SSH_HOST }}
|
host: ${{ env.SSH_HOST }}
|
||||||
@@ -161,8 +179,9 @@ jobs:
|
|||||||
source: "docker-compose.cloud.yml"
|
source: "docker-compose.cloud.yml"
|
||||||
target: "/tmp/ppanel-deploy/"
|
target: "/tmp/ppanel-deploy/"
|
||||||
|
|
||||||
# 步骤6: 连接服务器更新并启动
|
# 步骤8: 连接服务器更新、健康检查并按需回滚
|
||||||
- name: 🚀 连接服务器更新并启动
|
- name: 🚀 连接服务器更新并启动
|
||||||
|
if: github.event_name == 'push'
|
||||||
uses: appleboy/ssh-action@v1.0.3
|
uses: appleboy/ssh-action@v1.0.3
|
||||||
with:
|
with:
|
||||||
host: ${{ env.SSH_HOST }}
|
host: ${{ env.SSH_HOST }}
|
||||||
@@ -172,36 +191,114 @@ jobs:
|
|||||||
timeout: 300s
|
timeout: 300s
|
||||||
command_timeout: 600s
|
command_timeout: 600s
|
||||||
script: |
|
script: |
|
||||||
|
set -e
|
||||||
|
|
||||||
echo "连接服务器成功,开始部署..."
|
echo "连接服务器成功,开始部署..."
|
||||||
echo "部署目录: ${{ env.DEPLOY_PATH }}"
|
echo "部署目录: ${{ env.DEPLOY_PATH }}"
|
||||||
echo "部署标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
echo "部署标签: ${{ env.DOCKER_TAG_SUFFIX }}"
|
||||||
echo "登录用户: ${{ env.SSH_USER }}"
|
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
|
if [ "${{ github.ref_name }}" = "main" ]; then
|
||||||
sudo mkdir -p ${{ env.DEPLOY_PATH }}
|
sudo mkdir -p ${{ env.DEPLOY_PATH }}
|
||||||
sudo cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
|
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
|
else
|
||||||
mkdir -p ${{ env.DEPLOY_PATH }}
|
mkdir -p ${{ env.DEPLOY_PATH }}
|
||||||
cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
|
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
|
fi
|
||||||
|
|
||||||
echo "✅ 部署命令执行完成"
|
cd ${{ env.DEPLOY_PATH }}
|
||||||
|
|
||||||
# 步骤6: 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
|
- name: 📱 发送成功通知到Telegram
|
||||||
if: success()
|
if: success() && github.event_name == 'push'
|
||||||
uses: appleboy/telegram-action@master
|
uses: appleboy/telegram-action@master
|
||||||
with:
|
with:
|
||||||
token: ${{ env.TG_BOT_TOKEN }}
|
token: ${{ env.TG_BOT_TOKEN }}
|
||||||
@@ -215,12 +312,13 @@ jobs:
|
|||||||
👤 提交者: ${{ github.actor }}
|
👤 提交者: ${{ github.actor }}
|
||||||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
||||||
|
|
||||||
🚀 服务已成功部署到生产环境
|
${{ env.DEPLOY_ENV_LABEL }}
|
||||||
|
🩺 健康检查: 通过 (http://127.0.0.1:8080/v1/common/heartbeat)
|
||||||
parse_mode: Markdown
|
parse_mode: Markdown
|
||||||
|
|
||||||
# 步骤5: TG通知 (失败)
|
# 步骤10: TG通知 (失败)
|
||||||
- name: 📱 发送失败通知到Telegram
|
- name: 📱 发送失败通知到Telegram
|
||||||
if: failure()
|
if: failure() && github.event_name == 'push'
|
||||||
uses: appleboy/telegram-action@master
|
uses: appleboy/telegram-action@master
|
||||||
with:
|
with:
|
||||||
token: ${{ env.TG_BOT_TOKEN }}
|
token: ${{ env.TG_BOT_TOKEN }}
|
||||||
@@ -234,6 +332,6 @@ jobs:
|
|||||||
👤 提交者: ${{ github.actor }}
|
👤 提交者: ${{ github.actor }}
|
||||||
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
🕐 时间: ${{ github.event.head_commit.timestamp }}
|
||||||
|
|
||||||
|
🩺 健康检查: 失败或未完成;若新版本健康检查连续 3 次失败,已自动尝试回滚并重新检查
|
||||||
⚠️ 请检查构建日志获取详细信息
|
⚠️ 请检查构建日志获取详细信息
|
||||||
parse_mode: Markdown
|
parse_mode: Markdown
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user