ci: scope the updater signing key to signing steps and split the legacy publish job

The signing key moves from job-level to step-level env, so npm install scripts, setup actions and crate build scripts never see it. Linux compiles without the key and signs in a separate tauri bundle step. The legacy rollback builds and signs under a read-only, unpersisted token with the locked Tauri CLI, and a separate job holds the write token to publish.
This commit is contained in:
tiennm99 committed 2026-10-21 17:29:56 +07:00
1 parent 81f29f2e4f
commit 9a9b01ff07
4 files changed
+94 -28

No files matched your search

+63 -13
View File
@@ -6,6 +6,10 @@ name: Release legacy
# the signed `latest.json`, and Tauri Linux clients find no entry for their
# platform, which they report as up to date. The Win32 app accepts the
# `--wait-pid <pid> --updated-to X` relaunch the Tauri updater uses.
#
# The build job holds a read-only token that is never written to disk, and
# only the steps that check or use the updater signing key see it. The write
# token lives in the publish job, which builds nothing.
on:
workflow_dispatch:
@@ -31,11 +35,9 @@ jobs:
win-x64:
name: Windows x64 (legacy)
runs-on: windows-latest
permissions:
contents: write
outputs:
sha: ${{ steps.sha.outputs.sha }}
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
VERSION: ${{ inputs.version }}
TAG: ${{ inputs.tag }}
defaults:
@@ -43,6 +45,8 @@ jobs:
shell: bash
steps:
- name: Check the inputs and the signing key
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if ! [[ "$VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
echo "::error::version '$VERSION' is not X.Y.Z."
@@ -60,13 +64,19 @@ jobs:
- uses: actions/checkout@v7
with:
ref: ${{ inputs.ref }}
persist-credentials: false
# The legacy ref predates the release script, so take it from the
# default branch.
# The legacy ref predates package.json and the release script, so take
# both from the default branch.
- uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
path: current
persist-credentials: false
- name: Record the commit the release points at
id: sha
run: echo "sha=$(git rev-parse HEAD)" >> "$GITHUB_OUTPUT"
- uses: dtolnay/rust-toolchain@stable
@@ -75,6 +85,12 @@ jobs:
- uses: actions/setup-node@v7
with:
node-version: lts/*
cache: npm
cache-dependency-path: current/package-lock.json
# The Tauri CLI that signs the exe comes from the reviewed lockfile,
# not from a version resolved at run time.
- run: npm ci --prefix current
# `cargo update --workspace` rewrites only the package's own version
# in Cargo.lock, so the locked build below keeps every dependency.
@@ -90,21 +106,55 @@ jobs:
- run: cargo build --release --locked
# The legacy ref has no package.json, so the Tauri CLI comes from npx.
# `--no` refuses to download a CLI that the lockfile did not install.
- name: Sign the exe
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
exe=target/release/claude-code-usage-bubble.exe
npx --yes @tauri-apps/cli@2 signer sign --app-version "$VERSION" "$exe"
npm exec --prefix current --no -- tauri signer sign --app-version "$VERSION" "$exe"
test -s "$exe.sig"
mkdir -p dist
cp "$exe" "$exe.sig" dist/
- uses: actions/upload-artifact@v7
with:
name: release-legacy-win-x64
path: |
target/release/claude-code-usage-bubble.exe
target/release/claude-code-usage-bubble.exe.sig
retention-days: 7
if-no-files-found: error
publish:
name: Publish
needs: win-x64
runs-on: ubuntu-latest
permissions:
contents: write
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
TAG: ${{ inputs.tag }}
TARGET: ${{ needs.win-x64.outputs.sha }}
steps:
- uses: actions/checkout@v7
with:
ref: ${{ github.event.repository.default_branch }}
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: lts/*
- uses: actions/download-artifact@v8
with:
name: release-legacy-win-x64
path: dist
- name: Write the Windows-only latest.json and check the asset set
run: node current/scripts/make-latest-json.js --windows-only "$TAG" dist
run: node scripts/make-latest-json.js --windows-only "$TAG" dist
- name: Create the release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
DRAFT: ${{ inputs.draft }}
run: |
if [ "$DRAFT" = true ]; then
@@ -112,5 +162,5 @@ jobs:
else
mode=(--latest)
fi
gh release create "$TAG" dist/* --target "$(git rev-parse HEAD)" \
gh release create "$TAG" dist/* --repo "$GITHUB_REPOSITORY" --target "$TARGET" \
--title "$TAG" --generate-notes "${mode[@]}"
+19 -9
View File
@@ -76,12 +76,14 @@ jobs:
name: Windows x64
needs: prepare
runs-on: windows-latest
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# The updater signing key decides what every installed client will
# install, so only the steps that check or use it see it: never
# `npm ci` install scripts, the setup actions or crate build scripts.
steps:
- name: Refuse a missing signing key
shell: bash
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY//[[:space:]]/}" ]; then
echo "::error::The TAURI_SIGNING_PRIVATE_KEY secret is empty; an unsigned build cannot be released."
@@ -113,6 +115,8 @@ jobs:
shell: bash
env:
VERSION: ${{ needs.prepare.outputs.version }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
exe=target/release/claude-code-usage-bubble.exe
npx tauri signer sign --app-version "$VERSION" "$exe"
@@ -131,11 +135,11 @@ jobs:
name: Linux x64
needs: prepare
runs-on: ubuntu-22.04
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
# As on Windows, only the steps that check or use the signing key see it.
steps:
- name: Refuse a missing signing key
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
run: |
if [ -z "${TAURI_SIGNING_PRIVATE_KEY//[[:space:]]/}" ]; then
echo "::error::The TAURI_SIGNING_PRIVATE_KEY secret is empty; an unsigned build cannot be released."
@@ -163,12 +167,18 @@ jobs:
- run: npm ci
- name: Build the AppImage and deb with updater signatures
# Compile without the key, then bundle and sign with it: `tauri bundle`
# packages the binary already built and runs no cargo build scripts.
- run: npm run tauri build -- --no-bundle -- --locked
- name: Bundle the AppImage and deb with updater signatures
env:
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: >-
npm run tauri build --
npm run tauri bundle --
--bundles appimage,deb
--config '{"bundle":{"createUpdaterArtifacts":true}}'
-- --locked
# The bundler names outputs after productName, which has spaces, and
# GitHub rewrites spaces in asset names, so the URLs in latest.json