Skip to content

Commit 9e97ad4

Browse files
committed
feat: add macOS DMG creation with code signing support
- Created package.osx-dmg.sh script for DMG creation - Added support for code signing with Developer ID certificate - Added notarization support for Gatekeeper approval - Created entitlements.plist for proper app permissions - Updated package.yml to create both ZIP and DMG - Added comprehensive documentation for signing setup DMG features: - Automatic code signing if certificates available - Notarization for Gatekeeper approval - Falls back to unsigned DMG if no certificates - Both ZIP and DMG provided in releases Required GitHub Secrets (optional): - MACOS_CERTIFICATE: Base64 encoded .p12 certificate - MACOS_CERTIFICATE_PWD: Certificate password - APPLE_ID: Apple ID for notarization - NOTARIZE_PASSWORD: App-specific password - TEAM_ID: Apple Developer Team ID
1 parent 953ac24 commit 9e97ad4

File tree

4 files changed

+274
-3
lines changed

4 files changed

+274
-3
lines changed

.github/workflows/package.yml

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,19 +50,36 @@ jobs:
5050
with:
5151
name: sourcegit.${{ matrix.runtime }}
5252
path: build
53-
- name: Package
53+
- name: Package as ZIP (unsigned)
5454
env:
5555
VERSION: ${{ inputs.version }}
5656
RUNTIME: ${{ matrix.runtime }}
5757
run: |
5858
mkdir build/SourceGit
5959
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
6060
./build/scripts/package.osx-app.sh
61-
- name: Upload package artifact
61+
- name: Package as DMG (signed if certificates available)
62+
env:
63+
VERSION: ${{ inputs.version }}
64+
RUNTIME: ${{ matrix.runtime }}
65+
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
66+
MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }}
67+
APPLE_ID: ${{ secrets.APPLE_ID }}
68+
NOTARIZE_PASSWORD: ${{ secrets.NOTARIZE_PASSWORD }}
69+
TEAM_ID: ${{ secrets.TEAM_ID }}
70+
run: |
71+
# Re-extract for DMG (clean state)
72+
rm -rf build/SourceGit
73+
mkdir build/SourceGit
74+
tar -xf "build/sourcegit.${{ matrix.runtime }}.tar" -C build/SourceGit
75+
./build/scripts/package.osx-dmg.sh
76+
- name: Upload package artifacts
6277
uses: actions/upload-artifact@v4
6378
with:
6479
name: package.${{ matrix.runtime }}
65-
path: build/sourcegit_*.zip
80+
path: |
81+
build/sourcegit_*.zip
82+
build/sourcegit_*.dmg
6683
- name: Delete temp artifacts
6784
uses: geekyeggo/delete-artifact@v5
6885
with:

