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"