#### ✨ **新功能 (New Features)** #14
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Go-Release | |
| # 触发条件配置 | |
| on: | |
| push: | |
| # 针对 master 分支的普通提交也触发 | |
| branches: | |
| - "master" | |
| paths: | |
| - "internal/app/version.go" | |
| # 权限配置:允许脚本写入仓库内容(用于发布 Release) | |
| permissions: | |
| contents: write | |
| packages: write | |
| env: | |
| NAME: ${{ github.event.repository.name }} | |
| jobs: | |
| check-version: | |
| if: github.actor == 'haierkeys' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| should_release: ${{ steps.check.outputs.should_release }} | |
| release_tag: ${{ steps.check.outputs.release_tag }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| fetch-depth: 0 | |
| - name: Check Version | |
| id: check | |
| run: | | |
| # 从 internal/app/version.go (HEAD) 读取当前版本 | |
| # 预期格式: var Version string = "0.0.1" | |
| CURRENT_VERSION=$(grep -E 'Version\s+string' internal/app/version.go | awk -F '"' '{print $2}') | |
| echo "当前版本 (HEAD): $CURRENT_VERSION" | |
| # 从 internal/app/version.go (HEAD~1) 获取上一个版本 | |
| # 使用 git show 获取上一次提交的文件内容 | |
| # 注意: 我们将其包裹在一个块中以处理 HEAD~1 可能不存在的情况 (例如: 初始提交) | |
| if git rev-parse --verify HEAD~1 >/dev/null 2>&1; then | |
| PREV_FILE_CONTENT=$(git show HEAD~1:internal/app/version.go) | |
| PREV_VERSION=$(echo "$PREV_FILE_CONTENT" | grep -E 'Version\s+string' | awk -F '"' '{print $2}') | |
| else | |
| PREV_VERSION="0.0.0" | |
| fi | |
| # 如果 PREV_VERSION 为空 (例如: grep 失败), 默认为 0.0.0 | |
| if [ -z "$PREV_VERSION" ]; then | |
| PREV_VERSION="0.0.0" | |
| fi | |
| echo "上一个版本 (HEAD~1): $PREV_VERSION" | |
| # 规范化版本以进行比较 (如果存在 'v' 前缀则移除) | |
| V_LAST=${PREV_VERSION#v} | |
| V_CURR=${CURRENT_VERSION#v} | |
| # 比较版本 | |
| if [ "$V_CURR" != "$V_LAST" ]; then | |
| # 使用 sort -V 确定当前版本是否严格大于旧版本 | |
| NEWER_VERSION=$(echo -e "$V_LAST\n$V_CURR" | sort -V | tail -n 1) | |
| if [ "$NEWER_VERSION" == "$V_CURR" ] && [ "$NEWER_VERSION" != "$V_LAST" ]; then | |
| echo "检测到新版本: $V_CURR > $V_LAST" | |
| echo "should_release=true" >> $GITHUB_OUTPUT | |
| echo "release_tag=$V_CURR" >> $GITHUB_OUTPUT | |
| else | |
| echo "版本 $V_CURR 不大于 $V_LAST。跳过发布..." | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| else | |
| echo "版本匹配 ($V_CURR == $V_LAST)。跳过发布..." | |
| echo "should_release=false" >> $GITHUB_OUTPUT | |
| fi | |
| prepare-message: | |
| needs: check-version | |
| if: needs.check-version.outputs.should_release == 'true' && github.actor == 'haierkeys' | |
| runs-on: ubuntu-latest | |
| outputs: | |
| commit_msg: ${{ steps.trans.outputs.commit_msg }} | |
| steps: | |
| - uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| fetch-depth: 0 | |
| - name: Prepare Commit Message | |
| id: trans | |
| run: | | |
| # 获取提交文本信息 | |
| msg=$(git log -1 --pretty=%B) | |
| # 设置 Python 进行翻译 | |
| pip install deep-translator > /dev/null 2>&1 || true | |
| # 运行翻译脚本 | |
| export COMMIT_MSG="$msg" | |
| echo "commit_msg<<EOF" >> $GITHUB_OUTPUT | |
| if [ -f "scripts/translate_commit.py" ]; then | |
| python3 scripts/translate_commit.py >> $GITHUB_OUTPUT | |
| else | |
| echo "未找到翻译脚本,使用原始消息" | |
| echo "$msg" >> $GITHUB_OUTPUT | |
| fi | |
| echo "EOF" >> $GITHUB_OUTPUT | |
| create-release: | |
| needs: [check-version, prepare-message] | |
| if: needs.check-version.outputs.should_release == 'true' && github.actor == 'haierkeys' | |
| name: Create Release | |
| runs-on: ubuntu-latest | |
| outputs: | |
| upload_url: ${{ steps.create_release.outputs.upload_url }} | |
| steps: | |
| - name: Create Release | |
| id: create_release | |
| uses: softprops/action-gh-release@v2 | |
| env: | |
| token: ${{ secrets.GITHUB_TOKEN }} | |
| with: | |
| tag_name: ${{ needs.check-version.outputs.release_tag }} | |
| name: ${{ needs.check-version.outputs.release_tag }} | |
| draft: false | |
| prerelease: false | |
| generate_release_notes: true | |
| body: ${{ needs.prepare-message.outputs.commit_msg }} | |
| target_commitish: ${{ github.sha }} # 明确指定在当前分支的 commit 上创建 tag | |
| overwrite_files: true | |
| build-binaries: | |
| needs: [check-version, prepare-message, create-release] | |
| if: needs.check-version.outputs.should_release == 'true' && github.actor == 'haierkeys' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| fetch-depth: 0 | |
| - uses: actions/setup-go@v6 | |
| with: | |
| go-version-file: 'go.mod' | |
| - name: Check Go Version | |
| run: go version | |
| - name: Go Build Prepare | |
| run: go install github.com/mitchellh/gox@latest | |
| - name: Go Build Multi-platform | |
| env: | |
| COMMIT_MSG: ${{ needs.prepare-message.outputs.commit_msg }} | |
| run: make gox-all | |
| - name: Create Changelog | |
| env: | |
| COMMIT_MSG: ${{ needs.prepare-message.outputs.commit_msg }} | |
| run: | | |
| echo "${COMMIT_MSG}" > ./build/changelog.txt | |
| - name: Create GZip Archives for All Platforms | |
| env: | |
| RELEASE_BASENAME: ${{ env.NAME }}-${{ needs.check-version.outputs.release_tag }} | |
| run: | | |
| # 为所有平台创建 tar.gz 包 | |
| # Create tar.gz packages for all platforms | |
| declare -a PLATFORMS=("darwin_amd64" "darwin_arm64" "linux_amd64" "linux_arm64" "linux_arm" "windows_amd64") | |
| for PLATFORM in "${PLATFORMS[@]}"; do | |
| GOOS=$(echo "$PLATFORM" | cut -d'_' -f1) | |
| GOARCH=$(echo "$PLATFORM" | cut -d'_' -f2) | |
| # 这里的重命名逻辑:如果是 linux_arm,则将后缀改为 armv7l | |
| # Rename linux_arm to linux-armv7l for easier identification | |
| TARGET_ARCH=$GOARCH | |
| if [ "$PLATFORM" == "linux_arm" ]; then | |
| TARGET_ARCH="armv7l" | |
| fi | |
| ARCHIVE_NAME="${RELEASE_BASENAME}-${GOOS}-${TARGET_ARCH}.tar.gz" | |
| echo "Creating archive: ${ARCHIVE_NAME}" | |
| tar -czvf "./build/${ARCHIVE_NAME}" ./config -C "./build/${PLATFORM}/" . | |
| done | |
| - name: Upload Build Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: build_file | |
| path: ./build/ | |
| - name: Upload Config Artifacts | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: config | |
| path: ./config | |
| - name: Upload Release Archives | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: release_archives | |
| path: | | |
| ./build/${{ env.NAME }}-${{ needs.check-version.outputs.release_tag }}-*.tar.gz | |
| ./build/changelog.txt | |
| push-docker: | |
| needs: [check-version, build-binaries,create-release] | |
| if: needs.check-version.outputs.should_release == 'true' && github.actor == 'haierkeys' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| - name: Download Build Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: build_file | |
| path: ./build/ | |
| - name: Download Config Artifacts | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: config | |
| path: ./config | |
| - name: Set Environment Variables | |
| run: | | |
| # NAME is already set globally | |
| # Use the tag from check-version | |
| TAG_VERSION=${{ needs.check-version.outputs.release_tag }} | |
| echo "TAG_VERSION=${TAG_VERSION}" >> ${GITHUB_ENV} | |
| echo "IMAGE_TAG=${TAG_VERSION}" >> ${GITHUB_ENV} | |
| echo "BUILD_DATE=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> ${GITHUB_ENV} | |
| echo "GIT_COMMIT=$(git rev-parse --short HEAD)" >> ${GITHUB_ENV} | |
| - name: Append 'latest' tag if on main branch | |
| if: github.ref == 'refs/heads/main' || github.ref == 'refs/heads/master' | |
| run: echo "IMAGE_TAG=${{ env.IMAGE_TAG }},latest" >> ${GITHUB_ENV} | |
| - uses: docker/setup-qemu-action@v2 | |
| - uses: docker/setup-buildx-action@v2 | |
| - name: Docker Build & Publish to GitHub Container Registry | |
| uses: elgohr/Publish-Docker-Github-Action@v5 | |
| with: | |
| dockerfile: docker/Dockerfile | |
| name: ${{ github.actor }}/${{ env.NAME }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.GITHUB_TOKEN }} | |
| platforms: linux/amd64,linux/arm64,linux/arm/v7 | |
| registry: ghcr.io | |
| snapshot: false | |
| tags: "${{ env.IMAGE_TAG }}" | |
| buildargs: | | |
| VERSION=${{ env.TAG_VERSION }} | |
| BUILD_DATE=${{ env.BUILD_DATE }} | |
| GIT_COMMIT=${{ env.GIT_COMMIT }} | |
| - name: Docker Build & Publish to DockerHub | |
| uses: elgohr/Publish-Docker-Github-Action@v5 | |
| with: | |
| dockerfile: docker/Dockerfile | |
| name: ${{ github.actor }}/${{ env.NAME }} | |
| username: ${{ github.actor }} | |
| password: ${{ secrets.DOCKERHUB_TOKEN }} | |
| platforms: linux/amd64,linux/arm64,linux/arm/v7 | |
| snapshot: false | |
| tags: "${{ env.IMAGE_TAG }}" | |
| buildargs: | | |
| VERSION=${{ env.TAG_VERSION }} | |
| BUILD_DATE=${{ env.BUILD_DATE }} | |
| GIT_COMMIT=${{ env.GIT_COMMIT }} | |
| - name: Docker Build & Publish to CNB | |
| uses: elgohr/Publish-Docker-Github-Action@v5 | |
| with: | |
| dockerfile: docker/Dockerfile | |
| name: haierkeys/${{ env.NAME }} | |
| username: ${{ secrets.CNB_USERNAME }} | |
| password: ${{ secrets.CNB_TOKEN }} | |
| platforms: linux/amd64,linux/arm64,linux/arm/v7 | |
| registry: docker.cnb.cool | |
| snapshot: false | |
| tags: "${{ env.IMAGE_TAG }}" | |
| buildargs: | | |
| VERSION=${{ env.TAG_VERSION }} | |
| BUILD_DATE=${{ env.BUILD_DATE }} | |
| GIT_COMMIT=${{ env.GIT_COMMIT }} | |
| push-release-files: | |
| needs: [create-release, build-binaries, check-version] | |
| if: needs.check-version.outputs.should_release == 'true' | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Download Release Archives | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release_archives | |
| path: ./archives/ | |
| - name: Upload Archives to GitHub Release | |
| uses: softprops/action-gh-release@v2 | |
| with: | |
| tag_name: ${{ needs.check-version.outputs.release_tag }} | |
| files: ./archives/* | |
| push-cnb-release: | |
| needs: [check-version, prepare-message, build-binaries, create-release] | |
| if: needs.check-version.outputs.should_release == 'true' | |
| name: Push CNB Release | |
| runs-on: ubuntu-latest | |
| env: | |
| CNB_REPO: haierkeys/fast-note-sync-service | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ github.ref }} | |
| fetch-depth: 0 | |
| - name: Push Tag to CNB | |
| env: | |
| CNB_TOKEN: ${{ secrets.CNB_TOKEN }} | |
| RELEASE_TAG: ${{ needs.check-version.outputs.release_tag }} | |
| run: | | |
| # 确保拉取了最新的 tag 信息 | |
| git fetch --tags | |
| git remote add cnb "https://cnb:${CNB_TOKEN}@cnb.cool/${CNB_REPO}.git" | |
| # 直接推送已存在的 tag | |
| git push cnb "${RELEASE_TAG}" --force | |
| - name: Download Release Archives | |
| uses: actions/download-artifact@v4 | |
| with: | |
| name: release_archives | |
| path: ./archives/ | |
| - name: Create CNB Release and Upload Assets | |
| env: | |
| CNB_TOKEN: ${{ secrets.CNB_TOKEN }} | |
| RELEASE_TAG: ${{ needs.check-version.outputs.release_tag }} | |
| RELEASE_BODY: ${{ needs.prepare-message.outputs.commit_msg }} | |
| run: | | |
| set -e | |
| # ============================================================ | |
| # 第一步: 创建 CNB Release | |
| # Step 1: Create CNB Release | |
| # ============================================================ | |
| echo "::group::Creating CNB Release: ${RELEASE_TAG}" | |
| RELEASE_RESPONSE=$(curl -s -w "\n%{http_code}" -X POST \ | |
| -H "Accept: application/vnd.cnb.api+json" \ | |
| -H "Authorization: Bearer ${CNB_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$(jq -n \ | |
| --arg tag "${RELEASE_TAG}" \ | |
| --arg name "${RELEASE_TAG}" \ | |
| --arg body "${RELEASE_BODY}" \ | |
| '{tag_name: $tag, name: $name, body: $body, prerelease: false, draft: false}')" \ | |
| "https://api.cnb.cool/${CNB_REPO}/-/releases") | |
| HTTP_CODE=$(echo "$RELEASE_RESPONSE" | tail -1) | |
| RESPONSE_BODY=$(echo "$RELEASE_RESPONSE" | sed '$d') | |
| if [ "$HTTP_CODE" -ge 200 ] && [ "$HTTP_CODE" -lt 300 ]; then | |
| RELEASE_ID=$(echo "$RESPONSE_BODY" | jq -r '.id') | |
| echo "CNB Release created successfully, ID: ${RELEASE_ID}" | |
| else | |
| echo "::warning::Failed to create CNB Release (HTTP ${HTTP_CODE}), attempting to fetch existing release..." | |
| # 尝试获取已存在的 Release / Try to get existing release | |
| LIST_RESPONSE=$(curl -s \ | |
| -H "Accept: application/vnd.cnb.api+json" \ | |
| -H "Authorization: Bearer ${CNB_TOKEN}" \ | |
| "https://api.cnb.cool/${CNB_REPO}/-/releases") | |
| RELEASE_ID=$(echo "$LIST_RESPONSE" | jq -r ".[] | select(.tag_name==\"${RELEASE_TAG}\") | .id") | |
| if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then | |
| echo "::error::Cannot create or find CNB Release for tag ${RELEASE_TAG}" | |
| exit 1 | |
| fi | |
| echo "Found existing CNB Release, ID: ${RELEASE_ID}" | |
| fi | |
| echo "::endgroup::" | |
| # ============================================================ | |
| # 第二步: 并行上传所有已打包的构建产物 | |
| # Step 2: Parallel upload all pre-built release archives | |
| # ============================================================ | |
| for FILE_PATH in ./archives/*; do | |
| ( | |
| FILE_NAME=$(basename "$FILE_PATH") | |
| echo "Uploading ${FILE_NAME}..." | |
| FILE_SIZE=$(stat -c%s "$FILE_PATH") | |
| # 2a. 获取上传 URL / Get upload URL | |
| UPLOAD_RESPONSE=$(curl -s -X POST \ | |
| -H "Accept: application/vnd.cnb.api+json" \ | |
| -H "Authorization: Bearer ${CNB_TOKEN}" \ | |
| -H "Content-Type: application/json" \ | |
| -d "$(jq -n \ | |
| --arg name "${FILE_NAME}" \ | |
| --argjson size ${FILE_SIZE} \ | |
| '{asset_name: $name, size: $size, overwrite: true}')" \ | |
| "https://api.cnb.cool/${CNB_REPO}/-/releases/${RELEASE_ID}/asset-upload-url") | |
| UPLOAD_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.upload_url') | |
| VERIFY_URL=$(echo "$UPLOAD_RESPONSE" | jq -r '.verify_url') | |
| if [ -z "$UPLOAD_URL" ] || [ "$UPLOAD_URL" = "null" ]; then | |
| echo "::error::Failed to get upload URL for ${FILE_NAME}" | |
| echo "Response: ${UPLOAD_RESPONSE}" | |
| exit 1 | |
| fi | |
| # 2b. 上传文件内容 / Upload file content | |
| echo "Uploading ${FILE_NAME} to: ${UPLOAD_URL}" | |
| CONTENT_TYPE="application/gzip" | |
| if [[ "${FILE_NAME}" == *.txt ]]; then | |
| CONTENT_TYPE="text/plain" | |
| fi | |
| UPLOAD_RESULT=$(curl -s -w "\n%{http_code}" -X PUT \ | |
| -H "Content-Type: ${CONTENT_TYPE}" \ | |
| --data-binary @"$FILE_PATH" \ | |
| "$UPLOAD_URL") | |
| UPLOAD_HTTP_CODE=$(echo "$UPLOAD_RESULT" | tail -1) | |
| if [ "$UPLOAD_HTTP_CODE" -ne 200 ] && [ "$UPLOAD_HTTP_CODE" -ne 201 ]; then | |
| echo "::error::Failed to upload ${FILE_NAME} (HTTP ${UPLOAD_HTTP_CODE})" | |
| exit 1 | |
| fi | |
| # 2c. 确认上传完成 / Confirm upload | |
| echo "Confirming upload for ${FILE_NAME}..." | |
| CONFIRM_RESULT=$(curl -s -w "\n%{http_code}" -X POST \ | |
| -H "Accept: application/vnd.cnb.api+json" \ | |
| -H "Authorization: Bearer ${CNB_TOKEN}" \ | |
| "${VERIFY_URL}") | |
| CONFIRM_HTTP_CODE=$(echo "$CONFIRM_RESULT" | tail -1) | |
| if [ "$CONFIRM_HTTP_CODE" -ne 200 ] && [ "$CONFIRM_HTTP_CODE" -ne 201 ]; then | |
| echo "::error::Failed to confirm upload for ${FILE_NAME} (HTTP ${CONFIRM_HTTP_CODE})" | |
| exit 1 | |
| fi | |
| echo "✅ ${FILE_NAME} uploaded successfully" | |
| ) & | |
| done | |
| # 等待所有后台任务完成 | |
| wait | |
| echo "🎉 All assets uploaded to CNB Release!" |