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