mirror of
https://github.com/pepaskopec-blip/Graduately.git
synced 2026-10-27 15:14: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: |
|
run: |
|
||||||
brew install gtk4 pkg-config
|
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
|
- name: Build .app zip
|
||||||
env:
|
env:
|
||||||
COMMIT: ${{ github.sha }}
|
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: |
|
run: |
|
||||||
chmod +x scripts/bundle-macos.sh
|
chmod +x scripts/bundle-macos.sh
|
||||||
./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),
|
Kam se to nainstaluje: `/Applications` nebo `~/Applications` (macOS),
|
||||||
`%LOCALAPPDATA%\Programs\Maturita` (Windows), `~/Applications` (Linux).
|
`%LOCALAPPDATA%\Programs\Maturita` (Windows), `~/Applications` (Linux).
|
||||||
|
|
||||||
Buildy nejsou podepsané, proto je při **prvním** spuštění potřeba výše uvedený
|
Buildy zatím nejsou notarizované (chybí Apple Developer ID), proto je při
|
||||||
krok navíc. Aplikace se pak udržuje sama: po startu porovná svůj commit se
|
**prvním** spuštění potřeba výše uvedený krok navíc. Aplikace se pak udržuje
|
||||||
souborem `VERSION` ve větvi `builds` a aktualizaci nabídne v pruhu nahoře.
|
sama: po startu porovná svůj commit se souborem `VERSION` ve větvi `builds`
|
||||||
Ruční kontrola je v **Nastavení → Aktualizace**.
|
a aktualizaci nabídne v pruhu nahoře. Ruční kontrola je
|
||||||
|
v **Nastavení → Aktualizace**.
|
||||||
|
|
||||||
#### Bez instalátoru
|
#### 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`.
|
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)
|
#### Windows (MSYS2 UCRT64, bez Visual Studia)
|
||||||
|
|
||||||
```cmd
|
```cmd
|
||||||
|
|
@ -154,10 +165,10 @@ the current build when you run it, so the file never goes stale.
|
||||||
Install locations: `/Applications` or `~/Applications` (macOS),
|
Install locations: `/Applications` or `~/Applications` (macOS),
|
||||||
`%LOCALAPPDATA%\Programs\Maturita` (Windows), `~/Applications` (Linux).
|
`%LOCALAPPDATA%\Programs\Maturita` (Windows), `~/Applications` (Linux).
|
||||||
|
|
||||||
The builds are unsigned, so the first launch needs the extra step above.
|
The builds are not notarized yet (no Apple Developer ID), so the first
|
||||||
After that the app stays current: it compares its commit to `VERSION` on
|
launch still needs the extra step above. After that the app stays current:
|
||||||
`builds` and offers an update in a banner. Manual check:
|
it compares its commit to `VERSION` on `builds` and offers an update in a
|
||||||
**Settings → Updates**.
|
banner. Manual check: **Settings → Updates**.
|
||||||
|
|
||||||
#### Packages without the installer
|
#### 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`.
|
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)
|
#### Windows (MSYS2 UCRT64, no Visual Studio)
|
||||||
|
|
||||||
```cmd
|
```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
|
iconutil -c icns -o "$RES/AppIcon.icns" "$ICONSET" && rm -rf "$ICONSET" || true
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Ad-hoc sign the .app so Gatekeeper is less likely to kill dyld loads.
|
# Developer ID + notarization is what lets a downloaded app open without
|
||||||
codesign --force --deep --sign - "$APP" >/dev/null 2>&1 || true
|
# 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"
|
echo "==> Smoke-testing launch"
|
||||||
if [[ -n "${GITHUB_ACTIONS:-}${CI:-}" ]]; then
|
if [[ -n "${GITHUB_ACTIONS:-}${CI:-}" ]]; then
|
||||||
|
|
@ -270,6 +382,8 @@ else
|
||||||
echo " smoke test OK (process stayed up)"
|
echo " smoke test OK (process stayed up)"
|
||||||
fi
|
fi
|
||||||
|
|
||||||
|
notarize_bundle
|
||||||
|
|
||||||
echo "==> Zipping"
|
echo "==> Zipping"
|
||||||
rm -f "$ROOT/dist/$OUT_ZIP"
|
rm -f "$ROOT/dist/$OUT_ZIP"
|
||||||
(
|
(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue