Skip to content

Commit 05dedcd

Browse files
awhitchurchclaude
andcommitted
Ship desktop-only releases; ad-hoc sign macOS
v3.0.0 is desktop-only (Linux/Windows/macOS). Mobile already ships through the App Store and Google Play, and the GitHub APK needs its own release keystore, so the Android job moves out of the release path for now. macOS is ad-hoc signed rather than notarized: the AC_CERTIFICATE secret imports 0 valid codesigning identities, so there is nothing to notarize with. Notarization needs a fresh "Developer ID Application" .p12. Also fix the Android heap failure found while validating (JetifyTransform OOM'd at -Xmx1536M) and bump actions/checkout to v5 (v4 is on deprecated Node 20). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 3274f82 commit 05dedcd

3 files changed

Lines changed: 34 additions & 169 deletions

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ jobs:
2424
timeout-minutes: 30
2525
steps:
2626
- name: Checkout
27-
uses: actions/checkout@v4
27+
uses: actions/checkout@v5
2828

2929
- name: Setup Flutter
3030
uses: subosito/flutter-action@v2

.github/workflows/release.yml

Lines changed: 27 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,12 @@
77
# Artifacts:
88
# OpenView-linux-x64.zip
99
# OpenView-windows-x64.zip
10-
# OpenView-macos-arm64.zip (Developer ID signed + notarized when the
11-
# AC_CERTIFICATE / APPLE_* secrets are present)
12-
# OpenView-android.apk (release-signed when the KEYSTORE_* secrets are
13-
# present; tag builds FAIL without them)
10+
# OpenView-macos-arm64.zip (ad-hoc signed — see the macOS job)
1411
# SHA256SUMS.txt
1512
#
16-
# Required secrets for a fully signed release:
17-
# macOS AC_CERTIFICATE, AC_CERTIFICATE_PASSWORD, KEYCHAIN_PASSWORD,
18-
# APPLE_ID, APPLE_PASSWORD (app-specific pw), APPLE_TEAM_ID
19-
# Android KEYSTORE_BASE64, KEY_ALIAS, KEY_PASSWORD, STORE_PASSWORD
13+
# Desktop only. Mobile ships through the App Store and Google Play, and the
14+
# Android APK / iOS pipeline is tracked separately — do not add a mobile job
15+
# here without also sorting out release signing (see docs/mobile-deployment.md).
2016
name: Release builds
2117

2218
on:
@@ -46,7 +42,7 @@ jobs:
4642
timeout-minutes: 5
4743
steps:
4844
- name: Checkout
49-
uses: actions/checkout@v4
45+
uses: actions/checkout@v5
5046

5147
- name: Tag matches pubspec version
5248
if: startsWith(github.ref, 'refs/tags/')
@@ -68,7 +64,7 @@ jobs:
6864
timeout-minutes: 60
6965
steps:
7066
- name: Checkout
71-
uses: actions/checkout@v4
67+
uses: actions/checkout@v5
7268

7369
- name: Setup Flutter
7470
uses: subosito/flutter-action@v2
@@ -113,7 +109,7 @@ jobs:
113109
timeout-minutes: 60
114110
steps:
115111
- name: Checkout
116-
uses: actions/checkout@v4
112+
uses: actions/checkout@v5
117113

118114
- name: Setup Flutter
119115
uses: subosito/flutter-action@v2
@@ -149,14 +145,20 @@ jobs:
149145
# Development' found"). We therefore configure with Flutter and build with
150146
# xcodebuild, overriding the signing settings on the command line (command-line
151147
# settings outrank the ones baked into project.pbxproj).
148+
#
149+
# The app is ad-hoc signed: it runs, but it is not notarized, so Gatekeeper
150+
# shows "Apple cannot check it for malicious software" on first launch and the
151+
# user must right-click → Open (or clear the quarantine bit). Developer ID
152+
# signing + notarization is a follow-up — the AC_CERTIFICATE / APPLE_* secrets
153+
# for it already exist in this repo.
152154
macos:
153155
name: macOS arm64
154156
needs: version
155157
runs-on: macos-latest
156158
timeout-minutes: 90
157159
steps:
158160
- name: Checkout
159-
uses: actions/checkout@v4
161+
uses: actions/checkout@v5
160162

161163
- name: Setup Flutter
162164
uses: subosito/flutter-action@v2
@@ -168,92 +170,25 @@ jobs:
168170
- name: Install build tools (libserialport)
169171
run: brew install automake libtool
170172

171-
- name: Import Developer ID certificate
172-
id: cert
173-
env:
174-
AC_CERTIFICATE: ${{ secrets.AC_CERTIFICATE }}
175-
AC_CERTIFICATE_PASSWORD: ${{ secrets.AC_CERTIFICATE_PASSWORD }}
176-
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
177-
run: |
178-
if [ -z "$AC_CERTIFICATE" ]; then
179-
echo "::warning::AC_CERTIFICATE not set — building ad-hoc signed (Gatekeeper will warn users)."
180-
echo "identity=-" >> "$GITHUB_OUTPUT"
181-
exit 0
182-
fi
183-
184-
keychain="$RUNNER_TEMP/build.keychain-db"
185-
security create-keychain -p "$KEYCHAIN_PASSWORD" "$keychain"
186-
security set-keychain-settings -lut 21600 "$keychain"
187-
security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$keychain"
188-
189-
echo "$AC_CERTIFICATE" | base64 --decode > "$RUNNER_TEMP/cert.p12"
190-
security import "$RUNNER_TEMP/cert.p12" -k "$keychain" \
191-
-P "$AC_CERTIFICATE_PASSWORD" -T /usr/bin/codesign -T /usr/bin/security
192-
security set-key-partition-list -S apple-tool:,apple:,codesign: \
193-
-s -k "$KEYCHAIN_PASSWORD" "$keychain" >/dev/null
194-
# Put our keychain first in the search list so xcodebuild finds the key.
195-
security list-keychains -d user -s "$keychain" $(security list-keychains -d user | tr -d '"')
196-
rm -f "$RUNNER_TEMP/cert.p12"
197-
198-
security find-identity -v -p codesigning "$keychain"
199-
identity=$(security find-identity -v -p codesigning "$keychain" \
200-
| grep 'Developer ID Application' | head -1 | sed -E 's/.*"(.*)"/\1/')
201-
if [ -z "$identity" ]; then
202-
echo "::warning::No 'Developer ID Application' identity in AC_CERTIFICATE —" \
203-
"falling back to ad-hoc signing (no notarization)."
204-
identity='-'
205-
fi
206-
echo "identity=$identity" >> "$GITHUB_OUTPUT"
207-
208173
- name: Flutter config & deps
209174
run: |
210175
flutter config --enable-macos-desktop
211176
flutter pub get
212177
213178
- name: Build macOS release
214-
env:
215-
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
216-
IDENTITY: ${{ steps.cert.outputs.identity }}
217179
run: |
218180
# Generate the ephemeral xcconfig / plugin registrant without building.
219181
flutter build macos --release --config-only
220182
221-
args=(
222-
-workspace macos/Runner.xcworkspace
223-
-scheme Runner
224-
-configuration Release
225-
-derivedDataPath "$RUNNER_TEMP/dd"
226-
CODE_SIGN_STYLE=Manual
227-
PROVISIONING_PROFILE_SPECIFIER=
228-
)
229-
if [ "$IDENTITY" = "-" ]; then
230-
args+=(CODE_SIGN_IDENTITY=-) # ad-hoc: runs locally, Gatekeeper warns
231-
else
232-
args+=(
233-
CODE_SIGN_IDENTITY="$IDENTITY"
234-
DEVELOPMENT_TEAM="$APPLE_TEAM_ID"
235-
ENABLE_HARDENED_RUNTIME=YES # required for notarization
236-
OTHER_CODE_SIGN_FLAGS=--timestamp
237-
)
238-
fi
239-
xcodebuild "${args[@]}" build
240-
241-
- name: Notarize & staple
242-
if: steps.cert.outputs.identity != '-'
243-
env:
244-
APPLE_ID: ${{ secrets.APPLE_ID }}
245-
APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }}
246-
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
247-
run: |
248-
app="$RUNNER_TEMP/dd/Build/Products/Release/OpenView.app"
249-
ditto -c -k --sequesterRsrc --keepParent "$app" "$RUNNER_TEMP/notarize.zip"
250-
xcrun notarytool submit "$RUNNER_TEMP/notarize.zip" \
251-
--apple-id "$APPLE_ID" \
252-
--password "$APPLE_PASSWORD" \
253-
--team-id "$APPLE_TEAM_ID" \
254-
--wait
255-
xcrun stapler staple "$app"
256-
spctl --assess --type execute --verbose "$app"
183+
xcodebuild \
184+
-workspace macos/Runner.xcworkspace \
185+
-scheme Runner \
186+
-configuration Release \
187+
-derivedDataPath "$RUNNER_TEMP/dd" \
188+
CODE_SIGN_STYLE=Manual \
189+
CODE_SIGN_IDENTITY=- \
190+
PROVISIONING_PROFILE_SPECIFIER= \
191+
build
257192
258193
- name: Package
259194
run: |
@@ -263,9 +198,9 @@ jobs:
263198
find "$RUNNER_TEMP/dd/Build/Products" -maxdepth 3 -name '*.app' || true
264199
exit 1
265200
fi
266-
# ditto (not zip) preserves app-bundle metadata and the stapled ticket.
201+
codesign --verify --strict --verbose=2 "$app"
202+
# ditto (not zip) preserves app-bundle metadata and symlinks.
267203
ditto -c -k --sequesterRsrc --keepParent "$app" OpenView-macos-arm64.zip
268-
codesign --verify --deep --strict --verbose=2 "$app" || true
269204
270205
- name: Upload artifact
271206
uses: actions/upload-artifact@v4
@@ -274,83 +209,10 @@ jobs:
274209
path: OpenView-macos-arm64.zip
275210
if-no-files-found: error
276211

