feat(channel-pricing): 渠道模型定价新增时间段(峰谷)价格 + opencode grok-4.5 /message… #4
Workflow file for this run
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: 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: | |
| resolve-release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| release_sha: ${{ steps.resolve.outputs.release_sha }} | |
| tag_name: ${{ steps.resolve.outputs.tag_name }} | |
| version: ${{ steps.resolve.outputs.version }} | |
| steps: | |
| - name: Checkout release resolver | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| - name: Resolve immutable release commit | |
| id: resolve | |
| env: | |
| EVENT_NAME: ${{ github.event_name }} | |
| INPUT_TAG: ${{ github.event.inputs.tag }} | |
| EVENT_REF: ${{ github.ref }} | |
| run: | | |
| set -euo pipefail | |
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | |
| TAG_NAME=$INPUT_TAG | |
| else | |
| case "$EVENT_REF" in | |
| refs/tags/*) TAG_NAME=${EVENT_REF#refs/tags/} ;; | |
| *) echo "Release must be triggered by a tag" >&2; exit 1 ;; | |
| esac | |
| fi | |
| if ! [[ "$TAG_NAME" =~ ^v[0-9]+\.[0-9]+\.[0-9]+([.-][0-9A-Za-z][0-9A-Za-z.-]*)?$ ]]; then | |
| echo "Invalid release tag: $TAG_NAME" >&2 | |
| exit 1 | |
| fi | |
| git fetch --force origin "refs/tags/$TAG_NAME:refs/tags/$TAG_NAME" | |
| RELEASE_SHA=$(git rev-parse "$TAG_NAME^{commit}") | |
| if ! [[ "$RELEASE_SHA" =~ ^[0-9a-f]{40}$ ]]; then | |
| echo "Unable to resolve immutable commit for $TAG_NAME" >&2 | |
| exit 1 | |
| fi | |
| echo "tag_name=$TAG_NAME" >> "$GITHUB_OUTPUT" | |
| echo "version=${TAG_NAME#v}" >> "$GITHUB_OUTPUT" | |
| echo "release_sha=$RELEASE_SHA" >> "$GITHUB_OUTPUT" | |
| echo "Resolved $TAG_NAME to $RELEASE_SHA" | |
| quality-gate: | |
| needs: resolve-release | |
| uses: ./.github/workflows/backend-ci.yml | |
| with: | |
| frontend_coverage: true | |
| upload_frontend_artifact: true | |
| checkout_ref: ${{ needs.resolve-release.outputs.release_sha }} | |
| security-gate: | |
| needs: resolve-release | |
| uses: ./.github/workflows/security-scan.yml | |
| with: | |
| checkout_ref: ${{ needs.resolve-release.outputs.release_sha }} | |
| # Update VERSION file with tag version | |
| update-version: | |
| needs: resolve-release | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| ref: ${{ needs.resolve-release.outputs.release_sha }} | |
| - name: Update VERSION file | |
| env: | |
| VERSION: ${{ needs.resolve-release.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| 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 | |
| release: | |
| needs: [resolve-release, quality-gate, security-gate, update-version] | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Checkout | |
| uses: actions/checkout@v6 | |
| with: | |
| fetch-depth: 0 | |
| ref: ${{ needs.resolve-release.outputs.release_sha }} | |
| - 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.26.5' | |
| # 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 | |
| env: | |
| TAG_NAME: ${{ needs.resolve-release.outputs.tag_name }} | |
| EXPECTED_SHA: ${{ needs.resolve-release.outputs.release_sha }} | |
| run: | | |
| set -euo pipefail | |
| git fetch --force origin "refs/tags/$TAG_NAME:refs/tags/$TAG_NAME" | |
| ACTUAL_SHA=$(git rev-parse "$TAG_NAME^{commit}") | |
| if [ "$ACTUAL_SHA" != "$EXPECTED_SHA" ]; then | |
| echo "Tag moved after validation: expected=$EXPECTED_SHA actual=$ACTUAL_SHA" >&2 | |
| exit 1 | |
| fi | |
| - name: Get tag message | |
| id: tag_message | |
| env: | |
| TAG_NAME: ${{ needs.resolve-release.outputs.tag_name }} | |
| run: | | |
| set -euo pipefail | |
| 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 | |
| # 使用不可预测的分隔符安全处理不受信任的多行 tag 内容。 | |
| DELIMITER="TAG_MESSAGE_$(openssl rand -hex 16)" | |
| { | |
| echo "message<<$DELIMITER" | |
| echo "$TAG_MESSAGE" | |
| echo "$DELIMITER" | |
| } >> "$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 ${{ 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 }}/pixelapi | |
| short-description: "PixelAPI - 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 }} | |
| TAG_NAME: ${{ needs.resolve-release.outputs.tag_name }} | |
| TAG_MESSAGE: ${{ steps.tag_message.outputs.message }} | |
| REPO: ${{ github.repository }} | |
| 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 | |
| VERSION=${TAG_NAME#v} | |
| GHCR_IMAGE="ghcr.io/${REPO,,}" # ${,,} converts to lowercase | |
| # 获取 tag message 内容并转义 Markdown 特殊字符 | |
| 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="🚀 *PixelAPI 新版本发布!*"$'\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}/pixelapi" | |
| 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/pixelapi)"$'\n'$'\n' | |
| MESSAGE+="#PixelAPI #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: [resolve-release, 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 | |
| env: | |
| DEFAULT_BRANCH: ${{ github.event.repository.default_branch }} | |
| VERSION: ${{ needs.resolve-release.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| 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:$DEFAULT_BRANCH" |