mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 07:04:35 +00:00
Add Developer ID signing and notarization to the macOS build.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
parent
2cb3565df5
commit
452dba4a8a
4 changed files with 191 additions and 10 deletions
32
.github/workflows/build.yml
vendored
32
.github/workflows/build.yml
vendored
|
|
@ -94,9 +94,41 @@ jobs:
|
|||
run: |
|
||||
brew install gtk4 pkg-config
|
||||
|
||||
# Developer ID lives in repo secrets. Without it the bundle is only
|
||||
# ad-hoc signed and Gatekeeper still blocks downloaded copies.
|
||||
- name: Import Developer ID certificate
|
||||
if: ${{ secrets.MACOS_CERTIFICATE != '' }}
|
||||
env:
|
||||
MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }}
|
||||
MACOS_CERTIFICATE_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
cert="$RUNNER_TEMP/certificate.p12"
|
||||
keychain="$RUNNER_TEMP/signing.keychain-db"
|
||||
pass="$(openssl rand -base64 32)"
|
||||
echo "$MACOS_CERTIFICATE" | base64 --decode > "$cert"
|
||||
security create-keychain -p "$pass" "$keychain"
|
||||
security set-keychain-settings -lut 21600 "$keychain"
|
||||
security unlock-keychain -p "$pass" "$keychain"
|
||||
security import "$cert" -k "$keychain" \
|
||||
-P "$MACOS_CERTIFICATE_PASSWORD" \
|
||||
-T /usr/bin/codesign -T /usr/bin/security
|
||||
security set-key-partition-list -S apple-tool:,apple: -s \
|
||||
-k "$pass" "$keychain"
|
||||
security list-keychains -d user -s "$keychain" \
|
||||
$(security list-keychains -d user | tr -d '"')
|
||||
rm -f "$cert"
|
||||
|
||||
- name: Build .app zip
|
||||
env:
|
||||
COMMIT: ${{ github.sha }}
|
||||
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
|
||||
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}
|
||||
APPLE_ID: ${{ secrets.APPLE_ID }}
|
||||
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
|
||||
APPLE_API_KEY: ${{ secrets.APPLE_API_KEY }}
|
||||
APPLE_API_KEY_ID: ${{ secrets.APPLE_API_KEY_ID }}
|
||||
APPLE_API_ISSUER: ${{ secrets.APPLE_API_ISSUER }}
|
||||
run: |
|
||||
chmod +x scripts/bundle-macos.sh
|
||||
./scripts/bundle-macos.sh
|
||||
|
|
|
|||
37
README.md
37
README.md
|
|
@ -33,10 +33,11 @@ build, takže soubor nikdy nezestárne.
|
|||
Kam se to nainstaluje: `/Applications` nebo `~/Applications` (macOS),
|
||||
`%LOCALAPPDATA%\Programs\Maturita` (Windows), `~/Applications` (Linux).
|
||||
|
||||
Buildy nejsou podepsané, proto je při **prvním** spuštění potřeba výše uvedený
|
||||
krok navíc. Aplikace se pak udržuje sama: po startu porovná svůj commit se
|
||||
souborem `VERSION` ve větvi `builds` a aktualizaci nabídne v pruhu nahoře.
|
||||
Ruční kontrola je v **Nastavení → Aktualizace**.
|
||||
Buildy zatím nejsou notarizované (chybí Apple Developer ID), proto je při
|
||||
**prvním** spuštění potřeba výše uvedený krok navíc. Aplikace se pak udržuje
|
||||
sama: po startu porovná svůj commit se souborem `VERSION` ve větvi `builds`
|
||||
a aktualizaci nabídne v pruhu nahoře. Ruční kontrola je
|
||||
v **Nastavení → Aktualizace**.
|
||||
|
||||
#### Bez instalátoru
|
||||
|
||||
|
|
@ -95,6 +96,16 @@ make bundle # AppImage / .app zip / Windows složka s DLL
|
|||
|
||||
Volitelně CMake: `cmake -S . -B build && cmake --build build`.
|
||||
|
||||
Aby stažená aplikace na macOS šla otevřít bez potvrzení v Nastavení, CI
|
||||
potřebuje **Apple Developer Program** a GitHub Secrets:
|
||||
|
||||
- `MACOS_CERTIFICATE` — Developer ID Application `.p12` v base64
|
||||
- `MACOS_CERTIFICATE_PASSWORD`
|
||||
- `APPLE_API_KEY`, `APPLE_API_KEY_ID`, `APPLE_API_ISSUER` — notarizace
|
||||
(nebo `APPLE_ID` + `APPLE_APP_SPECIFIC_PASSWORD` + `APPLE_TEAM_ID`)
|
||||
|
||||
Bez toho zůstane jen ad-hoc podpis a Gatekeeper stažený build zablokuje.
|
||||
|
||||
#### Windows (MSYS2 UCRT64, bez Visual Studia)
|
||||
|
||||
```cmd
|
||||
|
|
@ -154,10 +165,10 @@ the current build when you run it, so the file never goes stale.
|
|||
Install locations: `/Applications` or `~/Applications` (macOS),
|
||||
`%LOCALAPPDATA%\Programs\Maturita` (Windows), `~/Applications` (Linux).
|
||||
|
||||
The builds are unsigned, so the first launch needs the extra step above.
|
||||
After that the app stays current: it compares its commit to `VERSION` on
|
||||
`builds` and offers an update in a banner. Manual check:
|
||||
**Settings → Updates**.
|
||||
The builds are not notarized yet (no Apple Developer ID), so the first
|
||||
launch still needs the extra step above. After that the app stays current:
|
||||
it compares its commit to `VERSION` on `builds` and offers an update in a
|
||||
banner. Manual check: **Settings → Updates**.
|
||||
|
||||
#### Packages without the installer
|
||||
|
||||
|
|
@ -216,6 +227,16 @@ make bundle # AppImage / .app zip / Windows DLL folder
|
|||
|
||||
Optional CMake: `cmake -S . -B build && cmake --build build`.
|
||||
|
||||
Skipping Gatekeeper on a downloaded macOS build needs an
|
||||
**Apple Developer Program** membership and these GitHub Secrets:
|
||||
|
||||
- `MACOS_CERTIFICATE` — Developer ID Application `.p12`, base64-encoded
|
||||
- `MACOS_CERTIFICATE_PASSWORD`
|
||||
- `APPLE_API_KEY`, `APPLE_API_KEY_ID`, `APPLE_API_ISSUER` for notarization
|
||||
(or `APPLE_ID` + `APPLE_APP_SPECIFIC_PASSWORD` + `APPLE_TEAM_ID`)
|
||||
|
||||
Without them the bundle stays ad-hoc signed and Gatekeeper still blocks it.
|
||||
|
||||
#### Windows (MSYS2 UCRT64, no Visual Studio)
|
||||
|
||||
```cmd
|
||||
|
|
|
|||
14
macos/Maturita.entitlements
Normal file
14
macos/Maturita.entitlements
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN"
|
||||
"http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- The launcher exports DYLD_LIBRARY_PATH for bundled GTK dylibs. -->
|
||||
<key>com.apple.security.cs.allow-dyld-environment-variables</key>
|
||||
<true/>
|
||||
<!-- Homebrew-copied libraries are re-signed, but GTK still loads extra
|
||||
modules from Resources; keep validation off so those resolve. -->
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
|
@ -243,8 +243,120 @@ if command -v sips >/dev/null 2>&1 && command -v iconutil >/dev/null 2>&1; then
|
|||
iconutil -c icns -o "$RES/AppIcon.icns" "$ICONSET" && rm -rf "$ICONSET" || true
|
||||
fi
|
||||
|
||||
# Ad-hoc sign the .app so Gatekeeper is less likely to kill dyld loads.
|
||||
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 || true
|
||||
# Developer ID + notarization is what lets a downloaded app open without
|
||||
# System Settings. Without a certificate the bundle stays ad-hoc signed, which
|
||||
# is enough for local runs but Gatekeeper still blocks internet downloads.
|
||||
ENTITLEMENTS="$ROOT/macos/Maturita.entitlements"
|
||||
|
||||
pick_sign_identity() {
|
||||
if [[ -n "${MACOS_SIGN_IDENTITY:-}" ]]; then
|
||||
printf '%s\n' "$MACOS_SIGN_IDENTITY"
|
||||
return
|
||||
fi
|
||||
local found
|
||||
found="$(security find-identity -v -p codesigning 2>/dev/null \
|
||||
| awk -F'"' '/Developer ID Application/ { print $2; exit }')"
|
||||
if [[ -n "$found" ]]; then
|
||||
printf '%s\n' "$found"
|
||||
return
|
||||
fi
|
||||
printf '%s\n' "-"
|
||||
}
|
||||
|
||||
sign_one() {
|
||||
local path="$1"
|
||||
local use_entitlements="${2:-}"
|
||||
local args=(--force --sign "$IDENTITY")
|
||||
|
||||
if [[ "$IDENTITY" != "-" ]]; then
|
||||
args+=(--options runtime --timestamp)
|
||||
fi
|
||||
if [[ "$use_entitlements" == "entitlements" && -f "$ENTITLEMENTS" ]]; then
|
||||
args+=(--entitlements "$ENTITLEMENTS")
|
||||
fi
|
||||
codesign "${args[@]}" "$path"
|
||||
}
|
||||
|
||||
sign_bundle() {
|
||||
local f
|
||||
|
||||
echo "==> Signing $APP_NAME"
|
||||
IDENTITY="$(pick_sign_identity)"
|
||||
if [[ "$IDENTITY" == "-" ]]; then
|
||||
echo " identity: ad-hoc (downloaded copies will still need Gatekeeper approval)"
|
||||
else
|
||||
echo " identity: $IDENTITY"
|
||||
fi
|
||||
|
||||
shopt -s nullglob
|
||||
for f in "$FW"/*.dylib; do
|
||||
sign_one "$f"
|
||||
done
|
||||
shopt -u nullglob
|
||||
|
||||
sign_one "$MACOS/maturita-bin" entitlements
|
||||
sign_one "$MACOS/maturita" entitlements
|
||||
sign_one "$APP" entitlements
|
||||
|
||||
codesign --verify --strict --verbose=2 "$APP"
|
||||
}
|
||||
|
||||
can_notarize() {
|
||||
[[ "$IDENTITY" != "-" ]] || return 1
|
||||
if [[ -n "${APPLE_API_KEY:-}" && -n "${APPLE_API_KEY_ID:-}" &&
|
||||
-n "${APPLE_API_ISSUER:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -n "${APPLE_ID:-}" && -n "${APPLE_APP_SPECIFIC_PASSWORD:-}" &&
|
||||
-n "${APPLE_TEAM_ID:-}" ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
|
||||
notarize_bundle() {
|
||||
local zip notary_args keyfile
|
||||
|
||||
if [[ "$IDENTITY" == "-" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if ! can_notarize; then
|
||||
if [[ -n "${GITHUB_ACTIONS:-}${CI:-}" ]]; then
|
||||
echo "error: Developer ID is present but notarization secrets are missing." >&2
|
||||
echo "Set APPLE_API_KEY + APPLE_API_KEY_ID + APPLE_API_ISSUER, or" >&2
|
||||
echo "APPLE_ID + APPLE_APP_SPECIFIC_PASSWORD + APPLE_TEAM_ID." >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " warning: signed, but not notarized — Gatekeeper will still block downloads"
|
||||
return 0
|
||||
fi
|
||||
|
||||
echo "==> Notarizing $APP_NAME"
|
||||
zip="$ROOT/dist/.notarize-$OUT_ZIP"
|
||||
rm -f "$zip"
|
||||
ditto -c -k --keepParent "$APP" "$zip"
|
||||
|
||||
notary_args=(submit "$zip" --wait)
|
||||
keyfile=""
|
||||
if [[ -n "${APPLE_API_KEY:-}" ]]; then
|
||||
keyfile="$(mktemp)"
|
||||
printf '%s\n' "$APPLE_API_KEY" > "$keyfile"
|
||||
notary_args+=(--key "$keyfile" --key-id "$APPLE_API_KEY_ID"
|
||||
--issuer "$APPLE_API_ISSUER")
|
||||
else
|
||||
notary_args+=(--apple-id "$APPLE_ID"
|
||||
--password "$APPLE_APP_SPECIFIC_PASSWORD"
|
||||
--team-id "$APPLE_TEAM_ID")
|
||||
fi
|
||||
|
||||
xcrun notarytool "${notary_args[@]}"
|
||||
[[ -n "$keyfile" ]] && rm -f "$keyfile"
|
||||
rm -f "$zip"
|
||||
xcrun stapler staple "$APP"
|
||||
echo " stapled notarization ticket"
|
||||
}
|
||||
|
||||
sign_bundle
|
||||
|
||||
echo "==> Smoke-testing launch"
|
||||
if [[ -n "${GITHUB_ACTIONS:-}${CI:-}" ]]; then
|
||||
|
|
@ -270,6 +382,8 @@ else
|
|||
echo " smoke test OK (process stayed up)"
|
||||
fi
|
||||
|
||||
notarize_bundle
|
||||
|
||||
echo "==> Zipping"
|
||||
rm -f "$ROOT/dist/$OUT_ZIP"
|
||||
(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue