Skip to content

Commit ec11381

Browse files
committed
ci: sign and notarize release builds
1 parent 33d0cb6 commit ec11381

2 files changed

Lines changed: 128 additions & 20 deletions

File tree

.github/workflows/build.yml

Lines changed: 113 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,26 @@ jobs:
2121
- name: Checkout
2222
uses: actions/checkout@v4
2323

24+
- name: Install signing certificate
25+
env:
26+
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
27+
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
28+
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
29+
run: |
30+
CERTIFICATE_PATH="$RUNNER_TEMP/build_certificate.p12"
31+
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
32+
CERTIFICATE_BASE64_PATH="$RUNNER_TEMP/build_certificate.base64"
33+
34+
printf '%s' "$BUILD_CERTIFICATE_BASE64" > "$CERTIFICATE_BASE64_PATH"
35+
base64 -D -i "$CERTIFICATE_BASE64_PATH" -o "$CERTIFICATE_PATH"
36+
37+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
38+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
39+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
40+
security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
41+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
42+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
43+
2444
- name: Set version
2545
id: version
2646
run: |
@@ -36,44 +56,76 @@ jobs:
3656
echo "is_release=false" >> $GITHUB_OUTPUT
3757
fi
3858
39-
- name: Build
59+
- name: Archive
4060
run: |
4161
xcodebuild \
4262
-project Wave.xcodeproj \
4363
-scheme Wave \
4464
-configuration Release \
65+
-archivePath build/Wave.xcarchive \
4566
-derivedDataPath build \
67+
archive \
4668
MARKETING_VERSION="${{ steps.version.outputs.marketing_version }}" \
4769
CURRENT_PROJECT_VERSION="${{ steps.version.outputs.marketing_version }}" \
48-
CODE_SIGNING_ALLOWED=NO \
49-
CODE_SIGNING_REQUIRED=NO \
70+
CODE_SIGN_IDENTITY="Developer ID Application" \
71+
OTHER_CODE_SIGN_FLAGS="--keychain $RUNNER_TEMP/app-signing.keychain-db" \
5072
STRIP_INSTALLED_PRODUCT=YES \
5173
DEAD_CODE_STRIPPING=YES \
5274
DEBUG_INFORMATION_FORMAT=dwarf-with-dsym \
5375
SWIFT_OPTIMIZATION_LEVEL=-Osize \
5476
SWIFT_COMPILATION_MODE=wholemodule \
5577
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES=NO
5678
79+
- name: Export signed app
80+
env:
81+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
82+
run: |
83+
cat <<EOF > "$RUNNER_TEMP/ExportOptions.plist"
84+
<?xml version="1.0" encoding="UTF-8"?>
85+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
86+
<plist version="1.0">
87+
<dict>
88+
<key>method</key>
89+
<string>developer-id</string>
90+
<key>signingStyle</key>
91+
<string>automatic</string>
92+
<key>signingCertificate</key>
93+
<string>Developer ID Application</string>
94+
<key>teamID</key>
95+
<string>${APPLE_TEAM_ID}</string>
96+
</dict>
97+
</plist>
98+
EOF
99+
100+
xcodebuild \
101+
-exportArchive \
102+
-archivePath build/Wave.xcarchive \
103+
-exportPath build/export \
104+
-exportOptionsPlist "$RUNNER_TEMP/ExportOptions.plist" \
105+
OTHER_CODE_SIGN_FLAGS="--keychain $RUNNER_TEMP/app-signing.keychain-db"
106+
57107
- name: Debug app size
58108
run: |
59109
echo "=== App bundle total ==="
60-
du -sh build/Build/Products/Release/Wave.app
110+
du -sh build/export/Wave.app
61111
echo "=== Contents breakdown ==="
62-
du -sh build/Build/Products/Release/Wave.app/Contents/*
112+
du -sh build/export/Wave.app/Contents/*
63113
echo "=== Frameworks ==="
64-
du -sh build/Build/Products/Release/Wave.app/Contents/Frameworks/* 2>/dev/null || echo "No frameworks"
114+
du -sh build/export/Wave.app/Contents/Frameworks/* 2>/dev/null || echo "No frameworks"
65115
echo "=== Binary ==="
66-
du -sh build/Build/Products/Release/Wave.app/Contents/MacOS/*
116+
du -sh build/export/Wave.app/Contents/MacOS/*
67117
echo "=== Plugins/XPC ==="
68-
du -sh build/Build/Products/Release/Wave.app/Contents/PlugIns/* 2>/dev/null || echo "No plugins"
118+
du -sh build/export/Wave.app/Contents/PlugIns/* 2>/dev/null || echo "No plugins"
69119
echo "=== Resources (top level) ==="
70-
du -sh build/Build/Products/Release/Wave.app/Contents/Resources/* 2>/dev/null | sort -rh | head -20
120+
du -sh build/export/Wave.app/Contents/Resources/* 2>/dev/null | sort -rh | head -20
71121
72-
- name: Ad-hoc sign
73-
run: codesign --force --deep --sign - build/Build/Products/Release/Wave.app
122+
- name: Verify signed app
123+
run: |
124+
codesign --verify --deep --strict --verbose=2 build/export/Wave.app
125+
spctl -a -vvv -t execute build/export/Wave.app
74126
75127
- name: Upload app
76-
run: ditto -c -k --sequesterRsrc --keepParent build/Build/Products/Release/Wave.app Wave.app.zip
128+
run: ditto -c -k --sequesterRsrc --keepParent build/export/Wave.app Wave.app.zip
77129

78130
- name: Upload app artifact
79131
uses: actions/upload-artifact@v4
@@ -97,6 +149,26 @@ jobs:
97149
- name: Extract app
98150
run: ditto -x -k Wave.app.zip .
99151

152+
- name: Install signing certificate
153+
env:
154+
BUILD_CERTIFICATE_BASE64: ${{ secrets.BUILD_CERTIFICATE_BASE64 }}
155+
P12_PASSWORD: ${{ secrets.P12_PASSWORD }}
156+
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
157+
run: |
158+
CERTIFICATE_PATH="$RUNNER_TEMP/build_certificate.p12"
159+
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
160+
CERTIFICATE_BASE64_PATH="$RUNNER_TEMP/build_certificate.base64"
161+
162+
printf '%s' "$BUILD_CERTIFICATE_BASE64" > "$CERTIFICATE_BASE64_PATH"
163+
base64 -D -i "$CERTIFICATE_BASE64_PATH" -o "$CERTIFICATE_PATH"
164+
165+
security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
166+
security set-keychain-settings -lut 21600 "$KEYCHAIN_PATH"
167+
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
168+
security import "$CERTIFICATE_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN_PATH"
169+
security set-key-partition-list -S apple-tool:,apple:,codesign: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN_PATH"
170+
security list-keychains -d user -s "$KEYCHAIN_PATH" $(security list-keychains -d user | tr -d '"')
171+
100172
- name: Install create-dmg
101173
run: brew install create-dmg
102174

@@ -118,6 +190,35 @@ jobs:
118190
"$DMG_NAME" \
119191
Wave.app/
120192
193+
- name: Sign DMG
194+
run: |
195+
DMG_NAME="${{ steps.dmg.outputs.dmg_name }}"
196+
codesign --force --sign "Developer ID Application" --keychain "$RUNNER_TEMP/app-signing.keychain-db" "$DMG_NAME"
197+
codesign --verify --verbose=2 "$DMG_NAME"
198+
199+
- name: Notarize and staple DMG
200+
env:
201+
APPLE_ID: ${{ secrets.APPLE_ID }}
202+
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
203+
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
204+
run: |
205+
DMG_NAME="${{ steps.dmg.outputs.dmg_name }}"
206+
KEYCHAIN_PATH="$RUNNER_TEMP/app-signing.keychain-db"
207+
208+
xcrun notarytool store-credentials wave-notary \
209+
--apple-id "$APPLE_ID" \
210+
--team-id "$APPLE_TEAM_ID" \
211+
--password "$APPLE_APP_SPECIFIC_PASSWORD" \
212+
--keychain "$KEYCHAIN_PATH"
213+
214+
xcrun notarytool submit "$DMG_NAME" \
215+
--keychain-profile wave-notary \
216+
--keychain "$KEYCHAIN_PATH" \
217+
--wait
218+
219+
xcrun stapler staple "$DMG_NAME"
220+
spctl -a -t open --context context:primary-signature -vv "$DMG_NAME"
221+
121222
- name: Upload DMG
122223
uses: actions/upload-artifact@v4
123224
with:

README.md

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -32,14 +32,7 @@ Both shortcuts are fully customizable in Settings → Shortcut.
3232

3333
Download the latest DMG from [Releases](https://github.com/mxvsh/wave/releases/latest).
3434

35-
> **Note:** Wave is not code-signed. On first launch, macOS will block it. To open it:
36-
> 1. Right-click `Wave.app`**Open**
37-
> 2. Click **Open** in the dialog
38-
>
39-
> Alternatively, run in Terminal:
40-
> ```bash
41-
> xattr -dr com.apple.quarantine /Applications/Wave.app
42-
> ```
35+
Releases are distributed as signed, notarized DMGs.
4336

4437
## Build from source
4538

@@ -53,6 +46,20 @@ open build/Build/Products/Release/Wave.app
5346

5447
Or launch from Xcode — open `Wave.xcodeproj`, select the `Wave` scheme, and run.
5548

49+
## Signed releases
50+
51+
GitHub Actions builds signed and notarized release DMGs for tags matching `v*.*.*`.
52+
53+
Required repository secrets:
54+
55+
- `BUILD_CERTIFICATE_BASE64` — base64-encoded `Developer ID Application` `.p12`
56+
- `P12_PASSWORD` — password for the `.p12`
57+
- `KEYCHAIN_PASSWORD` — temporary CI keychain password
58+
- `APPLE_ID` — Apple ID email used for notarization
59+
- `APPLE_APP_SPECIFIC_PASSWORD` — app-specific password for notarization
60+
- `APPLE_TEAM_ID` — Apple Developer Team ID
61+
- `SPARKLE_PRIVATE_KEY` — Sparkle EdDSA private key for appcast generation
62+
5663
## Roadmap
5764

5865
- [x] Toggle and Push to Talk recording modes

0 commit comments

Comments
 (0)