From 029e57158c3dceaa6fdc8adebc303cea4e47eaff Mon Sep 17 00:00:00 2001 From: jack Date: Mon, 20 Jul 2026 11:15:02 +0800 Subject: [PATCH] fix(release): Developer-ID sign nested computerd bundle before notarization The computerd helpers ship as a nested .app resource (Resources/jcode-computerd.app) inside the Tauri app. Tauri signs the outer app and externalBin sidecars with Developer ID but does not re-sign resource bundles, so the ad-hoc signature from build_computerd_bundle.sh reached notarization and Apple rejected it: 'not signed with a valid Developer ID certificate', 'no secure timestamp', 'hardened runtime not enabled'. Add a CI step that imports the Developer ID cert into a throwaway keychain and re-signs the nested bundle (hardened runtime + secure timestamp, shared identifier) before 'pnpm tauri build'. Skipped when signing is disabled (no APPLE_CERTIFICATE), since Tauri then neither signs nor notarizes. --- .github/workflows/release.yml | 69 +++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 86df598..cb89e20 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -401,6 +401,75 @@ jobs: echo "notarization: DISABLED (no API key or Apple ID secrets)" fi + # The computerd bundle is a nested .app *resource* inside the Tauri app + # (Resources/jcode-computerd.app). Tauri signs the outer app and its + # externalBin sidecars with Developer ID, but does NOT re-sign resource + # bundles — so the ad-hoc signature applied by build_computerd_bundle.sh + # would survive into the notarization upload and Apple rejects it ("not + # signed with a valid Developer ID certificate" / "no secure timestamp" / + # "hardened runtime not enabled"). Re-sign the nested bundle here, before + # `tauri build`, so the outer signature references a properly signed inner + # bundle. Skipped when signing is disabled (no APPLE_CERTIFICATE): Tauri + # then neither signs nor notarizes, and the ad-hoc bundle is fine for an + # unsigned local build. + - name: Sign computerd bundle (Developer ID) + if: runner.os == 'macOS' + shell: bash + run: | + set -euo pipefail + if [ -z "${APPLE_CERTIFICATE:-}" ]; then + echo "code-signing disabled — leaving ad-hoc signature (no notarization)" + exit 0 + fi + BUNDLE="desktop/src-tauri/bundles/jcode-computerd.app" + + # Import the Developer ID certificate into a throwaway keychain so + # codesign can use it. Tauri builds its own keychain later for the + # outer app; the two coexist. + KEYCHAIN="$RUNNER_TEMP/computerd-signing.keychain-db" + KEYCHAIN_PW="$(openssl rand -hex 16)" + security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN" + security set-keychain-settings -lut 21600 "$KEYCHAIN" + security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN" + CERT="$RUNNER_TEMP/computerd-cert.p12" + echo -n "$APPLE_CERTIFICATE" | base64 --decode > "$CERT" + security import "$CERT" -P "$APPLE_CERTIFICATE_PASSWORD" \ + -A -t cert -f pkcs12 -k "$KEYCHAIN" + security set-key-partition-list -S apple-tool:,apple: \ + -k "$KEYCHAIN_PW" "$KEYCHAIN" >/dev/null + # Prepend our keychain so codesign resolves the identity. The + # unquoted substitution is intentional: it expands to the list of + # existing keychain paths, each a separate argument. + # shellcheck disable=SC2046 + security list-keychains -d user -s "$KEYCHAIN" \ + $(security list-keychains -d user | tr -d '"') + + # Resolve the signing identity (fall back to the first valid + # codesigning identity in the keychain when not explicitly set). + IDENTITY="${APPLE_SIGNING_IDENTITY:-}" + if [ -z "$IDENTITY" ]; then + IDENTITY="$(security find-identity -v -p codesigning "$KEYCHAIN" | awk -F'"' 'NR==1{print $2}')" + fi + [ -n "$IDENTITY" ] || { echo "no Developer ID signing identity found" >&2; exit 1; } + + # Sign from the inside out: workers first, then the daemon, then the + # bundle wrapper. Hardened runtime (--options runtime) + a secure + # timestamp are both mandatory for notarization. A shared identifier + # keeps the three binaries under one TCC identity (see the bundle + # script header). The daemon uses only AX + ScreenCaptureKit, which + # work under hardened runtime without extra entitlements. + for bin in jcode-computerd-capture jcode-computerd-onboarding jcode-computerd; do + [ -x "$BUNDLE/Contents/MacOS/$bin" ] || continue + codesign --force --options runtime --timestamp \ + --sign "$IDENTITY" --identifier com.cnjack.jcode.computerd \ + "$BUNDLE/Contents/MacOS/$bin" + done + codesign --force --options runtime --timestamp \ + --sign "$IDENTITY" "$BUNDLE" + + echo "Signed nested bundle with: $IDENTITY" + codesign --verify --deep --strict --verbose=2 "$BUNDLE" + - name: Build desktop bundle shell: bash working-directory: desktop