Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,9 +50,38 @@ jobs:
with:
python-version: "3.12"
cache: pip
# Import the Developer ID Application cert into a temporary keychain so
# build_macos.sh can codesign. No-op (unsigned build) when the secret is
# absent, so forks / unconfigured repos still produce a working .dmg.
- name: Import Developer ID certificate
env:
CERT_B64: ${{ secrets.MACOS_CERTIFICATE_P12_BASE64 }}
CERT_PASSWORD: ${{ secrets.MACOS_CERTIFICATE_PASSWORD }}
run: |
if [ -z "${CERT_B64}" ]; then
echo "MACOS_CERTIFICATE_P12_BASE64 not set — building unsigned."
exit 0
fi
KEYCHAIN="$RUNNER_TEMP/signing.keychain-db"
KEYCHAIN_PW="$(uuidgen)"
printf '%s' "${CERT_B64}" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security create-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN"
security set-keychain-settings -lut 21600 "$KEYCHAIN"
security unlock-keychain -p "$KEYCHAIN_PW" "$KEYCHAIN"
security import "$RUNNER_TEMP/cert.p12" -k "$KEYCHAIN" -P "${CERT_PASSWORD}" \
-T /usr/bin/codesign -T /usr/bin/security
security set-key-partition-list -S apple-tool:,apple:,codesign: \
-s -k "$KEYCHAIN_PW" "$KEYCHAIN" >/dev/null
security list-keychains -d user -s "$KEYCHAIN" \
$(security list-keychains -d user | sed 's/["[:space:]]//g')
rm -f "$RUNNER_TEMP/cert.p12"
- name: Build .app + .dmg
env:
MAKE_DMG: "1"
MACOS_SIGN_IDENTITY: ${{ secrets.MACOS_SIGN_IDENTITY }}
MACOS_NOTARY_APPLE_ID: ${{ secrets.MACOS_NOTARY_APPLE_ID }}
MACOS_NOTARY_TEAM_ID: ${{ secrets.MACOS_NOTARY_TEAM_ID }}
MACOS_NOTARY_PASSWORD: ${{ secrets.MACOS_NOTARY_PASSWORD }}
run: bash packaging/build_macos.sh
- uses: actions/upload-artifact@v4
with:
Expand Down
41 changes: 38 additions & 3 deletions docs/building.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,44 @@ bash packaging/build_macos.sh # dist/AutoPTZ.app
MAKE_DMG=1 bash packaging/build_macos.sh # + dist/AutoPTZ-<ver>-macos-arm64.dmg
```

Produces an unsigned, correctly-named bundle (`CFBundleName=AutoPTZ`). Signing +
notarization need your Apple Developer ID — the exact `codesign` / `notarytool` /
`stapler` commands are printed at the end of the script.
Produces a correctly-named bundle (`CFBundleName=AutoPTZ`). By default it is
**unsigned** and the script prints the manual `codesign` / `notarytool` / `stapler`
commands at the end.

### Signing + notarization (opt-in)

Set `MACOS_SIGN_IDENTITY` to your Developer ID and the script signs the `.app` (and,
with `MAKE_DMG=1`, the `.dmg`). Add notary credentials and it also notarizes +
staples:

```bash
export MACOS_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"
# notarize too (either a stored profile, or Apple-ID creds):
export MACOS_NOTARY_KEYCHAIN_PROFILE="AUTOPTZ_NOTARY" # from `notarytool store-credentials`
# …or…
export MACOS_NOTARY_APPLE_ID="you@example.com"
export MACOS_NOTARY_TEAM_ID="TEAMID"
export MACOS_NOTARY_PASSWORD="app-specific-password"
MAKE_DMG=1 bash packaging/build_macos.sh # signed + notarized + stapled .dmg
```

`security find-identity -v -p codesigning` lists your identity + Team ID. Entitlements
come from `packaging/entitlements.plist` (hardened runtime, required for notarization).

### Signed releases in CI

The [release workflow](../.github/workflows/release.yml) signs + notarizes the published
`.dmg` automatically once these repository secrets are set (Settings → Secrets and
variables → Actions); without them it still builds an unsigned `.dmg`:

| Secret | What it is |
| --- | --- |
| `MACOS_CERTIFICATE_P12_BASE64` | Your "Developer ID Application" cert exported as `.p12`, base64-encoded (`base64 -i cert.p12 \| pbcopy`). |
| `MACOS_CERTIFICATE_PASSWORD` | The password you set when exporting the `.p12`. |
| `MACOS_SIGN_IDENTITY` | `Developer ID Application: Your Name (TEAMID)`. |
| `MACOS_NOTARY_APPLE_ID` | Apple ID email for notarization. |
| `MACOS_NOTARY_TEAM_ID` | Your 10-character Team ID. |
| `MACOS_NOTARY_PASSWORD` | An app-specific password for that Apple ID. |

## Windows → `.exe` + installer

Expand Down
6 changes: 3 additions & 3 deletions docs/installation.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ Download the latest build for your OS from the
[Releases page](https://github.com/AutoPTZ/autoptz/releases):

- **macOS** — `AutoPTZ-<version>-macos-arm64.dmg`. Open it and drag **AutoPTZ**
to Applications. The builds are currently **unsigned**, so the first launch
needs: right-click the app → **Open** → **Open** (or System Settings → Privacy
& Security → *Open Anyway*).
to Applications. Signed + notarized releases open normally. If you build it
yourself unsigned, the first launch needs: right-click the app → **Open** →
**Open** (or System Settings → Privacy & Security → *Open Anyway*).
- **Windows** — `AutoPTZ-<version>-windows-x64-setup.exe`. Run it; it installs
Start-menu/desktop shortcuts and an uninstaller. SmartScreen may warn on the
unsigned installer — **More info → Run anyway**.
Expand Down
57 changes: 57 additions & 0 deletions packaging/build_macos.sh
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,38 @@ echo "==> AutoPTZ macOS build"
echo " repo: ${ROOT}"
echo " venv: ${VENV}"

# ── signing config (opt-in) ──────────────────────────────────────────────────
# Sign + notarize only when MACOS_SIGN_IDENTITY is set (CI sets it from repo
# secrets; locally, export it to sign with your Developer ID, e.g.
# export MACOS_SIGN_IDENTITY="Developer ID Application: Your Name (TEAMID)"
# Unset → unsigned build, and the manual sign/notarize commands are printed at
# the end instead.
SIGN_IDENTITY="${MACOS_SIGN_IDENTITY:-}"

# Notarize + staple a signed artifact (.app or .dmg) with whichever credentials
# are available; a no-op (with a note) when none are set.
notarize_and_staple() {
local target="$1"
if [[ -n "${MACOS_NOTARY_KEYCHAIN_PROFILE:-}" ]]; then
echo "==> Notarizing ${target} via keychain profile ${MACOS_NOTARY_KEYCHAIN_PROFILE}"
xcrun notarytool submit "${target}" \
--keychain-profile "${MACOS_NOTARY_KEYCHAIN_PROFILE}" --wait
elif [[ -n "${MACOS_NOTARY_APPLE_ID:-}" && -n "${MACOS_NOTARY_TEAM_ID:-}" \
&& -n "${MACOS_NOTARY_PASSWORD:-}" ]]; then
echo "==> Notarizing ${target} via Apple ID ${MACOS_NOTARY_APPLE_ID}"
xcrun notarytool submit "${target}" \
--apple-id "${MACOS_NOTARY_APPLE_ID}" \
--team-id "${MACOS_NOTARY_TEAM_ID}" \
--password "${MACOS_NOTARY_PASSWORD}" --wait
else
echo "==> Signed but NOT notarized (no notary credentials): ${target}"
echo " Set MACOS_NOTARY_APPLE_ID/TEAM_ID/PASSWORD or MACOS_NOTARY_KEYCHAIN_PROFILE."
return 0
fi
echo "==> Stapling ${target}"
xcrun stapler staple "${target}"
}

# ── 1. venv ─────────────────────────────────────────────────────────────────
if [[ ! -x "${PY}" ]]; then
echo "==> Creating venv at ${VENV}"
Expand Down Expand Up @@ -69,6 +101,18 @@ echo "==> Verifying CFBundleName (the app-name fix):"
/usr/libexec/PlistBuddy -c 'Print :CFBundleName' "${APP}/Contents/Info.plist"
plutil -lint "${APP}/Contents/Info.plist"

# ── 5b. sign the .app (opt-in) ───────────────────────────────────────────────
# Deep-sign with the hardened runtime + entitlements so the bundle is notarizable.
# Must happen BEFORE the .dmg is built so the dmg ships the signed app.
if [[ -n "${SIGN_IDENTITY}" ]]; then
echo "==> Codesigning ${APP} with: ${SIGN_IDENTITY}"
codesign --deep --force --options runtime --timestamp \
--entitlements packaging/entitlements.plist \
--sign "${SIGN_IDENTITY}" "${APP}"
codesign --verify --deep --strict --verbose=2 "${APP}"
echo "==> ${APP} signed"
fi

# ── 6. (optional) DMG ─────────────────────────────────────────────────────────
# MAKE_DMG=1 produces a compressed, versioned dmg with an /Applications symlink
# (drag-to-install). Dependency-free (uses macOS hdiutil). The release workflow
Expand All @@ -85,6 +129,19 @@ if [[ "${MAKE_DMG:-0}" == "1" ]]; then
-ov -format UDZO "${DMG}"
rm -rf "${STAGE}"
echo "==> Built ${DMG}"

# Sign + notarize + staple the distributed .dmg (opt-in).
if [[ -n "${SIGN_IDENTITY}" ]]; then
echo "==> Codesigning ${DMG}"
codesign --force --timestamp --sign "${SIGN_IDENTITY}" "${DMG}"
notarize_and_staple "${DMG}"
fi
fi

if [[ -n "${SIGN_IDENTITY}" ]]; then
echo
echo "==> Signed build complete (notarized if notary credentials were provided)."
exit 0
fi

cat <<'EOF'
Expand Down
2 changes: 1 addition & 1 deletion packaging/entitlements.plist
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
packaging/entitlements.plist : macOS hardened-runtime entitlements.

Applied at signing time via codesign (see packaging/build_macos.sh and
docs/v2-rework/11-packaging-and-distribution.md for the exact commands).
docs/building.md for the exact commands and the CI signing secrets).

Hardened runtime is REQUIRED for Apple notarization. The JIT and
unsigned-executable-memory entitlements below are what Python plus PySide6 / Qt
Expand Down
Loading