277-
# ── Android APK ────────────────────────────────────────────────────────
278-
android:
279-
name: Android APK
280-
needs: version
281-
runs-on: ubuntu-latest
282-
timeout-minutes: 60
283-
steps:
284-
- name: Checkout
285-
uses: actions/checkout@v4
286-
287-
- name: Setup Java
288-
uses: actions/setup-java@v4
289-
with:
290-
distribution: temurin
291-
java-version: '17'
292-
cache: gradle
293-
294-
- name: Setup Flutter
295-
uses: subosito/flutter-action@v2
296-
with:
297-
flutter-version: ${{ env.FLUTTER_VERSION }}
298-
channel: stable
299-
cache: true
300-
301-
- name: Flutter deps
302-
run: flutter pub get
303-
304-
# Writes android/key.properties, which build.gradle.kts picks up. A tag
305-
# build without a keystore is a hard error — shipping a debug-signed APK on
306-
# a public release is worse than shipping no APK (it can never be upgraded
307-
# in place by a properly signed build).
308-
- name: Prepare release signing
309-
env:
310-
KEYSTORE_BASE64: ${{ secrets.KEYSTORE_BASE64 }}
311-
KEY_ALIAS: ${{ secrets.KEY_ALIAS }}
312-
KEY_PASSWORD: ${{ secrets.KEY_PASSWORD }}
313-
STORE_PASSWORD: ${{ secrets.STORE_PASSWORD }}
314-
run: |
315-
if [ -z "$KEYSTORE_BASE64" ]; then
316-
if [ "${GITHUB_REF_TYPE}" = "tag" ]; then
317-
echo "::error::KEYSTORE_BASE64 is not set. A tagged release must ship a" \
318-
"release-signed APK. Add KEYSTORE_BASE64, KEY_ALIAS, KEY_PASSWORD and" \
319-
"STORE_PASSWORD as repository secrets, then re-run."
320-
exit 1
321-
fi
322-
echo "::warning::No keystore secrets — this APK will be debug-signed (dev build only)."
323-
exit 0
324-
fi
325-
echo "$KEYSTORE_BASE64" | base64 --decode > android/release-keystore.jks
326-
cat > android/key.properties <<EOF
327-
storeFile=release-keystore.jks
328-
storePassword=$STORE_PASSWORD
329-
keyAlias=$KEY_ALIAS
330-
keyPassword=$KEY_PASSWORD
331-
EOF
332-
333-
- name: Build APK
334-
run: flutter build apk --release
335-
336-
- name: Verify APK signature
337-
run: |
338-
cp build/app/outputs/flutter-apk/app-release.apk OpenView-android.apk
339-
apksigner=$(find "$ANDROID_HOME/build-tools" -name apksigner | sort -r | head -1)
340-
"$apksigner" verify --print-certs OpenView-android.apk
341-
ls -la OpenView-android.apk
342-
343-
- name: Upload artifact
344-
uses: actions/upload-artifact@v4
345-
with:
346-
name: OpenView-android
347-
path: OpenView-android.apk
348-
if-no-files-found: error
349-
350212
# ── Publish GitHub Release (tags only) ─────────────────────────────────
351213
publish:
352214
name: Publish GitHub Release
353-
needs: [linux, windows, macos, android]
215+
needs: [linux, windows, macos]
354216
if: startsWith(github.ref, 'refs/tags/')
355217
runs-on: ubuntu-latest
356218
timeout-minutes: 15
@@ -364,7 +226,7 @@ jobs:
364226
- name: Checksums
365227
working-directory: release
366228
run: |
367-
sha256sum ./*.zip ./*.apk | sed 's|\./||' | tee SHA256SUMS.txt
229+
sha256sum ./*.zip | sed 's|\./||' | tee SHA256SUMS.txt
368230
369231
- name: Create GitHub Release
370232
uses: softprops/action-gh-release@v2
@@ -375,7 +237,6 @@ jobs:
375237
release/OpenView-linux-x64.zip
376238
release/OpenView-windows-x64.zip
377239
release/OpenView-macos-arm64.zip
378-
release/OpenView-android.apk
379240
release/SHA256SUMS.txt
380241
env:
381242
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

android/gradle.properties

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,8 @@
1-
org.gradle.jvmargs=-Xmx1536M
1+
# 1536M is not enough to jetify/merge the Flutter release native libs — the CI
2+
# APK build died with "Java heap space" in JetifyTransform.
3+
org.gradle.jvmargs=-Xmx4g
24
android.enableR8=true
35
android.useAndroidX=true
4-
android.enableJetifier=true
6+
# Jetifier rewrites legacy android.support.* deps to AndroidX. Every plugin here
7+
# is already AndroidX, so this only cost build time and heap.
8+
android.enableJetifier=false

0 commit comments

Comments
 (0)