From 79d27800dbb004ddbf6ec1ddca54fafbfd6fe0c8 Mon Sep 17 00:00:00 2001 From: VU3ESV Date: Thu, 4 Jun 2026 22:02:44 +0200 Subject: [PATCH] ci: retry codesign timestamp + dynamic release notes MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two release hardening changes: 1. codesign with retries — Apple's secure-timestamp service is intermittently unavailable ("The timestamp service is not available."), which failed a release at the DMG-signing step. A small cs() wrapper retries codesign a few times before giving up. 2. Release notes now reflect the actual signing state: when the signing + notary secrets are present the build is Developer-ID signed + Apple-notarized, so the notes say to just drag-and-launch (no Gatekeeper bypass); without secrets (ad-hoc) they keep the xattr quarantine-removal instructions. Composed into dist/RELEASE_NOTES.md and used via body_path. Co-Authored-By: Claude Opus 4.8 --- .github/workflows/release.yml | 50 +++++++++++++++++++++++------------ scripts/package-signed.sh | 16 ++++++++--- 2 files changed, 46 insertions(+), 20 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index bfba8c1..05d4d8b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -167,6 +167,38 @@ jobs: dist/LP-700-App-*.radioplugin.sha256 retention-days: 30 + - name: Compose release notes + if: steps.ver.outputs.skip != 'true' + env: + V: ${{ steps.ver.outputs.value }} + REPO: ${{ github.repository }} + # 'true' only when the signing + notary secrets are present — i.e. the build was + # Developer-ID signed and Apple-notarized. Otherwise it's an ad-hoc build. + NOTARIZED: ${{ secrets.MACOS_CERT_P12_BASE64 != '' && secrets.NOTARY_PASSWORD != '' }} + run: | + set -euo pipefail + { + echo "## LP-700-App ${V}" + echo + echo "macOS native client for the [LP-700 WebSocket Server](https://github.com/VU3ESV/LP-700-Server)." + echo + echo "### Install" + echo + echo "1. Download \`LP-700-App-${V}.dmg\` below." + echo "2. Open the DMG and drag **LP-700-App.app** to **/Applications**." + if [ "${NOTARIZED}" = "true" ]; then + echo "3. Launch it — signed with Developer ID and **Apple-notarized**, so no Gatekeeper bypass is needed." + else + echo "3. **One-time Gatekeeper bypass** (this build is ad-hoc-signed, not Apple-notarized):" + echo " \`\`\`sh" + echo " xattr -dr com.apple.quarantine /Applications/LP-700-App.app" + echo " \`\`\`" + echo "4. Launch normally." + fi + echo + echo "See [README](https://github.com/${REPO}#install) and [ARCHITECTURE.md](https://github.com/${REPO}/blob/main/ARCHITECTURE.md)." + } > dist/RELEASE_NOTES.md + - name: Create GitHub release if: steps.ver.outputs.skip != 'true' uses: softprops/action-gh-release@v3 @@ -177,23 +209,7 @@ jobs: draft: false prerelease: ${{ inputs.prerelease == true }} generate_release_notes: true - body: | - ## LP-700-App ${{ steps.ver.outputs.value }} - - macOS native client for the [LP-700 WebSocket Server](https://github.com/VU3ESV/LP-700-Server). - - ### Install - - 1. Download `LP-700-App-${{ steps.ver.outputs.value }}.dmg` below. - 2. Open the DMG and drag **LP-700-App.app** to **/Applications**. - 3. **One-time Gatekeeper bypass** (this build is ad-hoc-signed, not Apple-notarized): - ```sh - xattr -dr com.apple.quarantine /Applications/LP-700-App.app - ``` - 4. Launch normally. - - See [README](https://github.com/${{ github.repository }}#install) and - [ARCHITECTURE.md](https://github.com/${{ github.repository }}/blob/main/ARCHITECTURE.md). + body_path: dist/RELEASE_NOTES.md files: | dist/LP-700-App-*.dmg dist/LP-700-App-*.dmg.sha256 diff --git a/scripts/package-signed.sh b/scripts/package-signed.sh index 8d1d7d8..a6f2207 100755 --- a/scripts/package-signed.sh +++ b/scripts/package-signed.sh @@ -67,12 +67,22 @@ if [ -n "${MACOS_CERT_P12_BASE64:-}" ]; then IDENTITY="$(security find-identity -v -p codesigning "$KC" | sed -n 's/.*"\(Developer ID Application: .*\)"/\1/p' | head -1)" fi +# codesign with a few retries — Apple's secure-timestamp service is intermittently unavailable +# ("The timestamp service is not available."), which would otherwise fail the whole release. +cs() { + local n=1 + until codesign "$@"; do + [ "$n" -ge 4 ] && return 1 + echo " codesign attempt $n failed — retrying in 15s…" >&2; sleep 15; n=$((n + 1)) + done +} + # --- sign inside-out (extension first, then the app) ------------------------------------ if [ -n "$IDENTITY" ]; then echo "==> Signing with: $IDENTITY" - codesign --force -s "$IDENTITY" -o runtime --timestamp \ + cs --force -s "$IDENTITY" -o runtime --timestamp \ --entitlements "$ENTITLEMENTS" "$APP/Contents/Extensions/$APPEX_NAME" - codesign --force -s "$IDENTITY" -o runtime --timestamp "$APP" + cs --force -s "$IDENTITY" -o runtime --timestamp "$APP" else echo "==> WARNING: no MACOS_CERT_P12_BASE64 — ad-hoc signing (extension will NOT register on other Macs)" codesign --force -s - --deep "$APP" @@ -107,7 +117,7 @@ DMG="dist/LP-700-App-${VERSION}.dmg" # Gatekeeper prompt), then notarize + staple the DMG itself. if [ -n "$IDENTITY" ]; then echo "==> Codesigning the DMG" - codesign --force -s "$IDENTITY" --timestamp "$DMG" + cs --force -s "$IDENTITY" --timestamp "$DMG" fi if [ "$HAVE_NOTARY" = 1 ]; then echo "==> Notarizing the DMG + stapling (a few minutes)…"