mirror of
https://github.com/block/buzz.git
synced 2026-11-04 08:21:48 +00:00
feat(ci): sign and notarize OSS desktop release builds
Replace ad-hoc code signing with proper Apple code signing via block/apple-codesign-action. The unsigned DMG from the Tauri build is sent to Block's signing service which signs with a Developer ID certificate, notarizes with Apple, and returns a signed DMG. Updater artifacts (tar.gz + minisign signature) are now created post-signing so the archive contains the properly signed .app. Changes: - Add codesign action step (pinned to v1.1.0 SHA) - Add spctl notarization verification alongside codesign --verify - Move updater artifact creation to post-signing step - Use pnpm exec tauri signer (lockfile-pinned) instead of npx - Name archive with version so GitHub asset filename matches latest.json URL (gh release upload # syntax is label-only) - Add id-token: write permission for AWS OIDC - Update RELEASING.md with new secrets and signed/notarized docs - Remove signingIdentity and createUpdaterArtifacts from release config Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
74
.github/workflows/release.yml
vendored
74
.github/workflows/release.yml
vendored
@@ -15,9 +15,10 @@ jobs:
|
||||
release:
|
||||
name: Release
|
||||
runs-on: macos-latest
|
||||
timeout-minutes: 60
|
||||
timeout-minutes: 120
|
||||
permissions:
|
||||
contents: write
|
||||
id-token: write
|
||||
env:
|
||||
VERSION: ${{ inputs.version }}
|
||||
steps:
|
||||
@@ -59,38 +60,57 @@ jobs:
|
||||
env:
|
||||
SPROUT_UPDATER_PUBLIC_KEY: ${{ secrets.SPROUT_UPDATER_PUBLIC_KEY }}
|
||||
SPROUT_UPDATER_ENDPOINT: https://github.com/block/sprout/releases/download/sprout-desktop-latest/latest.json
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
CMAKE_POLICY_VERSION_MINIMUM: "3.5"
|
||||
|
||||
- name: Sign with Apple codesigning service
|
||||
id: codesign
|
||||
uses: block/apple-codesign-action@679535d1ab7c5a7c18e6f9afcba3464512cc3dde # v1.1.0
|
||||
with:
|
||||
osx-codesign-role: ${{ secrets.OSX_CODESIGN_ROLE }}
|
||||
codesign-s3-bucket: ${{ secrets.CODESIGN_S3_BUCKET }}
|
||||
unsigned-artifact-path: desktop/src-tauri/target/release/bundle/dmg/Sprout_${{ env.VERSION }}_aarch64.dmg
|
||||
entitlements-plist-path: desktop/src-tauri/Entitlements.plist
|
||||
|
||||
- name: Verify code signature
|
||||
run: |
|
||||
codesign --verify --deep --strict --verbose=2 \
|
||||
desktop/src-tauri/target/release/bundle/macos/Sprout.app
|
||||
# Extract signed .app from the action's output zip
|
||||
SIGNED_APP_DIR="${RUNNER_TEMP}/signed-app"
|
||||
mkdir -p "$SIGNED_APP_DIR"
|
||||
ditto -x -k "${{ steps.codesign.outputs.signed-artifact-path }}" "$SIGNED_APP_DIR"
|
||||
if [[ ! -d "$SIGNED_APP_DIR/Sprout.app" ]]; then
|
||||
echo "::error::Sprout.app not found after extracting signed artifact"
|
||||
exit 1
|
||||
fi
|
||||
codesign --verify --deep --strict --verbose=2 "$SIGNED_APP_DIR/Sprout.app"
|
||||
spctl -a -t exec -vv "$SIGNED_APP_DIR/Sprout.app"
|
||||
|
||||
- name: Locate build artifacts
|
||||
id: artifacts
|
||||
- name: Create updater artifacts
|
||||
id: updater
|
||||
run: |
|
||||
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
|
||||
SIGNED_APP_DIR="${RUNNER_TEMP}/signed-app"
|
||||
|
||||
# Find the DMG (Tauri names it Sprout_<version>_<arch>.dmg)
|
||||
DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
|
||||
if [[ -z "$DMG" ]]; then
|
||||
echo "::error::No DMG found in $BUNDLE_DIR/dmg"
|
||||
ARCHIVE_NAME="Sprout_${VERSION}_aarch64.app.tar.gz"
|
||||
|
||||
# Create updater tarball from signed .app
|
||||
ARCHIVE="${RUNNER_TEMP}/${ARCHIVE_NAME}"
|
||||
(cd "$SIGNED_APP_DIR" && tar -czf "$ARCHIVE" "Sprout.app")
|
||||
|
||||
# Sign the archive with Tauri's minisign key
|
||||
# tauri signer reads TAURI_SIGNING_PRIVATE_KEY + TAURI_SIGNING_PRIVATE_KEY_PASSWORD from env
|
||||
cd desktop && pnpm exec tauri signer sign "$ARCHIVE"
|
||||
|
||||
SIGNATURE="${ARCHIVE}.sig"
|
||||
if [[ ! -f "$SIGNATURE" ]]; then
|
||||
echo "::error::tauri signer produced no signature file"
|
||||
exit 1
|
||||
fi
|
||||
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"
|
||||
|
||||
# Find the updater .tar.gz and .sig
|
||||
ARCHIVE=$(find "$BUNDLE_DIR/macos" -name '*.tar.gz' ! -name '*.sig' -type f | head -1)
|
||||
SIG="${ARCHIVE}.sig"
|
||||
if [[ -z "$ARCHIVE" || ! -f "$SIG" ]]; then
|
||||
echo "::error::Updater archive or signature not found in $BUNDLE_DIR/macos"
|
||||
exit 1
|
||||
fi
|
||||
echo "archive=$ARCHIVE" >> "$GITHUB_OUTPUT"
|
||||
echo "archive_name=$(basename "$ARCHIVE")" >> "$GITHUB_OUTPUT"
|
||||
echo "sig=$SIG" >> "$GITHUB_OUTPUT"
|
||||
echo "archive_name=$ARCHIVE_NAME" >> "$GITHUB_OUTPUT"
|
||||
echo "sig=$SIGNATURE" >> "$GITHUB_OUTPUT"
|
||||
env:
|
||||
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
|
||||
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
|
||||
|
||||
- name: Generate latest.json
|
||||
run: |
|
||||
@@ -101,8 +121,8 @@ jobs:
|
||||
> latest.json
|
||||
cat latest.json
|
||||
env:
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
ARCHIVE_NAME: ${{ steps.artifacts.outputs.archive_name }}
|
||||
SIG_PATH: ${{ steps.updater.outputs.sig }}
|
||||
ARCHIVE_NAME: ${{ steps.updater.outputs.archive_name }}
|
||||
|
||||
- name: Create versioned GitHub release
|
||||
run: |
|
||||
@@ -114,7 +134,7 @@ jobs:
|
||||
"$DMG_PATH"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
DMG_PATH: ${{ steps.artifacts.outputs.dmg }}
|
||||
DMG_PATH: ${{ steps.codesign.outputs.signed-dmg-path }}
|
||||
|
||||
- name: Update rolling release for auto-updater
|
||||
run: |
|
||||
@@ -130,5 +150,5 @@ jobs:
|
||||
--clobber
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
ARCHIVE_PATH: ${{ steps.artifacts.outputs.archive }}
|
||||
SIG_PATH: ${{ steps.artifacts.outputs.sig }}
|
||||
ARCHIVE_PATH: ${{ steps.updater.outputs.archive }}
|
||||
SIG_PATH: ${{ steps.updater.outputs.sig }}
|
||||
|
||||
33
RELEASING.md
33
RELEASING.md
@@ -17,6 +17,8 @@ desktop app.
|
||||
| `SPROUT_UPDATER_PUBLIC_KEY` | Tauri updater public key (minisign) |
|
||||
| `TAURI_SIGNING_PRIVATE_KEY` | Tauri updater private key (used to sign the update archive) |
|
||||
| `TAURI_SIGNING_PRIVATE_KEY_PASSWORD` | Password for the private key |
|
||||
| `OSX_CODESIGN_ROLE` | AWS IAM role ARN for OIDC authentication with the signing service |
|
||||
| `CODESIGN_S3_BUCKET` | S3 bucket used for artifact transfer during signing |
|
||||
|
||||
---
|
||||
|
||||
@@ -42,8 +44,9 @@ The workflow will:
|
||||
- Patch the version into `package.json`, `tauri.conf.json`, and `Cargo.toml`
|
||||
- Build all sidecar binaries (`sprout-acp`, `sprout-mcp`,
|
||||
`git-credential-nostr`)
|
||||
- Build the Tauri desktop app with updater signing enabled
|
||||
- Create a versioned GitHub release (`v0.4.0`) with the `.dmg` installer
|
||||
- Build the Tauri desktop app
|
||||
- Sign and notarize the app via Block's Apple code signing service
|
||||
- Create a versioned GitHub release (`v0.4.0`) with the signed `.dmg` installer
|
||||
- Update the rolling `sprout-desktop-latest` release with the signed
|
||||
update archive and `latest.json` manifest for the auto-updater
|
||||
|
||||
@@ -71,14 +74,17 @@ adding a matrix build to the workflow.
|
||||
|
||||
## Code Signing (macOS)
|
||||
|
||||
OSS release builds use **ad-hoc code signing** (`signingIdentity: "-"`)
|
||||
rather than a Developer ID certificate. This means the app is not
|
||||
notarized by Apple.
|
||||
OSS release builds are **signed and notarized** via Block's Apple code
|
||||
signing service (`block/apple-codesign-action`). The app is signed with a
|
||||
Developer ID certificate and notarized by Apple, so users will not see
|
||||
Gatekeeper warnings on first launch.
|
||||
|
||||
On first launch, macOS Gatekeeper will block the app with a "damaged" or
|
||||
"unidentified developer" message. Users can bypass this by
|
||||
**right-clicking the app > Open** (or via System Settings > Privacy &
|
||||
Security). After the first launch the app will open normally.
|
||||
The following additional secrets are required for code signing:
|
||||
|
||||
| Secret | Purpose |
|
||||
|--------|---------|
|
||||
| `OSX_CODESIGN_ROLE` | AWS IAM role ARN for OIDC authentication with the signing service |
|
||||
| `CODESIGN_S3_BUCKET` | S3 bucket used for artifact transfer during signing |
|
||||
|
||||
---
|
||||
|
||||
@@ -104,10 +110,11 @@ The version string must be valid semver: `MAJOR.MINOR.PATCH` with an
|
||||
optional pre-release suffix (e.g. `1.0.0-beta.1`). Do not include a `v`
|
||||
prefix.
|
||||
|
||||
### Build fails at "Build Tauri app"
|
||||
Check that the signing secrets are configured correctly. The build
|
||||
requires `TAURI_SIGNING_PRIVATE_KEY` and
|
||||
`TAURI_SIGNING_PRIVATE_KEY_PASSWORD` to be set.
|
||||
### Build fails at "Sign with Apple codesigning service"
|
||||
Check that `OSX_CODESIGN_ROLE` and `CODESIGN_S3_BUCKET` secrets are
|
||||
configured correctly. The signing service requires valid AWS OIDC
|
||||
credentials. Also verify the repository has `id-token: write` permission
|
||||
in the workflow.
|
||||
|
||||
### Auto-updater reports "no update available"
|
||||
Verify that the `sprout-desktop-latest` release exists and contains a
|
||||
|
||||
@@ -9,15 +9,14 @@ import { resolve } from "node:path";
|
||||
//
|
||||
// For OSS release builds this script emits:
|
||||
// 1. bundle.macOS.minimumSystemVersion = "10.15" for broad compatibility.
|
||||
// 2. bundle.createUpdaterArtifacts = true so Tauri produces the .tar.gz
|
||||
// archive and .sig signature during the build.
|
||||
// 3. plugins.updater with the public key and endpoint from env vars.
|
||||
// 2. plugins.updater with the public key and endpoint from env vars.
|
||||
// Both SPROUT_UPDATER_PUBLIC_KEY and SPROUT_UPDATER_ENDPOINT are required —
|
||||
// the script fails if either is missing (OSS builds always ship with updater).
|
||||
// 4. bundle.macOS.signingIdentity = "-" for ad-hoc code signing. This
|
||||
// prevents macOS Gatekeeper from rejecting the app as "damaged".
|
||||
// Users will see the standard "unidentified developer" dialog on first
|
||||
// launch, which they can bypass via right-click > Open.
|
||||
//
|
||||
// Note: signingIdentity is intentionally omitted — the workflow uses
|
||||
// block/apple-codesign-action to sign the .app post-build. Updater
|
||||
// artifacts (tar.gz + minisign signature) are also created post-signing
|
||||
// so the archive contains the properly signed .app.
|
||||
|
||||
const outputConfigPath = resolve(
|
||||
process.cwd(),
|
||||
@@ -41,9 +40,7 @@ const releaseConfig = {
|
||||
bundle: {
|
||||
macOS: {
|
||||
minimumSystemVersion: "10.15",
|
||||
signingIdentity: "-",
|
||||
},
|
||||
createUpdaterArtifacts: true,
|
||||
},
|
||||
plugins: {
|
||||
updater: {
|
||||
|
||||
Reference in New Issue
Block a user