Skip to content

Commit ac365fd

Browse files
authored
Sign and Notarize the Mac installer. (#1300)
This requires the installer to be an app instead of a script, so the script is compiled into an app now. To make things easier for users, this is now distributed as a zip archive instead of a dmg so it doesn't need to be ejected.
1 parent 45796db commit ac365fd

File tree

5 files changed

+252
-21
lines changed

5 files changed

+252
-21
lines changed

.github/workflows/build.yml

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,168 @@ jobs:
8181
certificate-profile-name: ${{ vars.AZURE_SIGNING_CERT_NAME }}
8282
files-folder: ${{ github.workspace }}\installer
8383
files-folder-filter: exe
84+
- name: sign Mac
85+
if: ${{ github.event_name == 'push' && matrix.os == 'macos-latest' }}
86+
env:
87+
APPLE_ID: ${{ secrets.APPLE_ID }}
88+
APPLE_ID_PASSWORD: ${{ secrets.APPLE_ID_PASSWORD }}
89+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
90+
APPLE_CERTIFICATE_P12: ${{ secrets.APPLE_CERTIFICATE_P12 }}
91+
APPLE_CERTIFICATE_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }}
92+
run: |
93+
set -euxo pipefail
94+
95+
# Create and configure keychain
96+
security create-keychain -p temp_password build.keychain
97+
security default-keychain -s build.keychain
98+
security unlock-keychain -p temp_password build.keychain
99+
security list-keychains -d user -s build.keychain
100+
101+
# Decode and save the certificate from the environment variable
102+
echo "$APPLE_CERTIFICATE_P12" | base64 --decode > certificate.p12
103+
104+
# Import certificate
105+
security import certificate.p12 -k build.keychain -P "$APPLE_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
106+
107+
# Set key partition list for codesign access with specific certificate targeting
108+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k temp_password -D "Developer ID Application" -t private build.keychain
109+
110+
# Extract identity from build.keychain only
111+
IDENTITY=$(security find-identity -v -p codesigning build.keychain | grep "Developer ID Application" | head -1 | sed 's/.*) \([^"]*\).*/\1/' | xargs)
112+
echo "Signing with identity: $IDENTITY"
113+
if [[ -z "$IDENTITY" ]]; then
114+
echo "No codesigning identity found in build.keychain!"
115+
exit 1
116+
fi
117+
118+
cd installer/mac
119+
120+
# Set up app bundle and entitlements
121+
APP_BUNDLE="OSARAInstaller.app"
122+
123+
# Create a temporary entitlements file for runtime (consistent with local build.sh)
124+
runtime_entitlements="$APP_BUNDLE/Contents/Resources/runtime.entitlements"
125+
cp OSARAInstaller.entitlements "$runtime_entitlements"
126+
127+
# Deep sign all components in the app bundle with entitlements
128+
echo "=== Deep signing app bundle with entitlements ==="
129+
find $APP_BUNDLE -type f \( -name "*.dylib" -o -name "*.so" -o -name "*.framework" \) -exec codesign --force --options runtime --entitlements OSARAInstaller.entitlements --sign "$IDENTITY" {} \; || echo "No additional libraries found to sign"
130+
131+
# Sign the main executable with timestamp and entitlements
132+
codesign --force --options runtime --timestamp --entitlements OSARAInstaller.entitlements --sign "$IDENTITY" "$APP_BUNDLE/Contents/MacOS/applet"
133+
134+
# Sign the app bundle with timestamp and entitlements
135+
codesign --force --options runtime --timestamp --entitlements OSARAInstaller.entitlements --sign "$IDENTITY" $APP_BUNDLE
136+
137+
# Verify signing with detailed output
138+
echo "=== Verifying code signature ==="
139+
codesign --verify --verbose=4 $APP_BUNDLE
140+
codesign --display --verbose=4 $APP_BUNDLE
141+
142+
# Pre-flight validation using spctl
143+
echo "=== Pre-flight validation with spctl ==="
144+
spctl --assess --verbose=4 --type execute $APP_BUNDLE || echo "spctl assessment failed - this is expected before notarization"
145+
146+
# Validate bundle structure
147+
echo "=== Validating bundle structure ==="
148+
if [[ ! -f "$APP_BUNDLE/Contents/Info.plist" ]]; then
149+
echo "ERROR: Missing Info.plist"
150+
exit 1
151+
fi
152+
if [[ ! -f "$APP_BUNDLE/Contents/MacOS/applet" ]]; then
153+
echo "ERROR: Missing main executable"
154+
exit 1
155+
fi
156+
echo "Bundle structure validation passed"
157+
158+
# Create zip for notarization (just the app bundle)
159+
zip -r "osara_temp.zip" $APP_BUNDLE
160+
161+
# Submit for notarization
162+
echo "=== Submit for notarization ==="
163+
164+
# Submit without verbose to get clean JSON output
165+
if xcrun notarytool submit osara_temp.zip \
166+
--apple-id "$APPLE_ID" \
167+
--password "$APPLE_ID_PASSWORD" \
168+
--team-id "$APPLE_TEAM_ID" \
169+
--wait \
170+
--output-format json > notarization_output.json 2>&1; then
171+
172+
echo "=== Notarization submission completed ==="
173+
cat notarization_output.json
174+
175+
# Check if notarization was successful
176+
if command -v jq >/dev/null 2>&1; then
177+
STATUS=$(jq -r '.status // "Unknown"' notarization_output.json)
178+
echo "Notarization status: $STATUS"
179+
180+
if [[ "$STATUS" == "Accepted" ]]; then
181+
echo "✅ Notarization successful!"
182+
else
183+
echo "❌ Notarization failed with status: $STATUS"
184+
185+
# Try to get detailed log
186+
SUBMISSION_ID=$(jq -r '.id // empty' notarization_output.json)
187+
if [[ -n "$SUBMISSION_ID" ]]; then
188+
echo "=== Fetching detailed notarization log for submission $SUBMISSION_ID ==="
189+
if xcrun notarytool log "$SUBMISSION_ID" \
190+
--apple-id "$APPLE_ID" \
191+
--password "$APPLE_ID_PASSWORD" \
192+
--team-id "$APPLE_TEAM_ID" > notarization_log.txt 2>&1; then
193+
echo "=== Notarization Log ==="
194+
cat notarization_log.txt
195+
else
196+
echo "Failed to fetch notarization log"
197+
fi
198+
fi
199+
exit 1
200+
fi
201+
else
202+
echo "jq not available - cannot parse notarization response"
203+
exit 1
204+
fi
205+
else
206+
echo "❌ Notarization submission failed"
207+
cat notarization_output.json || echo "No output file generated"
208+
exit 1
209+
fi
210+
211+
# Staple notarization to app
212+
echo "=== Stapling notarization ticket ==="
213+
if xcrun stapler staple $APP_BUNDLE; then
214+
echo "✅ Stapling successful"
215+
else
216+
echo "❌ Stapling failed - but continuing as this might be a timing issue"
217+
# Don't exit here as stapling can sometimes fail due to timing
218+
fi
219+
220+
# Verify stapling
221+
echo "=== Verifying stapled notarization ==="
222+
if xcrun stapler validate $APP_BUNDLE; then
223+
echo "✅ Stapling validation successful"
224+
else
225+
echo "⚠️ Stapling validation failed"
226+
fi
227+
228+
# Final validation with spctl
229+
echo "=== Final validation with spctl ==="
230+
if spctl --assess --verbose=4 --type execute $APP_BUNDLE; then
231+
echo "✅ Final spctl validation passed"
232+
else
233+
echo "⚠️ Final spctl validation failed"
234+
fi
235+
236+
# Create final signed and notarized zip with app bundle and license
237+
rm -f ../osara_${{ env.version }}.zip
238+
zip -r ../osara_${{ env.version }}.zip $APP_BUNDLE
239+
cd ../..
240+
241+
# Cleanup
242+
rm installer/mac/osara_temp.zip certificate.p12
243+
security delete-keychain build.keychain
244+
245+
echo "Mac app signed and notarized successfully!"
84246
# We only need to upload the pot on one OS. We arbitrarily pick Windows.
85247
- name: pot
86248
if: ${{ github.event_name == 'push' && matrix.os == 'windows-latest' }}

