| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306 |
- name: Release
- on:
- push:
- tags:
- - 'v*'
- workflow_dispatch:
- inputs:
- tag:
- description: 'Tag to release (e.g., v1.0.0)'
- required: true
- type: string
- simple_release:
- description: 'Simple release: only x86_64 GHCR image, skip other artifacts'
- required: false
- type: boolean
- default: false
- # 环境变量:合并 workflow_dispatch 输入和 repository variable
- # tag push 触发时读取 vars.SIMPLE_RELEASE,workflow_dispatch 时使用输入参数
- env:
- SIMPLE_RELEASE: ${{ github.event.inputs.simple_release == 'true' || vars.SIMPLE_RELEASE == 'true' }}
- permissions:
- contents: write
- packages: write
- jobs:
- # Update VERSION file with tag version
- update-version:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v6
- - name: Update VERSION file
- run: |
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
- VERSION=${{ github.event.inputs.tag }}
- VERSION=${VERSION#v}
- else
- VERSION=${GITHUB_REF#refs/tags/v}
- fi
- echo "$VERSION" > backend/cmd/server/VERSION
- echo "Updated VERSION file to: $VERSION"
- - name: Upload VERSION artifact
- uses: actions/upload-artifact@v7
- with:
- name: version-file
- path: backend/cmd/server/VERSION
- retention-days: 1
- build-frontend:
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v6
- - name: Setup pnpm
- uses: pnpm/action-setup@v6
- with:
- version: 9
- - name: Setup Node.js
- uses: actions/setup-node@v6
- with:
- node-version: '20'
- cache: 'pnpm'
- cache-dependency-path: frontend/pnpm-lock.yaml
- - name: Install dependencies
- run: pnpm install --frozen-lockfile
- working-directory: frontend
- - name: Build frontend
- run: pnpm run build
- working-directory: frontend
- - name: Upload frontend artifact
- uses: actions/upload-artifact@v7
- with:
- name: frontend-dist
- path: backend/internal/web/dist/
- retention-days: 1
- release:
- needs: [update-version, build-frontend]
- runs-on: ubuntu-latest
- steps:
- - name: Checkout
- uses: actions/checkout@v6
- with:
- fetch-depth: 0
- ref: ${{ github.event.inputs.tag || github.ref }}
- - name: Download VERSION artifact
- uses: actions/download-artifact@v8
- with:
- name: version-file
- path: backend/cmd/server/
- - name: Download frontend artifact
- uses: actions/download-artifact@v8
- with:
- name: frontend-dist
- path: backend/internal/web/dist/
- - name: Setup Go
- uses: actions/setup-go@v6
- with:
- go-version-file: backend/go.mod
- check-latest: false
- cache-dependency-path: backend/go.sum
- - name: Verify Go version
- run: |
- go version | grep -q 'go1.27.0'
- # Docker setup for GoReleaser
- - name: Set up QEMU
- uses: docker/setup-qemu-action@v3
- - name: Set up Docker Buildx
- uses: docker/setup-buildx-action@v3
- - name: Login to DockerHub
- if: ${{ env.DOCKERHUB_USERNAME != '' }}
- uses: docker/login-action@v3
- env:
- DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
- - name: Login to GitHub Container Registry
- uses: docker/login-action@v3
- with:
- registry: ghcr.io
- username: ${{ github.repository_owner }}
- password: ${{ secrets.GITHUB_TOKEN }}
- - name: Fetch tags with annotations
- run: |
- # 确保获取完整的 annotated tag 信息
- git fetch --tags --force
- - name: Get tag message
- id: tag_message
- run: |
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
- TAG_NAME=${{ github.event.inputs.tag }}
- else
- TAG_NAME=${GITHUB_REF#refs/tags/}
- fi
- echo "Processing tag: $TAG_NAME"
- # 获取完整的 tag message(跳过第一行标题)
- TAG_MESSAGE=$(git tag -l --format='%(contents:body)' "$TAG_NAME")
- # 调试输出
- echo "Tag message length: ${#TAG_MESSAGE}"
- echo "Tag message preview:"
- echo "$TAG_MESSAGE" | head -10
- # 使用 EOF 分隔符处理多行内容
- echo "message<<EOF" >> $GITHUB_OUTPUT
- echo "$TAG_MESSAGE" >> $GITHUB_OUTPUT
- echo "EOF" >> $GITHUB_OUTPUT
- - name: Set lowercase owner for GHCR
- id: lowercase
- run: echo "owner=$(echo '${{ github.repository_owner }}' | tr '[:upper:]' '[:lower:]')" >> $GITHUB_OUTPUT
- - name: Run GoReleaser
- uses: goreleaser/goreleaser-action@v7
- with:
- version: '~> v2'
- args: release --clean --skip=validate ${{ env.SIMPLE_RELEASE == 'true' && '--config=.goreleaser.simple.yaml' || '' }}
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- TAG_MESSAGE: ${{ steps.tag_message.outputs.message }}
- GITHUB_REPO_OWNER: ${{ github.repository_owner }}
- GITHUB_REPO_OWNER_LOWER: ${{ steps.lowercase.outputs.owner }}
- GITHUB_REPO_NAME: ${{ github.event.repository.name }}
- DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME || 'skip' }}
- # Update DockerHub description
- - name: Update DockerHub description
- if: ${{ env.SIMPLE_RELEASE != 'true' && env.DOCKERHUB_USERNAME != '' }}
- uses: peter-evans/dockerhub-description@v5
- env:
- DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
- with:
- username: ${{ secrets.DOCKERHUB_USERNAME }}
- password: ${{ secrets.DOCKERHUB_TOKEN }}
- repository: ${{ secrets.DOCKERHUB_USERNAME }}/sub2api
- short-description: "Sub2API - AI API Gateway Platform"
- readme-filepath: ./deploy/DOCKER.md
- # Send Telegram notification
- - name: Send Telegram Notification
- if: ${{ env.SIMPLE_RELEASE != 'true' }}
- env:
- TELEGRAM_BOT_TOKEN: ${{ secrets.TELEGRAM_BOT_TOKEN }}
- TELEGRAM_CHAT_ID: ${{ secrets.TELEGRAM_CHAT_ID }}
- DOCKERHUB_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
- continue-on-error: true
- run: |
- # 检查必要的环境变量
- if [ -z "$TELEGRAM_BOT_TOKEN" ] || [ -z "$TELEGRAM_CHAT_ID" ]; then
- echo "Telegram credentials not configured, skipping notification"
- exit 0
- fi
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
- TAG_NAME=${{ github.event.inputs.tag }}
- else
- TAG_NAME=${GITHUB_REF#refs/tags/}
- fi
- VERSION=${TAG_NAME#v}
- REPO="${{ github.repository }}"
- GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase
- # 获取 tag message 内容并转义 Markdown 特殊字符
- TAG_MESSAGE='${{ steps.tag_message.outputs.message }}'
- TAG_MESSAGE=$(echo "$TAG_MESSAGE" | sed 's/\([_*`\[]\)/\\\1/g')
- # 限制消息长度(Telegram 消息限制 4096 字符,预留空间给头尾固定内容)
- if [ ${#TAG_MESSAGE} -gt 3500 ]; then
- TAG_MESSAGE="${TAG_MESSAGE:0:3500}..."
- fi
- # 构建消息内容
- MESSAGE="🚀 *Sub2API 新版本发布!*"$'\n'$'\n'
- MESSAGE+="📦 版本号: \`${VERSION}\`"$'\n'$'\n'
- # 添加更新内容
- if [ -n "$TAG_MESSAGE" ]; then
- MESSAGE+="${TAG_MESSAGE}"$'\n'$'\n'
- fi
- MESSAGE+="🐳 *Docker 部署:*"$'\n'
- MESSAGE+="\`\`\`bash"$'\n'
- # 根据是否配置 DockerHub 动态生成
- if [ -n "$DOCKERHUB_USERNAME" ]; then
- DOCKER_IMAGE="${DOCKERHUB_USERNAME}/sub2api"
- MESSAGE+="# Docker Hub"$'\n'
- MESSAGE+="docker pull ${DOCKER_IMAGE}:${VERSION}"$'\n'
- MESSAGE+="# GitHub Container Registry"$'\n'
- fi
- MESSAGE+="docker pull ${GHCR_IMAGE}:${VERSION}"$'\n'
- MESSAGE+="\`\`\`"$'\n'$'\n'
- MESSAGE+="🔗 *相关链接:*"$'\n'
- MESSAGE+="• [GitHub Release](https://github.com/${REPO}/releases/tag/${TAG_NAME})"$'\n'
- if [ -n "$DOCKERHUB_USERNAME" ]; then
- MESSAGE+="• [Docker Hub](https://hub.docker.com/r/${DOCKER_IMAGE})"$'\n'
- fi
- MESSAGE+="• [GitHub Packages](https://github.com/${REPO}/pkgs/container/sub2api)"$'\n'$'\n'
- MESSAGE+="#Sub2API #Release #${TAG_NAME//./_}"
- # 发送消息
- 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: "Markdown",
- disable_web_page_preview: true
- }')"
- sync-version-file:
- needs: [release]
- if: ${{ needs.release.result == 'success' }}
- runs-on: ubuntu-latest
- steps:
- - name: Checkout default branch
- uses: actions/checkout@v6
- with:
- ref: ${{ github.event.repository.default_branch }}
- - name: Sync VERSION file to released tag
- run: |
- if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
- VERSION=${{ github.event.inputs.tag }}
- VERSION=${VERSION#v}
- else
- VERSION=${GITHUB_REF#refs/tags/v}
- fi
- CURRENT_VERSION=$(tr -d '\r\n' < backend/cmd/server/VERSION || true)
- if [ "$CURRENT_VERSION" = "$VERSION" ]; then
- echo "VERSION file already matches $VERSION"
- exit 0
- fi
- echo "$VERSION" > backend/cmd/server/VERSION
- git config user.name "github-actions[bot]"
- git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
- git add backend/cmd/server/VERSION
- git commit -m "chore: sync VERSION to ${VERSION} [skip ci]"
- git push origin HEAD:${{ github.event.repository.default_branch }}
|