build/scripts/package.osx-dmg.sh

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
#!/bin/bash
2+
set -e
3+
4+
# DMG creation and signing script for macOS
5+
# Requires: VERSION and RUNTIME environment variables
6+
# Optional: MACOS_CERTIFICATE, MACOS_CERTIFICATE_PWD, APPLE_ID, NOTARIZE_PASSWORD, TEAM_ID
7+
8+
cd build
9+
10+
# Prepare the app bundle
11+
echo "Preparing SourceGit.app..."
12+
rm -rf SourceGit.app
13+
mkdir -p SourceGit.app/Contents/{MacOS,Resources}
14+
cp -r SourceGit/* SourceGit.app/Contents/MacOS/
15+
mv SourceGit.app/Contents/MacOS/SourceGit SourceGit.app/Contents/MacOS/SourceGit
16+
cp resources/app/App.icns SourceGit.app/Contents/Resources/App.icns
17+
sed "s/SOURCE_GIT_VERSION/$VERSION/g" resources/app/App.plist > SourceGit.app/Contents/Info.plist
18+
rm -rf SourceGit.app/Contents/MacOS/SourceGit.dsym
19+
20+
# Code signing (if certificate is available)
21+
if [ -n "$MACOS_CERTIFICATE" ] && [ -n "$MACOS_CERTIFICATE_PWD" ]; then
22+
echo "Setting up code signing..."
23+
24+
# Create temporary keychain
25+
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
26+
KEYCHAIN_PASSWORD=$(openssl rand -base64 32)
27+
28+
# Import certificate
29+
echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12
30+
31+
# Create and configure keychain
32+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
33+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
34+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
35+
36+
# Import certificate to keychain
37+
security import certificate.p12 -P "$MACOS_CERTIFICATE_PWD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
38+
security list-keychain -d user -s "$KEYCHAIN_PATH"
39+
40+
# Sign the app
41+
echo "Signing SourceGit.app..."
42+
codesign --deep --force --verify --verbose \
43+
--sign "Developer ID Application" \
44+
--options runtime \
45+
--entitlements resources/app/entitlements.plist \
46+
--timestamp \
47+
SourceGit.app
48+
49+
echo "Verifying signature..."
50+
codesign --verify --verbose SourceGit.app
51+
52+
# Clean up certificate
53+
rm certificate.p12
54+
else
55+
echo "No signing certificate found, creating unsigned DMG..."
56+
fi
57+
58+
# Create DMG
59+
echo "Creating DMG..."
60+
DMG_NAME="sourcegit_${VERSION}.${RUNTIME}.dmg"
61+
VOLUME_NAME="SourceGit ${VERSION}"
62+
63+
# Create a temporary directory for DMG contents
64+
rm -rf dmg_temp
65+
mkdir dmg_temp
66+
cp -R SourceGit.app dmg_temp/
67+
68+
# Create Applications symlink
69+
ln -s /Applications dmg_temp/Applications
70+
71+
# Create DMG using hdiutil (more reliable than create-dmg in CI)
72+
hdiutil create -volname "$VOLUME_NAME" \
73+
-srcfolder dmg_temp \
74+
-ov -format UDZO \
75+
"$DMG_NAME"
76+
77+
# Sign the DMG itself (if certificate is available)
78+
if [ -n "$MACOS_CERTIFICATE" ]; then
79+
echo "Signing DMG..."
80+
codesign --sign "Developer ID Application" \
81+
--timestamp \
82+
"$DMG_NAME"
83+
fi
84+
85+
# Notarization (if credentials are available)
86+
if [ -n "$APPLE_ID" ] && [ -n "$NOTARIZE_PASSWORD" ] && [ -n "$TEAM_ID" ]; then
87+
echo "Submitting for notarization..."
88+
89+
# Submit for notarization
90+
xcrun notarytool submit "$DMG_NAME" \
91+
--apple-id "$APPLE_ID" \
92+
--password "$NOTARIZE_PASSWORD" \
93+
--team-id "$TEAM_ID" \
94+
--wait
95+
96+
# Staple the notarization ticket
97+
echo "Stapling notarization..."
98+
xcrun stapler staple "$DMG_NAME"
99+
100+
echo "Verifying notarization..."
101+
spctl -a -t open --context context:primary-signature -v "$DMG_NAME"
102+
fi
103+
104+
# Clean up
105+
rm -rf dmg_temp SourceGit.app
106+
107+
echo "DMG created: $DMG_NAME"
108+
ls -lh "$DMG_NAME"

docs/MACOS_SIGNING.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
# macOS Code Signing and Notarization Setup
2+
3+
This guide explains how to set up code signing and notarization for macOS releases.
4+
5+
## Prerequisites
6+
7+
1. **Apple Developer Account** ($99/year)
8+
- Sign up at https://developer.apple.com
9+
- Enroll in the Apple Developer Program
10+
11+
2. **Developer ID Certificate**
12+
- In Xcode: Preferences → Accounts → Manage Certificates
13+
- Create a "Developer ID Application" certificate
14+
- Export as .p12 file with password
15+
16+
## GitHub Secrets Setup
17+
18+
Add these secrets to your GitHub repository (Settings → Secrets and variables → Actions):
19+
20+
### Required Secrets
21+
22+
1. **MACOS_CERTIFICATE**
23+
```bash
24+
# Convert your .p12 certificate to base64
25+
base64 -i DeveloperID_Application.p12 | pbcopy
26+
```
27+
Paste the base64 string as the secret value
28+
29+
2. **MACOS_CERTIFICATE_PWD**
30+
- The password you used when exporting the .p12 certificate
31+
32+
3. **APPLE_ID** (for notarization)
33+
- Your Apple ID email address
34+
35+
4. **NOTARIZE_PASSWORD** (for notarization)
36+
- Generate an app-specific password:
37+
- Go to https://appleid.apple.com/account/manage
38+
- Sign in → Security → App-Specific Passwords
39+
- Generate a password for "SourceGit Notarization"
40+
41+
5. **TEAM_ID** (for notarization)
42+
- Find your Team ID in Apple Developer account
43+
- Or run: `xcrun altool --list-providers -u "[email protected]" -p "app-specific-password"`
44+
45+
## Testing Locally
46+
47+
Test the signing process locally:
48+
49+
```bash
50+
# Set environment variables
51+
export VERSION="2025.34.10"
52+
export RUNTIME="osx-arm64"
53+
export MACOS_CERTIFICATE="base64_encoded_cert"
54+
export MACOS_CERTIFICATE_PWD="your_password"
55+
export APPLE_ID="[email protected]"
56+
export NOTARIZE_PASSWORD="app-specific-password"
57+
export TEAM_ID="YOURTEAMID"
58+
59+
# Run the DMG script
60+
cd build
61+
./scripts/package.osx-dmg.sh
62+
```
63+
64+
## Verification
65+
66+
After downloading the DMG:
67+
68+
```bash
69+
# Check signature
70+
codesign -dv --verbose=4 /path/to/SourceGit.app
71+
72+
# Check notarization
73+
spctl -a -t open --context context:primary-signature -v /path/to/sourcegit.dmg
74+
75+
# Verify Gatekeeper acceptance
76+
spctl -a -vvv /path/to/SourceGit.app
77+
```
78+
79+
## Workflow Behavior
80+
81+
The workflow creates both signed DMG and unsigned ZIP:
82+
83+
- **With secrets**: Creates signed and notarized DMG + unsigned ZIP
84+
- **Without secrets**: Creates unsigned DMG + unsigned ZIP
85+
86+
Both are uploaded as release assets, giving users options.
87+
88+
## Troubleshooting
89+
90+
### "Developer ID Application" not found
91+
- Ensure certificate is properly imported
92+
- Check keychain access permissions
93+
94+
### Notarization fails
95+
- Verify app-specific password is correct
96+
- Check Team ID matches your developer account
97+
- Ensure all entitlements are correct
98+
99+
### DMG won't open
100+
- Check if Gatekeeper is enabled: `spctl --status`
101+
- Try right-click → Open for first launch
102+
103+
## Cost Considerations
104+
105+
- Apple Developer Program: $99/year
106+
- No per-notarization costs
107+
- Unlimited app notarizations included
108+
109+
## Alternative: Ad-hoc Signing (Free)
110+
111+
For testing without Apple Developer account:
112+
```bash
113+
# Ad-hoc sign (no notarization possible)
114+
codesign --deep --force -s - SourceGit.app
115+
```
116+
117+
Note: Ad-hoc signed apps will still show warnings but can be opened with right-click → Open.

resources/app/entitlements.plist

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
3+
<plist version="1.0">
4+
<dict>
5+
<!-- Required for notarization -->
6+
<key>com.apple.security.cs.allow-jit</key>
7+
<true/>
8+
9+
<!-- Allow unsigned executable memory (required for .NET) -->
10+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
11+
<true/>
12+
13+
<!-- Allow DYLD environment variables -->
14+
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
15+
<true/>
16+
17+
<!-- File access permissions -->
18+
<key>com.apple.security.files.user-selected.read-write</key>
19+
<true/>
20+
21+
<!-- Network client (for Git operations) -->
22+
<key>com.apple.security.network.client</key>
23+
<true/>
24+
25+
<!-- Terminal/Shell access (for Git commands) -->
26+
<key>com.apple.security.inherit</key>
27+
<true/>
28+
</dict>
29+
</plist>

0 commit comments

Comments
 (0)