From c7d00d6f133b11cd088e84f2574dbabdb918fb00 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 11 Apr 2026 22:40:27 +0000
Subject: [PATCH] Fix macOS app signing, notarization, and bundling in CI
pipeline
Agent-Logs-Url: https://github.com/philliphoff/EncDotNet/sessions/1f998567-17c6-4429-8da4-c85982e84d65
Co-authored-by: philliphoff <6402946+philliphoff@users.noreply.github.com>
---
.github/workflows/ci.yml | 96 ++++++++++++++++----
src/EncDotNet.ChartViewer/Info.plist | 25 +++++
src/EncDotNet.ChartViewer/entitlements.plist | 10 ++
3 files changed, 112 insertions(+), 19 deletions(-)
create mode 100644 src/EncDotNet.ChartViewer/Info.plist
create mode 100644 src/EncDotNet.ChartViewer/entitlements.plist
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7a69160..939b308 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -10,6 +10,8 @@ on:
jobs:
build:
runs-on: ubuntu-latest
+ permissions:
+ contents: read
steps:
- uses: actions/checkout@v4
@@ -62,6 +64,8 @@ jobs:
rid: linux-x64
artifact: ChartViewer-linux-x64
runs-on: ${{ matrix.os }}
+ permissions:
+ contents: read
env:
HAS_SIGNING_SECRETS: ${{ secrets.APPLE_DEVELOPER_CERTIFICATE_P12 != '' }}
@@ -80,6 +84,18 @@ jobs:
--runtime ${{ matrix.rid }}
${{ matrix.rid == 'osx-arm64' && '-p:PublishSingleFile=false' || '' }}
+ - name: Create macOS .app bundle
+ if: matrix.rid == 'osx-arm64'
+ run: |
+ PUBLISH_DIR="src/EncDotNet.ChartViewer/bin/Release/net10.0/osx-arm64/publish"
+ APP="EncDotNet.ChartViewer.app"
+ mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
+ cp src/EncDotNet.ChartViewer/Info.plist "$APP/Contents/"
+ cp -a "$PUBLISH_DIR"/. "$APP/Contents/MacOS/"
+ # Remove debug symbols; they are not needed in the release bundle
+ # and codesign treats them as unsigned code objects.
+ find "$APP" -name '*.pdb' -delete
+
- name: Import signing certificate
if: matrix.rid == 'osx-arm64' && env.HAS_SIGNING_SECRETS == 'true'
env:
@@ -93,20 +109,34 @@ jobs:
security import cert.p12 -k build.keychain -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "" build.keychain
- - name: Sign macOS binary
+ - name: Sign macOS .app bundle
if: matrix.rid == 'osx-arm64' && env.HAS_SIGNING_SECRETS == 'true'
env:
APPLE_SIGNING_IDENTITY: ${{ secrets.APPLE_SIGNING_IDENTITY }}
run: |
- PUBLISH_DIR="src/EncDotNet.ChartViewer/bin/Release/net10.0/osx-arm64/publish"
- # Sign all .dylib files first, then the main executable
- find "$PUBLISH_DIR" -name '*.dylib' -exec \
- codesign --force --options runtime --timestamp --sign "$APPLE_SIGNING_IDENTITY" {} \;
+ APP="EncDotNet.ChartViewer.app"
+ ENTITLEMENTS="src/EncDotNet.ChartViewer/entitlements.plist"
+ MAIN_EXE="EncDotNet.ChartViewer"
+ # Sign all subcomponents individually before sealing the bundle.
+ # Exclude the main executable — it will be signed when we sign
+ # the bundle itself, which also validates all subcomponents.
+ find "$APP/Contents/MacOS" -type f ! -name "$MAIN_EXE" | while read -r f; do
+ if file "$f" | grep -q "Mach-O"; then
+ codesign --force --options runtime --timestamp \
+ --entitlements "$ENTITLEMENTS" \
+ --sign "$APPLE_SIGNING_IDENTITY" "$f"
+ else
+ codesign --force --timestamp \
+ --sign "$APPLE_SIGNING_IDENTITY" "$f"
+ fi
+ done
+ # Sign the bundle (signs main executable and seals everything)
codesign --force --options runtime --timestamp \
+ --entitlements "$ENTITLEMENTS" \
--sign "$APPLE_SIGNING_IDENTITY" \
- "$PUBLISH_DIR/EncDotNet.ChartViewer"
+ "$APP"
- - name: Notarize macOS binary
+ - name: Notarize macOS .app bundle
if: matrix.rid == 'osx-arm64' && env.HAS_SIGNING_SECRETS == 'true'
env:
APPLE_ID: ${{ secrets.APPLE_ID }}
@@ -114,19 +144,55 @@ jobs:
APPLE_APP_PASSWORD: ${{ secrets.APPLE_APP_PASSWORD }}
run: |
ditto -c -k --keepParent \
- src/EncDotNet.ChartViewer/bin/Release/net10.0/osx-arm64/publish \
+ EncDotNet.ChartViewer.app \
ChartViewer.zip
xcrun notarytool submit ChartViewer.zip \
--apple-id "$APPLE_ID" \
--team-id "$APPLE_TEAM_ID" \
--password "$APPLE_APP_PASSWORD" \
- --wait
+ --wait \
+ --output-format json | tee notarization-result.json
+ STATUS=$(python3 -c "import json,sys; print(json.load(sys.stdin)['status'])" < notarization-result.json)
+ if [ "$STATUS" != "Accepted" ]; then
+ ID=$(python3 -c "import json,sys; print(json.load(sys.stdin)['id'])" < notarization-result.json)
+ echo "::error::Notarization failed with status: $STATUS"
+ xcrun notarytool log "$ID" \
+ --apple-id "$APPLE_ID" \
+ --team-id "$APPLE_TEAM_ID" \
+ --password "$APPLE_APP_PASSWORD"
+ exit 1
+ fi
+
+ - name: Staple notarization ticket
+ if: matrix.rid == 'osx-arm64' && env.HAS_SIGNING_SECRETS == 'true'
+ continue-on-error: true
+ run: |
+ # The notarization ticket may not be immediately available in
+ # CloudKit. Stapling is optional — Gatekeeper will verify the
+ # notarization online on first launch if the ticket is absent.
+ for i in 1 2 3 4 5; do
+ if xcrun stapler staple EncDotNet.ChartViewer.app; then
+ exit 0
+ fi
+ echo "Staple attempt $i failed, waiting 30s..."
+ sleep 30
+ done
+ echo "::warning::Stapling failed after 5 attempts. The app is still notarized; Gatekeeper will verify online."
+
+ - name: Archive macOS .app bundle
+ if: matrix.rid == 'osx-arm64'
+ run: tar -czf "${{ matrix.artifact }}.tar.gz" EncDotNet.ChartViewer.app
+
+ - name: Archive published app
+ if: matrix.rid != 'osx-arm64'
+ shell: bash
+ run: tar -czf "${{ matrix.artifact }}.tar.gz" -C "src/EncDotNet.ChartViewer/bin/Release/net10.0/${{ matrix.rid }}/publish" .
- name: Upload published app
uses: actions/upload-artifact@v4
with:
name: ${{ matrix.artifact }}
- path: src/EncDotNet.ChartViewer/bin/Release/net10.0/${{ matrix.rid }}/publish/
+ path: ${{ matrix.artifact }}.tar.gz
publish-nuget:
if: startsWith(github.ref, 'refs/tags/v')
@@ -169,18 +235,10 @@ jobs:
with:
path: artifacts
- - name: Archive platform binaries
- run: |
- cd artifacts
- for dir in ChartViewer-*/; do
- name="${dir%/}"
- tar -czf "${name}.tar.gz" -C "$dir" .
- done
-
- name: Create GitHub Release
uses: softprops/action-gh-release@v2
with:
generate_release_notes: true
files: |
artifacts/nupkgs/*.nupkg
- artifacts/ChartViewer-*.tar.gz
+ artifacts/ChartViewer-*/*.tar.gz
diff --git a/src/EncDotNet.ChartViewer/Info.plist b/src/EncDotNet.ChartViewer/Info.plist
new file mode 100644
index 0000000..fe3f937
--- /dev/null
+++ b/src/EncDotNet.ChartViewer/Info.plist
@@ -0,0 +1,25 @@
+
+
+
+
+ CFBundleName
+ EncDotNet.ChartViewer
+ CFBundleDisplayName
+ Chart Viewer
+ CFBundleIdentifier
+ com.philliphoff.encdotnet.chartviewer
+ CFBundleVersion
+ 0.3.5
+ CFBundleShortVersionString
+ 0.3.5
+ CFBundleExecutable
+ EncDotNet.ChartViewer
+ CFBundlePackageType
+ APPL
+ LSMinimumSystemVersion
+ 12.0
+ NSHighResolutionCapable
+
+
+
diff --git a/src/EncDotNet.ChartViewer/entitlements.plist b/src/EncDotNet.ChartViewer/entitlements.plist
new file mode 100644
index 0000000..f00fbb5
--- /dev/null
+++ b/src/EncDotNet.ChartViewer/entitlements.plist
@@ -0,0 +1,10 @@
+
+
+
+
+ com.apple.security.cs.allow-jit
+
+ com.apple.security.cs.allow-unsigned-executable-memory
+
+
+