installer/mac/content/Install OSARA.command renamed to installer/mac/Install OSARA.js

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,22 @@
1-
#!/bin/bash
2-
osascript -l JavaScript - "`dirname \"$0\"`/.data/" << 'EOF'
31
"use strict";
42
var app = Application.currentApplication();
53
app.includeStandardAdditions = true;
64
var finder = Application("Finder");
75

6+
function getResourcesDir() {
7+
var bundleURL = $.NSBundle.mainBundle.bundleURL;
8+
if (bundleURL) {
9+
var bundlePath = bundleURL.path.js;
10+
return bundlePath + "/Contents/Resources";
11+
}
12+
13+
var processPath = $.NSProcessInfo.processInfo.processName.js;
14+
var argCount = $.NSProcessInfo.processInfo.arguments.count;
15+
throw new Error("Could not determine Resources directory. Process: " + processPath +
16+
", Args count: " + argCount +
17+
", Bundle path: " + (bundleURL ? bundleURL.path.js : "null"));
18+
}
19+
820
function isPortableReaper ( dir ) {
921
return (
1022
finder.exists(Path(dir + "/REAPER.app")) ||
@@ -14,7 +26,7 @@ function isPortableReaper ( dir ) {
1426
}
1527

1628
function run(argv) {
17-
var source = argv[0];
29+
var source = getResourcesDir();
1830
var res = app.displayDialog("Choose weather to install Osara into a standard or a portable Reaper", {
1931
buttons: ["Standard Reaper Installation", "Portable Reaper Installation", "Cancel"],
2032
defaultButton: "Standard Reaper Installation",
@@ -58,5 +70,8 @@ function run(argv) {
5870
} catch(ignore) {} // there might not be a keymap to backup
5971
s(`cp '${target}/KeyMaps/OSARA.ReaperKeyMap' '${target}/reaper-kb.ini'`);
6072
}
73+
app.displayDialog("Installation Complete", {
74+
buttons: ["OK"],
75+
defaultButton: "OK"
76+
});
6177
}
62-
EOF
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
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+
<key>com.apple.security.app-sandbox</key>
6+
<false/>
7+
<key>com.apple.security.files.user-selected.read-write</key>
8+
<true/>
9+
<key>com.apple.security.files.bookmarks.app-scope</key>
10+
<true/>
11+
<key>com.apple.security.automation.apple-events</key>
12+
<true/>
13+
<key>com.apple.security.cs.allow-jit</key>
14+
<true/>
15+
<key>com.apple.security.cs.allow-unsigned-executable-memory</key>
16+
<true/>
17+
<key>com.apple.security.cs.disable-library-validation</key>
18+
<true/>
19+
</dict>
20+
</plist>

installer/mac/build.sh

Lines changed: 50 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,54 @@
11
#!/bin/bash
22
set -e
33
version=$1
4-
dmg=../osara_$version.dmg
4+
zip_file=../osara_$version.zip
5+
app_name="OSARAInstaller.app"
56
cd "`dirname \"$0\"`"
6-
rm -rf content/.data
7-
mkdir content/.data
8-
cd content
9-
cp ../../../copying.txt .
10-
cd .data
11-
cp ../../../../build/reaper_osara.dylib .
12-
cp ../../../../config/mac/reaper-kb.ini OSARA.ReaperKeyMap
13-
mkdir locale
14-
cp ../../../../locale/*.po locale/
15-
cd ../..
16-
rm -f $dmg
17-
# We seem to need a delay here to avoid an "hdiutil: create failed - Resource busy" error.
18-
sleep 1
19-
hdiutil create -fs HFS+ -volname "OSARA $version" -srcfolder content $dmg
20-
rm -rf content/copying.txt content/.data
7+
8+
# Compile the script into the app bundle using JavaScript for Automation
9+
osacompile -l JavaScript -o "$app_name" "Install OSARA.js"
10+
11+
# Update the Info.plist with our custom bundle information
12+
cat > "$app_name/Contents/Info.plist" << EOF
13+
<?xml version="1.0" encoding="UTF-8"?>
14+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
15+
<plist version="1.0">
16+
<dict>
17+
<key>CFBundleExecutable</key>
18+
<string>applet</string>
19+
<key>CFBundleIdentifier</key>
20+
<string>co.osara.installer</string>
21+
<key>CFBundleName</key>
22+
<string>Install OSARA</string>
23+
<key>CFBundleVersion</key>
24+
<string>$version</string>
25+
<key>CFBundleShortVersionString</key>
26+
<string>$version</string>
27+
<key>CFBundlePackageType</key>
28+
<string>APPL</string>
29+
<key>CFBundleSignature</key>
30+
<string>????</string>
31+
<key>LSMinimumSystemVersion</key>
32+
<string>10.9</string>
33+
<key>NSAppleEventsUsageDescription</key>
34+
<string>This app needs to use AppleEvents to install OSARA components.</string>
35+
<key>NSAppleScriptEnabled</key>
36+
<true/>
37+
<key>OSAScriptingDefinition</key>
38+
<string>AppleScriptKit</string>
39+
</dict>
40+
</plist>
41+
EOF
42+
43+
# Copy resources into the app bundle
44+
cp ../../build/reaper_osara.dylib "$app_name/Contents/Resources/"
45+
cp ../../copying.txt "$app_name/Contents/Resources/"
46+
cp ../../config/mac/reaper-kb.ini "$app_name/Contents/Resources/OSARA.ReaperKeyMap"
47+
mkdir -p "$app_name/Contents/Resources/locale"
48+
cp ../../locale/*.po "$app_name/Contents/Resources/locale/"
49+
50+
# Create the final zip file with the app bundle and license
51+
rm -f $zip_file
52+
zip -r $zip_file "$app_name"
53+
54+
echo "Created installer package: $zip_file"

sconstruct

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ else: # Mac
5959
archEnv.SConscript("src/archBuild_sconscript",
6060
exports={"env": archEnv},
6161
variant_dir="build", duplicate=False)
62-
installer = env.Command("installer/osara_${version}.dmg", ["installer/mac/build.sh", "build"],
62+
installer = env.Command("installer/osara_${version}.zip", ["installer/mac/build.sh", "build"],
6363
[["$SOURCE", "$version"]])
6464
configRc = "build/config.rc"
6565

0 commit comments

Comments
 (0)