317 lines
12 KiB
YAML
317 lines
12 KiB
YAML
name: Release
|
||
on:
|
||
push:
|
||
tags:
|
||
- 'v*'
|
||
# 支持手动触发(用于 dry-run 测试整个编译流程)
|
||
workflow_dispatch:
|
||
inputs:
|
||
dry_run:
|
||
description: 'Dry-run 模式:执行完整编译流程但不发布(不推送到 GitHub Release 和 Docker Hub)'
|
||
required: false
|
||
default: true
|
||
type: boolean
|
||
tag:
|
||
description: '模拟的 tag 版本号(如 v0.8.0-rc.3),dry-run 时必填'
|
||
required: false
|
||
type: string
|
||
|
||
# dry-run 判断:workflow_dispatch 触发 + dry_run=true
|
||
# 注意:push tag 触发时 inputs 不存在,github.event.inputs.dry_run 为空字符串
|
||
env:
|
||
IS_DRY_RUN: ${{ github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true' }}
|
||
|
||
jobs:
|
||
# 预热 Go 依赖缓存
|
||
go-cache:
|
||
uses: ./.github/workflows/go-cache.yaml
|
||
|
||
release-bins-sharded:
|
||
needs: go-cache
|
||
runs-on: ubuntu-latest
|
||
permissions:
|
||
contents: read
|
||
actions: read
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
shard_index: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
|
||
env:
|
||
SHARD_TOTAL: 10
|
||
SHARD_INDEX: ${{ matrix.shard_index }}
|
||
SENTRY_DSN: ${{ secrets.SENTRY_DSN }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
# fetch-depth: 0 获取完整历史和所有 tags
|
||
# 注意:不要使用 fetch-tags: true,会导致 tag 触发时 fetch 冲突
|
||
# 参考: https://github.com/actions/checkout/issues/1467
|
||
fetch-depth: 0
|
||
- name: Set GIT_TAG
|
||
run: |
|
||
if [ -n "${{ inputs.tag }}" ]; then
|
||
echo "GIT_TAG=${{ inputs.tag }}" >> $GITHUB_ENV
|
||
else
|
||
echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||
fi
|
||
- uses: actions/setup-go@v6
|
||
with:
|
||
go-version: '1.25'
|
||
cache: true
|
||
cache-dependency-path: '**/go.sum'
|
||
- name: Restore cache of built web (src/webapp/build)
|
||
id: cache_web_build_restore
|
||
uses: actions/cache/restore@v4
|
||
with:
|
||
path: |
|
||
src/webapp/build
|
||
key: web-build-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( '.node-version', 'src/webapp/yarn.lock', 'src/webapp/package.json', 'src/webapp/tsconfig.json', 'src/webapp/src/**', 'src/webapp/public/**') }}
|
||
- name: Setup Node.js (fallback if cache miss)
|
||
if: ${{ steps.cache_web_build_restore.outputs.cache-hit != 'true' }}
|
||
uses: actions/setup-node@v4
|
||
with:
|
||
node-version-file: '.node-version'
|
||
cache: yarn
|
||
cache-dependency-path: 'src/webapp/yarn.lock'
|
||
- name: Build Web Page (fallback if cache miss)
|
||
if: ${{ steps.cache_web_build_restore.outputs.cache-hit != 'true' }}
|
||
run: make build-web
|
||
- name: Save web build cache
|
||
if: ${{ steps.cache_web_build_restore.outputs.cache-hit != 'true' }}
|
||
uses: actions/cache/save@v4
|
||
with:
|
||
path: |
|
||
src/webapp/build
|
||
key: web-build-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles( '.node-version', 'src/webapp/yarn.lock', 'src/webapp/package.json', 'src/webapp/tsconfig.json', 'src/webapp/src/**', 'src/webapp/public/**') }}
|
||
- name: Build go binaries (shard ${{ matrix.shard_index }}/10)
|
||
run: make release-no-web
|
||
- name: Upload packaged artifacts only
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: bililive-bins-shard-${{ matrix.shard_index }}
|
||
path: |
|
||
bin/*.tar.gz
|
||
bin/*.zip
|
||
bin/*.7z
|
||
if-no-files-found: ignore
|
||
|
||
publish-release:
|
||
runs-on: ubuntu-latest
|
||
needs: [release-bins-sharded]
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
with:
|
||
fetch-depth: 0
|
||
- name: Set GIT_TAG
|
||
run: |
|
||
if [ -n "${{ inputs.tag }}" ]; then
|
||
echo "GIT_TAG=${{ inputs.tag }}" >> $GITHUB_ENV
|
||
else
|
||
echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||
fi
|
||
- name: Download all shard artifacts
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: dist
|
||
- name: List artifacts (dry-run verification)
|
||
run: |
|
||
echo "=== 已下载的构建产物 ==="
|
||
find dist -type f | sort
|
||
echo "=== 产物统计 ==="
|
||
echo "tar.gz: $(find dist -name '*.tar.gz' | wc -l) 个"
|
||
echo "zip: $(find dist -name '*.zip' | wc -l) 个"
|
||
echo "7z: $(find dist -name '*.7z' | wc -l) 个"
|
||
- name: Publish GitHub Release with all packaged files
|
||
if: ${{ env.IS_DRY_RUN != 'true' }}
|
||
uses: softprops/action-gh-release@v1
|
||
with:
|
||
files: |
|
||
dist/**/*.tar.gz
|
||
dist/**/*.zip
|
||
dist/**/*.7z
|
||
prerelease: ${{ contains(env.GIT_TAG, 'rc') }}
|
||
generate_release_notes: true
|
||
- name: "[Dry-Run] 跳过 GitHub Release 发布"
|
||
if: ${{ env.IS_DRY_RUN == 'true' }}
|
||
run: echo "::notice::Dry-run 模式:跳过 GitHub Release 发布(GIT_TAG=$GIT_TAG)"
|
||
|
||
# Docker 镜像构建:按平台分矩阵,arm64 使用原生 ARM runner
|
||
# 优化策略:
|
||
# 1. amd64/arm64 使用原生 runner,避免 QEMU 模拟
|
||
# 2. 内置工具在 CI 原生环境中预下载,COPY 进镜像(避免在 QEMU 下解压 xz)
|
||
# 3. bgo 二进制直接从 artifact 获取,不再从 GitHub Releases curl 下载
|
||
release-docker-images:
|
||
needs: [publish-release]
|
||
strategy:
|
||
fail-fast: false
|
||
matrix:
|
||
include:
|
||
- platform: linux/amd64
|
||
runner: ubuntu-latest
|
||
arch: amd64
|
||
- platform: linux/arm64/v8
|
||
runner: ubuntu-24.04-arm
|
||
arch: arm64
|
||
- platform: linux/arm/v7
|
||
runner: ubuntu-24.04-arm # arm/v7 仍需 QEMU,但 ARM runner 上模拟 armv7 比 x86 模拟快
|
||
arch: arm
|
||
runs-on: ${{ matrix.runner }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- name: Set GIT_TAG
|
||
run: |
|
||
if [ -n "${{ inputs.tag }}" ]; then
|
||
echo "GIT_TAG=${{ inputs.tag }}" >> $GITHUB_ENV
|
||
else
|
||
echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||
fi
|
||
|
||
# 下载编译好的二进制 artifact(从 release-bins-sharded 产物中提取)
|
||
- name: Download build artifacts
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: dist
|
||
|
||
# 准备 Docker 构建上下文:提取对应架构的二进制
|
||
- name: Prepare bgo binary for Docker
|
||
run: |
|
||
mkdir -p docker-context/bin
|
||
# 从 shard artifacts 中查找对应架构的 tar.gz
|
||
TARBALL=$(find dist -name "bililive-linux-${{ matrix.arch }}.tar.gz" -print -quit)
|
||
if [ -z "$TARBALL" ]; then
|
||
echo "::error::找不到 bililive-linux-${{ matrix.arch }}.tar.gz"
|
||
exit 1
|
||
fi
|
||
echo "找到二进制包: $TARBALL"
|
||
tar xzf "$TARBALL" -C docker-context/bin "bililive-linux-${{ matrix.arch }}"
|
||
mv "docker-context/bin/bililive-linux-${{ matrix.arch }}" docker-context/bin/bililive-go
|
||
chmod +x docker-context/bin/bililive-go
|
||
ls -la docker-context/bin/
|
||
|
||
# 在原生环境中预下载内置工具(避免在 QEMU 下解压 xz 的巨大开销)
|
||
# 版本号和下载 URL 自动从 src/tools/remote-tools-config.json 读取
|
||
# 缓存策略:以 config hash + 架构作为 key,避免重复下载
|
||
- name: Cache built-in tools
|
||
id: cache-tools
|
||
uses: actions/cache@v4
|
||
with:
|
||
path: docker-tools-cache/${{ matrix.arch }}
|
||
key: docker-tools-${{ matrix.arch }}-${{ hashFiles('src/tools/remote-tools-config.json', 'scripts/docker-prepare-tools.mjs') }}
|
||
- name: Pre-download built-in tools
|
||
if: steps.cache-tools.outputs.cache-hit != 'true'
|
||
run: node scripts/docker-prepare-tools.mjs "${{ matrix.arch }}" docker-tools-cache/${{ matrix.arch }}
|
||
- name: Copy tools to docker-context
|
||
run: |
|
||
mkdir -p docker-context/tools
|
||
cp -r docker-tools-cache/${{ matrix.arch }}/* docker-context/tools/ 2>/dev/null || true
|
||
|
||
# 仅 arm/v7 需要 QEMU(用于 apt-get 安装 ffmpeg 等)
|
||
- name: Set up QEMU
|
||
if: matrix.arch == 'arm'
|
||
uses: docker/setup-qemu-action@v3
|
||
|
||
- name: Docker Setup Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: Log in to Docker Hub
|
||
if: ${{ env.IS_DRY_RUN != 'true' }}
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKER_USERNAME }}
|
||
password: ${{ secrets.DOCKER_TOKEN }}
|
||
|
||
# 构建并推送单平台镜像(by digest,不关联 tag)
|
||
# dry-run 模式下只构建不推送(push: false)
|
||
- name: Build and push single-platform image
|
||
id: build
|
||
uses: docker/build-push-action@v5
|
||
with:
|
||
context: .
|
||
file: Dockerfile
|
||
platforms: ${{ matrix.platform }}
|
||
push: ${{ env.IS_DRY_RUN != 'true' }}
|
||
tags: ""
|
||
outputs: ${{ env.IS_DRY_RUN != 'true' && 'type=image,name=chigusa/bililive-go,push-by-digest=true,name-canonical=true' || '' }}
|
||
cache-from: type=gha,scope=docker-${{ matrix.arch }}
|
||
cache-to: type=gha,mode=max,scope=docker-${{ matrix.arch }}
|
||
|
||
# 导出 digest 供后续合并(仅非 dry-run)
|
||
- name: Export digest
|
||
if: ${{ env.IS_DRY_RUN != 'true' }}
|
||
run: |
|
||
mkdir -p /tmp/digests
|
||
digest="${{ steps.build.outputs.digest }}"
|
||
touch "/tmp/digests/${digest#sha256:}"
|
||
|
||
- name: Upload digest
|
||
if: ${{ env.IS_DRY_RUN != 'true' }}
|
||
uses: actions/upload-artifact@v4
|
||
with:
|
||
name: docker-digest-${{ matrix.arch }}
|
||
path: /tmp/digests/*
|
||
if-no-files-found: error
|
||
retention-days: 1
|
||
|
||
- name: "[Dry-Run] Docker 构建成功"
|
||
if: ${{ env.IS_DRY_RUN == 'true' }}
|
||
run: echo "::notice::Dry-run 模式:Docker 镜像 (${{ matrix.platform }}) 构建成功,跳过推送"
|
||
|
||
# 合并各平台镜像为 multi-arch manifest
|
||
merge-docker-manifest:
|
||
runs-on: ubuntu-latest
|
||
needs: [release-docker-images]
|
||
# dry-run 模式下跳过 manifest 合并(没有推送 digest)
|
||
if: ${{ !(github.event_name == 'workflow_dispatch' && github.event.inputs.dry_run == 'true') }}
|
||
steps:
|
||
- uses: actions/checkout@v4
|
||
- run: echo "GIT_TAG=`echo $(git describe --tags --abbrev=0)`" >> $GITHUB_ENV
|
||
- run: |
|
||
if ! echo $GIT_TAG | grep "rc" >/dev/null; then
|
||
DOCKER_TAGS="chigusa/bililive-go:$GIT_TAG,chigusa/bililive-go:latest"
|
||
else
|
||
DOCKER_TAGS="chigusa/bililive-go:$GIT_TAG"
|
||
fi
|
||
echo "DOCKER_TAGS=$DOCKER_TAGS" >> $GITHUB_ENV
|
||
|
||
- name: Download all digests
|
||
uses: actions/download-artifact@v4
|
||
with:
|
||
path: /tmp/digests
|
||
pattern: docker-digest-*
|
||
merge-multiple: true
|
||
|
||
- name: Docker Setup Buildx
|
||
uses: docker/setup-buildx-action@v3
|
||
|
||
- name: Log in to Docker Hub
|
||
uses: docker/login-action@v3
|
||
with:
|
||
username: ${{ secrets.DOCKER_USERNAME }}
|
||
password: ${{ secrets.DOCKER_TOKEN }}
|
||
|
||
- name: Create and push multi-arch manifest
|
||
working-directory: /tmp/digests
|
||
run: |
|
||
# 构建 digest 参数列表
|
||
# 验证文件名是否为 SHA256 格式(64个十六进制字符),跳过非 digest 文件
|
||
# 原因:actions/download-artifact 合并多个 artifact 时可能引入元数据文件
|
||
DIGEST_ARGS=""
|
||
for dgst in *; do
|
||
if echo "$dgst" | grep -qE '^[0-9a-f]{64}$'; then
|
||
DIGEST_ARGS="$DIGEST_ARGS chigusa/bililive-go@sha256:$dgst"
|
||
else
|
||
echo "警告: 跳过非 digest 文件: $dgst"
|
||
fi
|
||
done
|
||
|
||
# 为每个 tag 创建 manifest
|
||
IFS=',' read -ra TAGS <<< "$DOCKER_TAGS"
|
||
for tag in "${TAGS[@]}"; do
|
||
echo "创建 manifest: $tag"
|
||
docker buildx imagetools create -t "$tag" $DIGEST_ARGS
|
||
done
|
||
|
||
- name: Inspect final image
|
||
run: docker buildx imagetools inspect "chigusa/bililive-go:$GIT_TAG"
|
||
|