Files
hi-server/.gitea/workflows/docker.yml
T
shanshanzhong147 ea1de71146
Build docker and publish / build (20.15.1) (pull_request) Successful in 8m3s
ci: gate image push and add deploy health rollback
Co-authored-by: multica-agent <github@multica.ai>
2026-05-25 21:16:48 -07:00

338 lines
13 KiB
YAML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
name: Build docker and publish
run-name: 简化的Docker构建和部署流程
on:
push:
branches:
- main
- internal
pull_request:
branches:
- main
- internal
env:
# Docker镜像仓库
REPO: ${{ vars.REPO || 'registry.kxsw.us/vpn-server' }}
# SSH连接信息 (根据分支自动选择服务器和用户)
SSH_HOST: ${{ github.ref_name == 'main' && vars.SSH_HOST || vars.DEV_SSH_HOST }}
SSH_PORT: ${{ vars.SSH_PORT }}
SSH_USER: ${{ github.ref_name == 'main' && 'ubuntu' || 'root' }}
# SSH私钥(Gitea Secret 名称:AWS
SSH_KEY: ${{ secrets.AWS }}
# TG通知
TG_BOT_TOKEN: ${{ secrets.TG_BOT_TOKEN }}
TG_CHAT_ID: ${{ secrets.TG_CHAT_ID }}
# Go构建变量
SERVICE: vpn
SERVICE_STYLE: vpn
VERSION: ${{ github.sha }}
BUILDTIME: ${{ github.event.head_commit.timestamp }}
GOARCH: amd64
jobs:
build:
runs-on: ario-server
container:
image: node:20
strategy:
matrix:
# 只有node支持版本号别名
node: ['20.15.1']
steps:
# 步骤1: 下载代码
- name: 📥 下载代码
uses: actions/checkout@v4
# 步骤2: 设置动态环境变量
- name: ⚙️ 设置动态环境变量
run: |
if [ "${{ github.ref_name }}" = "main" ]; then
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: |
set -e
export DEBIAN_FRONTEND=noninteractive
echo "等待 apt/dpkg 锁释放 (unattended-upgrades)..."
end=$((SECONDS+300))
while true; do
LOCKS_BUSY=0
if pgrep -x unattended-upgrades >/dev/null 2>&1; then LOCKS_BUSY=1; fi
if command -v fuser >/dev/null 2>&1; then
if fuser /var/lib/dpkg/lock >/dev/null 2>&1 \
|| fuser /var/lib/dpkg/lock-frontend >/dev/null 2>&1 \
|| fuser /var/lib/apt/lists/lock >/dev/null 2>&1; then
LOCKS_BUSY=1
fi
fi
if [ "$LOCKS_BUSY" -eq 0 ]; then break; fi
if [ $SECONDS -ge $end ]; then
echo "等待 apt/dpkg 锁超时,使用 Dpkg::Lock::Timeout 继续..."
break
fi
echo "仍在等待锁释放..."; sleep 5
done
# 基础工具
apt-get update -y -o Dpkg::Lock::Timeout=600
apt-get install -y -o Dpkg::Lock::Timeout=600 jq curl ca-certificates gnupg lsb-release
# 移除旧版 docker.io,避免客户端过旧 (API 1.41)
if dpkg -s docker.io >/dev/null 2>&1; then
apt-get remove -y docker.io || true
fi
# 安装 Docker 官方仓库的 CLI (确保 API >= 1.44)
distro_codename=$(. /etc/os-release && echo "$VERSION_CODENAME")
install_repo="deb [arch=amd64 signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/debian ${distro_codename} stable"
mkdir -p /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg | gpg --dearmor -o /etc/apt/keyrings/docker.gpg
echo "$install_repo" > /etc/apt/sources.list.d/docker.list
apt-get update -y -o Dpkg::Lock::Timeout=600
apt-get install -y -o Dpkg::Lock::Timeout=600 docker-ce-cli docker-buildx-plugin
# 版本检查
docker --version || true
docker version || true
echo "客户端 API 版本:" $(docker version --format '{{.Client.APIVersion}}')
# 步骤4: 构建镜像
- name: 🏗️ 构建镜像
run: |
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 }} \
$BUILD_TAG_ARGS \
.
echo "镜像构建完成"
# 步骤5: 发布到镜像仓库
- name: 📤 发布到镜像仓库
if: github.event_name == 'push'
run: |
echo "开始推送镜像..."
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 "镜像推送完成"
# 步骤6: 调试 - 打印部署目标(不输出敏感信息)
- name: 🔍 调试 - 打印部署目标
if: github.event_name == 'push'
run: |
echo "========== 部署目标调试 =========="
echo "当前分支: ${{ github.ref_name }}"
echo "SSH_HOST: ${{ env.SSH_HOST }}"
echo "SSH_PORT: ${{ env.SSH_PORT }}"
echo "SSH_USER: ${{ env.SSH_USER }}"
echo "SSH认证方式: 私钥 (AWS)"
echo "DEPLOY_PATH: ${{ env.DEPLOY_PATH }}"
echo "====================================="
# 步骤7: 传输配置文件
- name: 📂 传输配置文件
if: github.event_name == 'push'
uses: appleboy/scp-action@v0.1.7
with:
host: ${{ env.SSH_HOST }}
username: ${{ env.SSH_USER }}
key: ${{ env.SSH_KEY }}
port: ${{ env.SSH_PORT }}
source: "docker-compose.cloud.yml"
target: "/tmp/ppanel-deploy/"
# 步骤8: 连接服务器更新、健康检查并按需回滚
- name: 🚀 连接服务器更新并启动
if: github.event_name == 'push'
uses: appleboy/ssh-action@v1.0.3
with:
host: ${{ env.SSH_HOST }}
username: ${{ env.SSH_USER }}
key: ${{ env.SSH_KEY }}
port: ${{ env.SSH_PORT }}
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
else
mkdir -p ${{ env.DEPLOY_PATH }}
cp /tmp/ppanel-deploy/docker-compose.cloud.yml ${{ env.DEPLOY_PATH }}/docker-compose.cloud.yml
fi
cd ${{ env.DEPLOY_PATH }}
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
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 }}
🩺 健康检查: 通过 (http://127.0.0.1:8080/v1/common/heartbeat)
parse_mode: Markdown
# 步骤10: TG通知 (失败)
- name: 📱 发送失败通知到Telegram
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 }}
🩺 健康检查: 失败或未完成;若新版本健康检查连续 3 次失败,已自动尝试回滚并重新检查
⚠️ 请检查构建日志获取详细信息
parse_mode: Markdown