Skip to content

feat(channel-pricing): 渠道模型定价新增时间段(峰谷)价格 + opencode grok-4.5 /message… #4

feat(channel-pricing): 渠道模型定价新增时间段(峰谷)价格 + opencode grok-4.5 /message…

feat(channel-pricing): 渠道模型定价新增时间段(峰谷)价格 + opencode grok-4.5 /message… #4

Workflow file for this run

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"