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
27 changes: 25 additions & 2 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ jobs:
mode: ${{ steps.mode.outputs.mode }}
appcast: ${{ steps.mode.outputs.appcast }}
dmg_file: ${{ steps.version.outputs.dmg_file }}
notarization_id: ${{ steps.signed_release.outputs.notarization_id }}
env:
NOTARY_PROFILE: ci-notary
steps:
Expand Down Expand Up @@ -201,12 +202,26 @@ jobs:
--issuer "$NOTARY_ISSUER_ID"
rm -f "$key_file"

- name: Build, sign, and notarize release
- name: Build, sign, and submit DMG for notarization
id: signed_release
if: steps.mode.outputs.mode == 'signed'
working-directory: macos
env:
SIGNING_TEAM_ID: ${{ secrets.SIGNING_TEAM_ID }}
run: scripts/release.sh
run: |
set -eu
scripts/release.sh
submission_id=$(plutil -extract id raw -o - .build-artifacts/notarization-logs/dmg-submit.json)
echo "notarization_id=$submission_id" >> "$GITHUB_OUTPUT"
{
echo "## Apple notarization submitted"
echo ""
echo "- Submission ID: \`$submission_id\`"
echo "- DMG: \`$DMG_NAME.dmg\`"
echo "- Status check: \`xcrun notarytool info $submission_id --keychain-profile <your-profile>\`"
echo ""
echo "The workflow does not wait for Apple. Verify that this submission is Accepted before publishing the draft release."
} >> "$GITHUB_STEP_SUMMARY"

- name: Build unsigned release
if: steps.mode.outputs.mode == 'unsigned'
Expand Down Expand Up @@ -252,6 +267,14 @@ jobs:
sed -i '' 's/^ //' "$dist/appcast.xml"
xmllint --noout "$dist/appcast.xml"

- name: Upload notarization submission receipt
if: steps.mode.outputs.mode == 'signed'
uses: actions/upload-artifact@v4
with:
name: notarization-submission
path: macos/.build-artifacts/notarization-logs/dmg-submit.json
if-no-files-found: error

- name: Stage release artifacts
env:
MODE: ${{ steps.mode.outputs.mode }}
Expand Down
6 changes: 3 additions & 3 deletions macos/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ The distributable is universal (`arm64` + `x86_64`). Build an unsigned app and D
scripts/package-unsigned.sh
```

Unsigned artifacts are written to `dist/unsigned/`; signed/notarized release artifacts are isolated under `dist/signed/`. `package-unsigned.sh` first runs `scripts/test-packaging.sh` and the mandatory Swift tests. It assembles the app and compressed DMG in staging paths, verifies them, and only then atomically publishes each final pathname. Stale app/DMG stages, manifests, checksum temporaries, and unmounted image mounts are cleaned before packaging.
Unsigned artifacts are written to `dist/unsigned/`; Developer ID-signed release artifacts are isolated under `dist/signed/`. `package-unsigned.sh` first runs `scripts/test-packaging.sh` and the mandatory Swift tests. It assembles the app and compressed DMG in staging paths, verifies them, and only then atomically publishes each final pathname. Stale app/DMG stages, manifests, checksum temporaries, and unmounted image mounts are cleaned before packaging.

The unsigned path ad-hoc signs the Mach-O executable and then the bundle, verifies that both signatures are specifically ad-hoc, and verifies the bundle seal. Ad-hoc signing supplies integrity metadata only: it carries no publisher identity, is not notarized, and does not make a downloaded app trustworthy. The signed path still replaces those signatures with the configured Developer ID identity before notarization.

Expand Down Expand Up @@ -108,9 +108,9 @@ export NOTARY_PROFILE='forge-menubar-notary'
scripts/release.sh
```

The release pipeline validates the universal application slices, minimum OS, dependencies, and exact runtime-free payload inventory; replaces the development seal by signing the application executable and bundle with hardened runtime; notarizes, staples, and assesses the app and DMG; compares the complete mounted app bundle; validates the `/Applications` symlink; and atomically writes and verifies the exact manifest/checksum inventory plus retained notarization logs. Real signing credentials are not needed for development: `scripts/package-unsigned.sh` runs the ad-hoc validation path.
The release pipeline validates the universal application slices, minimum OS, dependencies, and exact runtime-free payload inventory; replaces the development seal by signing the application executable and bundle with hardened runtime; creates and signs the DMG; compares the complete mounted app bundle; validates the `/Applications` symlink; and atomically writes and verifies the exact manifest/checksum inventory. It submits only the signed DMG to Apple and returns as soon as `notarytool` supplies a submission ID. It does not submit the app separately, wait for acceptance, or staple the artifact. CI records the submission ID and uploads `dmg-submit.json`; confirm that Apple reports `Accepted` before publishing the draft release. Real signing credentials are not needed for development: `scripts/package-unsigned.sh` runs the ad-hoc validation path.

Useful individual commands are `scripts/assemble-app.sh`, `scripts/sign-app.sh`, `scripts/notarize-app.sh`, `scripts/create-dmg.sh`, `scripts/sign-dmg.sh`, `scripts/notarize-dmg.sh`, and `scripts/verify-release.sh`. `APP_VERSION`, `BUILD_NUMBER`, and `MINIMUM_MACOS_VERSION` accept numeric dot-separated plist versions only.
Useful individual commands are `scripts/assemble-app.sh`, `scripts/sign-app.sh`, `scripts/create-dmg.sh`, `scripts/sign-dmg.sh`, `scripts/notarize-dmg.sh`, and `scripts/verify-release.sh`. `APP_VERSION`, `BUILD_NUMBER`, and `MINIMUM_MACOS_VERSION` accept numeric dot-separated plist versions only.

## Current limitations and security

Expand Down
Loading
Loading