name: Build and Push Docker Image on: release: types: - published # 当通过 GitHub Releases 页面创建 release 时触发 workflow_dispatch: inputs: build_type: description: '构建类型' required: true type: choice options: - package-only # 只打包产物 - package-and-docker # 打包产物 + Docker 镜像 default: 'package-and-docker' version: description: '版本号(例如: v1.0.0)' required: false type: string tag_name: description: 'Git Tag 名称(留空则使用 version)' required: false type: string jobs: build-and-push: runs-on: ubuntu-latest permissions: contents: write # 需要写权限以上传 Assets steps: - name: Checkout code uses: actions/checkout@v4 with: ref: ${{ github.event.release.tag_name || github.event.inputs.tag_name || github.event.inputs.version || github.ref }} - name: Determine build type id: build_config run: | # 确定构建类型 if [ "${{ github.event_name }}" = "release" ]; then # Release 事件:默认只打包产物(不构建 Docker) BUILD_TYPE="package-only" echo "📦 Release 事件:将只打包产物(不构建 Docker)" else # workflow_dispatch 事件:使用用户输入 BUILD_TYPE="${{ github.event.inputs.build_type }}" echo "🔧 手动触发:构建类型 = ${BUILD_TYPE}" fi echo "BUILD_TYPE=${BUILD_TYPE}" >> $GITHUB_OUTPUT - name: Extract version and check if pre-release id: extract_version run: | # 从不同事件源提取版本号 if [ "${{ github.event_name }}" = "release" ]; then # Release 事件:从 release tag 中提取 TAG_NAME="${{ github.event.release.tag_name }}" IS_PRERELEASE="${{ github.event.release.prerelease }}" else # workflow_dispatch 事件:从输入参数中提取 TAG_NAME="${{ github.event.inputs.tag_name }}" if [ -z "$TAG_NAME" ]; then TAG_NAME="${{ github.event.inputs.version }}" fi # 如果仍然为空,尝试从 git ref 中提取 if [ -z "$TAG_NAME" ]; then TAG_NAME=${GITHUB_REF#refs/tags/} if [ "$TAG_NAME" = "$GITHUB_REF" ]; then # 不是 tag,尝试从分支名或 commit SHA 获取 TAG_NAME=${GITHUB_REF#refs/heads/} if [ "$TAG_NAME" = "$GITHUB_REF" ]; then TAG_NAME="dev-$(date +%Y%m%d-%H%M%S)" echo "⚠️ 未指定版本号,使用临时版本: $TAG_NAME" fi fi fi IS_PRERELEASE="false" fi # 验证版本号格式:v数字.数字.数字[-后缀](例如 v1.0.0, v2.10.102, v1.0.0-beta) if [[ ! "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+(-[a-zA-Z0-9.-]+)?$ ]] && [[ ! "$TAG_NAME" =~ ^dev- ]]; then echo "⚠️ 警告: 版本号格式不符合标准,但仍将继续构建" echo " 当前版本号: $TAG_NAME" echo " 标准格式应为: v数字.数字.数字 或 v数字.数字.数字-后缀 (例如: v1.0.0, v1.0.0-beta)" fi VERSION=${TAG_NAME#v} # 移除 v 前缀(如果存在) echo "VERSION=$VERSION" >> $GITHUB_OUTPUT echo "TAG=$TAG_NAME" >> $GITHUB_OUTPUT echo "IS_PRERELEASE=$IS_PRERELEASE" >> $GITHUB_OUTPUT if [ "$IS_PRERELEASE" = "true" ]; then echo "📋 这是 Pre-release: $TAG_NAME" else echo "📦 这是正式版本: $TAG_NAME" fi - name: Send Telegram notification (build started) if: steps.extract_version.outputs.IS_PRERELEASE == 'false' && steps.build_config.outputs.BUILD_TYPE == 'package-and-docker' env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | # 检查必要的环境变量 if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知" exit 0 fi # 获取构建信息 TAG="${{ steps.extract_version.outputs.TAG }}" if [ "${{ github.event_name }}" = "release" ]; then RELEASE_URL="${{ github.event.release.html_url }}" MESSAGE="🔨 Release 构建中"$'\n'$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔧 构建类型: Docker 升级"$'\n'"🔗 查看 Release" else WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" MESSAGE="🔨 构建中"$'\n'$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔧 构建类型: Docker 升级"$'\n'"🔗 查看 Workflow" fi # 发送 Telegram 消息(使用 jq 转义 JSON) curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg chat_id "$TELEGRAM_CHAT_ID" \ --arg text "$MESSAGE" \ '{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json # 检查发送结果 if [ $? -eq 0 ]; then RESPONSE=$(cat /tmp/telegram_response.json) if echo "$RESPONSE" | grep -q '"ok":true'; then echo "✅ Telegram 通知发送成功" else echo "❌ Telegram 通知发送失败: $RESPONSE" # 通知失败不应该导致整个 job 失败 exit 0 fi else echo "❌ 发送 Telegram 消息时发生错误" # 通知失败不应该导致整个 job 失败 exit 0 fi # ============ 编译前后端产物 ============ - name: Setup JDK 17 uses: actions/setup-java@v4 with: java-version: '17' distribution: 'temurin' - name: Build Backend JAR run: | cd backend chmod +x gradlew ./gradlew bootJar --no-daemon echo "✅ 后端构建完成" ls -lh build/libs/*.jar - name: Setup Node.js uses: actions/setup-node@v4 with: node-version: '18' - name: Build Frontend env: VERSION: ${{ steps.extract_version.outputs.VERSION }} GIT_TAG: ${{ steps.extract_version.outputs.TAG }} GITHUB_REPO_URL: https://github.com/WrBug/PolyHermes run: | cd frontend npm ci npm run build echo "✅ 前端构建完成" echo "📦 版本信息: VERSION=${{ steps.extract_version.outputs.VERSION }}, GIT_TAG=${{ steps.extract_version.outputs.TAG }}" du -sh dist/ # ============ 打包更新包 ============ - name: Create Update Package run: | echo "📦 开始打包更新包..." # 创建目录结构 mkdir -p update-package/backend mkdir -p update-package/frontend # 复制后端 JAR cp backend/build/libs/*.jar update-package/backend/polyhermes.jar echo "✅ 后端 JAR 已复制" # 复制前端产物 cp -r frontend/dist/* update-package/frontend/ echo "✅ 前端文件已复制" # 创建版本信息文件 if [ "${{ github.event_name }}" = "release" ]; then RELEASE_NOTES=$(echo '${{ github.event.release.body }}' | jq -Rs .) else RELEASE_NOTES="\"手动构建 - workflow_dispatch\"" fi cat > update-package/version.json <> $GITHUB_OUTPUT echo "✅ SHA256: $CHECKSUM" echo "$CHECKSUM $FILE" > checksums.txt - name: Upload Update Package to Release if: github.event_name == 'release' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ./polyhermes-${{ steps.extract_version.outputs.TAG }}-update.tar.gz asset_name: polyhermes-${{ steps.extract_version.outputs.TAG }}-update.tar.gz asset_content_type: application/gzip - name: Upload Checksums to Release if: github.event_name == 'release' uses: actions/upload-release-asset@v1 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} with: upload_url: ${{ github.event.release.upload_url }} asset_path: ./checksums.txt asset_name: checksums.txt asset_content_type: text/plain - name: Upload Update Package as Artifact if: github.event_name == 'workflow_dispatch' uses: actions/upload-artifact@v4 with: name: polyhermes-${{ steps.extract_version.outputs.TAG }}-update path: | polyhermes-${{ steps.extract_version.outputs.TAG }}-update.tar.gz checksums.txt retention-days: 30 - name: Set up Docker Buildx if: steps.build_config.outputs.BUILD_TYPE == 'package-and-docker' uses: docker/setup-buildx-action@v3 with: # 启用多架构构建支持 platforms: linux/amd64,linux/arm64 - name: Log in to Docker Hub if: steps.build_config.outputs.BUILD_TYPE == 'package-and-docker' uses: docker/login-action@v3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Prepare Docker build context if: steps.build_config.outputs.BUILD_TYPE == 'package-and-docker' run: | echo "📦 准备 Docker 构建上下文..." # 确保构建产物存在且可访问 if [ ! -d "frontend/dist" ]; then echo "❌ 错误:frontend/dist 不存在" exit 1 fi if [ ! -d "backend/build/libs" ] || [ -z "$(ls -A backend/build/libs/*.jar 2>/dev/null)" ]; then echo "❌ 错误:backend/build/libs/*.jar 不存在" exit 1 fi echo "✅ 构建产物已准备好" ls -lh frontend/dist/ | head -5 ls -lh backend/build/libs/*.jar - name: Build and push Docker image if: steps.build_config.outputs.BUILD_TYPE == 'package-and-docker' uses: docker/build-push-action@v5 with: context: . file: ./Dockerfile push: true # 多架构构建:支持 amd64 和 arm64 platforms: linux/amd64,linux/arm64 tags: | wrbug/polyhermes:${{ steps.extract_version.outputs.TAG }} ${{ steps.extract_version.outputs.IS_PRERELEASE == 'false' && 'wrbug/polyhermes:latest' || '' }} build-args: | BUILD_IN_DOCKER=false VERSION=${{ steps.extract_version.outputs.VERSION }} GIT_TAG=${{ steps.extract_version.outputs.TAG }} GITHUB_REPO_URL=https://github.com/WrBug/PolyHermes cache-from: type=registry,ref=wrbug/polyhermes:latest cache-to: type=inline - name: Skip Docker build notice if: steps.build_config.outputs.BUILD_TYPE == 'package-only' run: | echo "⏭️ 跳过 Docker 镜像构建(构建类型:package-only)" echo "✅ 仅打包产物已完成" - name: Send Telegram notification if: steps.extract_version.outputs.IS_PRERELEASE == 'false' env: TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }} TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }} run: | # 检查必要的环境变量 if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then echo "⚠️ Telegram Bot Token 或 Chat ID 未配置,跳过通知" exit 0 fi # 获取构建信息 VERSION="${{ steps.extract_version.outputs.VERSION }}" TAG="${{ steps.extract_version.outputs.TAG }}" BUILD_TYPE="${{ steps.build_config.outputs.BUILD_TYPE }}" # 构建消息内容(仅包含关键信息) DEPLOY_DOC_URL="https://github.com/WrBug/PolyHermes/blob/main/docs/zh/DEPLOYMENT.md" if [ "${{ github.event_name }}" = "release" ]; then RELEASE_URL="${{ github.event.release.html_url }}" if [ "$BUILD_TYPE" = "package-and-docker" ]; then MESSAGE="✅ Release 构建成功"$'\n'$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔧 构建类型: Docker 升级"$'\n'"🔗 查看 Release"$'\n'"📚 Docker 部署文档" else MESSAGE="✅ Release 打包成功"$'\n'$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔧 构建类型: 在线升级"$'\n'"🔗 查看 Release"$'\n'"📍 升级路径: 系统管理 → 概览 → 检查更新" fi else WORKFLOW_URL="https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}" if [ "$BUILD_TYPE" = "package-and-docker" ]; then MESSAGE="✅ 构建成功"$'\n'$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔧 构建类型: Docker 升级"$'\n'"🔗 查看 Workflow"$'\n'"📚 Docker 部署文档" else MESSAGE="✅ 打包成功"$'\n'$'\n'"🏷️ Tag: ${TAG}"$'\n'"🔧 构建类型: 在线升级"$'\n'"🔗 查看 Workflow"$'\n'"📍 升级路径: 系统管理 → 概览 → 检查更新" fi fi # 发送 Telegram 消息(使用 jq 转义 JSON) curl -s -X POST "https://api.telegram.org/bot${TELEGRAM_BOT_TOKEN}/sendMessage" \ -H "Content-Type: application/json" \ -d "$(jq -n \ --arg chat_id "$TELEGRAM_CHAT_ID" \ --arg text "$MESSAGE" \ '{chat_id: $chat_id, text: $text, parse_mode: "HTML", disable_web_page_preview: false}')" > /tmp/telegram_response.json # 检查发送结果 if [ $? -eq 0 ]; then RESPONSE=$(cat /tmp/telegram_response.json) if echo "$RESPONSE" | grep -q '"ok":true'; then echo "✅ Telegram 通知发送成功" else echo "❌ Telegram 通知发送失败: $RESPONSE" # 构建成功,通知失败不应该导致整个 job 失败 exit 0 fi else echo "❌ 发送 Telegram 消息时发生错误" # 构建成功,通知失败不应该导致整个 job 失败 exit 0 fi