Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
56 changes: 54 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ jobs:
timeout-minutes: 60
permissions:
contents: write
id-token: write # required by block/apple-codesign-action for OIDC
env:
VERSION: ${{ inputs.version }}
steps:
Expand Down Expand Up @@ -54,19 +55,70 @@ jobs:
cargo build --release -p sprout-acp -p sprout-mcp -p git-credential-nostr
./scripts/bundle-sidecars.sh

- name: Build Tauri app
run: cd desktop && pnpm tauri build --verbose --config src-tauri/tauri.release.conf.json
- name: Build unsigned Tauri app
run: cd desktop && pnpm tauri build --verbose --no-sign --config src-tauri/tauri.release.conf.json
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: Locate unsigned DMG
id: unsigned
run: |
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
DMG=$(find "$BUNDLE_DIR/dmg" -name '*.dmg' -type f | head -1)
if [[ -z "$DMG" ]]; then
echo "::error::No DMG found in $BUNDLE_DIR/dmg"
exit 1
fi
echo "dmg=$DMG" >> "$GITHUB_OUTPUT"

- name: Codesign and Notarize
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: ${{ steps.unsigned.outputs.dmg }}
entitlements-plist-path: desktop/src-tauri/Entitlements.plist
artifact-name: sprout-${{ github.sha }}-${{ github.run_id }}-arm64

- name: Replace DMG and rebuild updater archive
env:
SIGNED_DMG: ${{ steps.codesign.outputs.signed-dmg-path }}
SIGNED_APP_ZIP: ${{ steps.codesign.outputs.signed-artifact-path }}
UNSIGNED_DMG: ${{ steps.unsigned.outputs.dmg }}
TAURI_SIGNING_PRIVATE_KEY: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY }}
TAURI_SIGNING_PRIVATE_KEY_PASSWORD: ${{ secrets.TAURI_SIGNING_PRIVATE_KEY_PASSWORD }}
run: |
set -euo pipefail
BUNDLE_DIR="desktop/src-tauri/target/release/bundle"
APP_DIR="${BUNDLE_DIR}/macos"

# Replace unsigned DMG with the signed/notarized one.
cp "$SIGNED_DMG" "$UNSIGNED_DMG"

# Swap the unsigned .app for the signed .app extracted from the action's zip.
EXTRACT_DIR="${RUNNER_TEMP}/signed-app-extract"
rm -rf "$EXTRACT_DIR" && mkdir -p "$EXTRACT_DIR"
ditto -x -k "$SIGNED_APP_ZIP" "$EXTRACT_DIR"
rm -rf "${APP_DIR}/Sprout.app"
cp -R "${EXTRACT_DIR}/Sprout.app" "${APP_DIR}/Sprout.app"

# Rebuild the updater archive from the signed .app and re-sign it with the Tauri updater key.
rm -f "${APP_DIR}/Sprout.app.tar.gz" "${APP_DIR}/Sprout.app.tar.gz.sig"
(cd "$APP_DIR" && tar -czf Sprout.app.tar.gz Sprout.app)
TARBALL_ABS="$(pwd)/${APP_DIR}/Sprout.app.tar.gz"
(cd desktop && pnpm tauri signer sign "$TARBALL_ABS")

- name: Verify code signature
run: |
codesign --verify --deep --strict --verbose=2 \
desktop/src-tauri/target/release/bundle/macos/Sprout.app
spctl --assess --type execute --verbose=4 \
desktop/src-tauri/target/release/bundle/macos/Sprout.app

- name: Locate build artifacts
id: artifacts
Expand Down
11 changes: 5 additions & 6 deletions desktop/scripts/build-release-config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,12 @@ import { resolve } from "node:path";
// 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.
// Both SPROUT_UPDATER_PUBLIC_KEY and SPROUT_UPDATER_ENDPOINT are required
// 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.
//
// Apple code signing and notarization happen post-build via
// block/apple-codesign-action in release.yml, so no signingIdentity is
// emitted here and the Tauri build is invoked with --no-sign.

const outputConfigPath = resolve(
process.cwd(),
Expand All @@ -41,7 +41,6 @@ const releaseConfig = {
bundle: {
macOS: {
minimumSystemVersion: "10.15",
signingIdentity: "-",
},
createUpdaterArtifacts: true,
},
Expand Down