docs(readme): retake screenshots at full resolution #26
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
| # 打 tag 发版。构建只发生在这里 —— 本机不产出、也拿不到发布密钥, | |
| # 所以「这个 APK 是从这份源码编出来的」这句话有据可查。 | |
| # | |
| # 两份构建来源证明:job `release` 里的 attest-build-provenance(GitHub 自己核,快), | |
| # 以及 job `provenance` 走 slsa-github-generator 出的 SLSA Build L3 证明。后者跑在 | |
| # 隔离的可复用 workflow 里,拿不到这个仓库的构建脚本和签名密钥 —— 这正是 L2 与 L3 | |
| # 的区别所在。 | |
| # | |
| # 触发:推一个 `v` 开头的 tag,例如 `git tag v0.1.0 && git push origin v0.1.0`。 | |
| # 版本号取自 tag(去掉 `v`),传给 Gradle 的 -PbilbyVersion,不写在源码里。 | |
| # | |
| # 需要两个仓库 Secret: | |
| # BILBY_KEYSTORE_BASE64 —— 签名库的 base64 | |
| # BILBY_KEYSTORE_PASSWORD / BILBY_KEY_ALIAS / BILBY_KEY_PASSWORD | |
| name: Release | |
| on: | |
| push: | |
| tags: | |
| - "v*" | |
| permissions: | |
| contents: write | |
| # 构建来源证明要签名,需要这两项。它是「CI 构建」这句话的凭据本身。 | |
| id-token: write | |
| attestations: write | |
| jobs: | |
| release: | |
| runs-on: ubuntu-latest | |
| outputs: | |
| # 交给下面那个隔离的 provenance job。传的是摘要不是文件:generator 不信任、 | |
| # 也不需要拿到产物本身。 | |
| hashes: ${{ steps.collect.outputs.hashes }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| - uses: actions/setup-java@v5 | |
| with: | |
| distribution: temurin | |
| # compileOptions 是 21,这里不能更低。 | |
| java-version: "21" | |
| # 停在 v5,不跟到 v6。v6 把缓存拆成了闭源的 gradle-actions-caching,升上去 | |
| # 等于接受 Gradle 的商业 Terms of Use —— 这个仓库是 GPL-3.0,不为了一个 | |
| # 构建缓存把一份商业条款引进发布链路。v5 与 v4 的差别只是 Node 24。 | |
| - uses: gradle/actions/setup-gradle@v5 | |
| - name: Derive version from tag | |
| id: version | |
| run: echo "name=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT" | |
| # 密钥只在这一步落到磁盘,用完随 runner 一起销毁。 | |
| - name: Restore keystore | |
| env: | |
| KEYSTORE_BASE64: ${{ secrets.BILBY_KEYSTORE_BASE64 }} | |
| run: echo "$KEYSTORE_BASE64" | base64 -d > "$RUNNER_TEMP/bilby.jks" | |
| # 单测只有 debug 变体有:AGP 按 testBuildType(默认 debug)生成任务, | |
| # 写成 testReleaseUnitTest 会在 CI 上报 "Task not found"。 | |
| - name: Test | |
| run: ./gradlew testDebugUnitTest -PbilbyVersion=${{ steps.version.outputs.name }} | |
| - name: Build | |
| env: | |
| BILBY_KEYSTORE: ${{ runner.temp }}/bilby.jks | |
| BILBY_KEYSTORE_PASSWORD: ${{ secrets.BILBY_KEYSTORE_PASSWORD }} | |
| BILBY_KEY_ALIAS: ${{ secrets.BILBY_KEY_ALIAS }} | |
| BILBY_KEY_PASSWORD: ${{ secrets.BILBY_KEY_PASSWORD }} | |
| run: ./gradlew assembleRelease -PbilbyVersion=${{ steps.version.outputs.name }} | |
| # 装错架构会装不上,所以文件名必须一眼看出是哪个变体。 | |
| - name: Collect artifacts | |
| id: collect | |
| run: | | |
| mkdir -p dist | |
| version="${{ steps.version.outputs.name }}" | |
| for apk in app/build/outputs/apk/release/*.apk; do | |
| variant=$(basename "$apk" | sed -E 's/^app-(.*)-release\.apk$/\1/') | |
| cp "$apk" "dist/bilby-$version-$variant.apk" | |
| done | |
| # R8 的 mapping 一起发:release 崩溃栈是混淆过的,没有它谁也读不了用户贴的日志。 | |
| cp app/build/outputs/mapping/release/mapping.txt "dist/bilby-$version-mapping.txt" | |
| (cd dist && sha256sum * > SHA256SUMS.txt) | |
| ls -l dist | |
| # SLSA generator 要的是 base64 过的 `sha256sum` 输出。只列 APK: | |
| # 证明覆盖的是"用户会装进手机的那几个文件"。 | |
| echo "hashes=$(cd dist && sha256sum *.apk | base64 -w0)" >> "$GITHUB_OUTPUT" | |
| # 证明这些文件确实由这个 workflow、从这个 commit 构建。 | |
| # 校验:gh attestation verify bilby-<版本>-<变体>.apk --repo <owner>/<repo> | |
| - name: Attest build provenance | |
| uses: actions/attest-build-provenance@v4 | |
| with: | |
| subject-path: dist/*.apk | |
| - name: Publish release | |
| uses: softprops/action-gh-release@v3 | |
| with: | |
| draft: true | |
| files: dist/* | |
| body: | | |
| ## 安装 | |
| universal 为通用包;arm64-v8a、armeabi-v7a、x86_64 为对应架构的精简包,体积略小。四者为同一版本的不同打包,架构不匹配时无法安装。 | |
| ## 校验 | |
| APK 由 GitHub Actions 从本 tag 构建并签名。 | |
| - 校验和:`SHA256SUMS.txt` | |
| - 构建来源:`gh attestation verify <文件> --repo ${{ github.repository }}` | |
| - SLSA Build L3 证明:`multiple.intoto.jsonl`,用 [slsa-verifier](https://github.com/slsa-framework/slsa-verifier) 校验 | |
| ``` | |
| slsa-verifier verify-artifact <文件> --provenance-path multiple.intoto.jsonl --source-uri github.com/${{ github.repository }} | |
| ``` | |
| `mapping.txt` 用于还原崩溃堆栈。 | |
| # SLSA Build Level 3。 | |
| # | |
| # 上面那个 `attest-build-provenance` 已经能证明"这些文件由这个 workflow 从这个 commit | |
| # 构建",但它和构建跑在同一个 job 里 —— 按 SLSA 的说法,构建过程能影响到生成证明的过程, | |
| # 所以只到 L2。这个 job 用 slsa-github-generator 的可复用 workflow:它在自己的 | |
| # 上下文里跑,拿不到本仓库的构建脚本,也拿不到签名密钥,证明因此不可伪造(L3)。 | |
| # | |
| # 两份证明都留着,它们回答的不是同一个问题,验证方式也不同: | |
| # gh attestation verify <文件> --repo <owner>/<repo> # 快,GitHub 自己核 | |
| # slsa-verifier verify-artifact <文件> # --provenance-path multiple.intoto.jsonl # --source-uri github.com/<owner>/<repo> # 按 SLSA 规范核 | |
| provenance: | |
| needs: [release] | |
| permissions: | |
| # 证明要签名,签名要 OIDC 身份。 | |
| id-token: write | |
| contents: write | |
| # generator 要读本次 workflow run 的元数据来填 provenance。 | |
| actions: read | |
| # 这个 job 的日志里有一片 "Node.js 20 is deprecated" —— **来自 generator 内部** | |
| # (`detect-workflow-js`、`compute-sha256`、它自己 pin 的 checkout),不是本仓库能改的: | |
| # v2.1.0 已经是上游最新版。runner 目前会把它们强制跑在 Node 24 上,只是告警。 | |
| # 本仓库自己引的 action 都已在 Node 24 一代。 | |
| uses: slsa-framework/slsa-github-generator/.github/workflows/generator_generic_slsa3.yml@v2.1.0 | |
| with: | |
| base64-subjects: ${{ needs.release.outputs.hashes }} | |
| # **不让 generator 自己上传。** 它按 tag 查 release,而 GitHub 的按-tag 接口看不到 | |
| # 草稿;上面那个 job 发的正是草稿,于是它查不到、转而**新建并公开发布**一个同名 | |
| # release,里面只有一份 .intoto.jsonl,正文空白。v0.7.0 上真的发生过:发布页上 | |
| # 一个 tag 挂了两个 release,公开的那个还是空的。 | |
| # | |
| # 改成让它把证明留作 workflow 产物,由下面那个 job 按 release id 挂到草稿上。 | |
| upload-assets: false | |
| # 把 SLSA 证明挂到上面那个草稿 release 上。 | |
| # | |
| # 按 **release id** 找,不按 tag:`gh release view <tag>` 和 REST 的 releases/tags/<tag> | |
| # 都不返回草稿,只有列表接口看得到它。 | |
| attach-provenance: | |
| needs: [release, provenance] | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/download-artifact@v8 | |
| with: | |
| name: ${{ needs.provenance.outputs.provenance-name }} | |
| - name: Attach provenance to the draft release | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| NAME: ${{ needs.provenance.outputs.provenance-name }} | |
| run: | | |
| id=$(gh api "repos/$GITHUB_REPOSITORY/releases" --paginate \ | |
| --jq ".[] | select(.tag_name == \"$GITHUB_REF_NAME\") | .id" | head -1) | |
| if [ -z "$id" ]; then | |
| echo "找不到 $GITHUB_REF_NAME 的 release" >&2 | |
| exit 1 | |
| fi | |
| # 用 uploads 主机传二进制:gh api 走的是 api.github.com,那台不收附件。 | |
| curl -sS --fail-with-body -X POST \ | |
| -H "Authorization: Bearer $GH_TOKEN" \ | |
| -H "Content-Type: application/octet-stream" \ | |
| --data-binary "@$NAME" \ | |
| "https://uploads.github.com/repos/$GITHUB_REPOSITORY/releases/$id/assets?name=$NAME" \ | |
| > /dev/null |