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
22 changes: 14 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -117,17 +117,23 @@ jobs:
if: steps.ver.outputs.skip != 'true'
run: swift test

- name: Build universal .app
- name: Build signed + notarized DMG (app with embedded extension)
if: steps.ver.outputs.skip != 'true'
env:
VERSION: ${{ steps.ver.outputs.value }}
run: ./scripts/build-app.sh

- name: Build DMG
if: steps.ver.outputs.skip != 'true'
env:
VERSION: ${{ steps.ver.outputs.value }}
run: ./scripts/make-dmg.sh
# Signing/notarization secrets (see docs/SIGNING-SECRETS.md). When unset, the script
# falls back to ad-hoc and skips notarization — the build still succeeds, but the
# released app's extension will not register on other Macs.
MACOS_CERT_P12_BASE64: ${{ secrets.MACOS_CERT_P12_BASE64 }}
MACOS_CERT_PASSWORD: ${{ secrets.MACOS_CERT_PASSWORD }}
KEYCHAIN_PASSWORD: ${{ secrets.KEYCHAIN_PASSWORD }}
NOTARY_APPLE_ID: ${{ secrets.NOTARY_APPLE_ID }}
NOTARY_TEAM_ID: ${{ secrets.NOTARY_TEAM_ID }}
NOTARY_PASSWORD: ${{ secrets.NOTARY_PASSWORD }}
run: |
sudo xcode-select -s "$(ls -d /Applications/Xcode*.app | sort -V | tail -1)"
brew list xcodegen >/dev/null 2>&1 || brew install xcodegen
./scripts/package-signed.sh

- name: Build plugin package (.appex / .radioplugin)
if: steps.ver.outputs.skip != 'true'
Expand Down
44 changes: 44 additions & 0 deletions docs/SIGNING-SECRETS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Release signing secrets (LP-700-App)

The release workflow embeds the LP-700 ExtensionKit extension into `LP-700-App.app`, signs it
with Developer ID, and notarizes the DMG — so an installed app registers its extension for the
Amateur Radio Suite to host (see the suite's `docs/EXTENSIONKIT.md`).

This only happens when these **GitHub Actions secrets** are set on this repo
(*Settings → Secrets and variables → Actions*). Without them, the release still builds but is
**ad-hoc signed and not notarized** — fine to smoke-test, but its extension won't register on
another Mac.

## Secrets

| Secret | What it is |
|--------|------------|
| `MACOS_CERT_P12_BASE64` | LP-700's **Developer ID Application** cert+key exported as a `.p12`, base64-encoded |
| `MACOS_CERT_PASSWORD` | the password set when exporting that `.p12` |
| `KEYCHAIN_PASSWORD` | any value — password for the throwaway CI keychain |
| `NOTARY_APPLE_ID` | Apple ID email used for notarization |
| `NOTARY_TEAM_ID` | the team id (`Y6FT52BKDA`) |
| `NOTARY_PASSWORD` | an **app-specific password** for that Apple ID ([appleid.apple.com](https://appleid.apple.com) → Sign-In & Security → App-Specific Passwords) |

> This repo uses its **own** Developer ID cert (one per app, all under the same team), so revoking
> it won't affect the other apps. In the fresh CI keychain only this cert is present, so signing
> selects it unambiguously by name.

## Exporting the `.p12` (one time, on the Mac that has the cert)

Keychain Access → **login** keychain → **My Certificates** → find LP-700's
`Developer ID Application` (its private key is named e.g. `ARS LP-700`) → right-click →
**Export…** → `.p12`, set a password (that's `MACOS_CERT_PASSWORD`). Then:

```sh
base64 -i LP-700.p12 | pbcopy # paste as MACOS_CERT_P12_BASE64
```

(Tip: the LP-700 cert's SHA-1 is `A59B8647CA9706C6E8CDBB461C9801CF715132C4` if you need to pick it
among several in Keychain Access.)

## Notarization alternative (App Store Connect API key)

Instead of Apple ID + app-specific password you may prefer an ASC API key (no 2FA/expiry issues).
If you want that, say so and the workflow can switch to `--key/--key-id/--issuer` with
`NOTARY_API_KEY` / `NOTARY_API_KEY_ID` / `NOTARY_API_ISSUER` secrets.
96 changes: 96 additions & 0 deletions scripts/package-signed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
#!/usr/bin/env bash
# Build LP-700-App.app with the LP700 ExtensionKit extension EMBEDDED and Developer-ID
# signed, then build the DMG and (when notary creds are present) notarize + staple it.
#
# This is what makes an installed app register its extension for the Amateur Radio Suite to
# host (see the suite's docs/EXTENSIONKIT.md). Used by the release workflow; also runnable
# locally.
#
# Signing is GATED on secrets — with none set it falls back to ad-hoc so the build still
# succeeds (but an ad-hoc app's extension will NOT register on another Mac).
#
# VERSION=0.1.8 ./scripts/package-signed.sh
#
# Env — signing (from CI secrets):
# MACOS_CERT_P12_BASE64 base64 of the LP-700 Developer ID Application .p12 (cert + key)
# MACOS_CERT_PASSWORD the .p12 export password
# KEYCHAIN_PASSWORD password for the temp keychain (any value)
# Env — notarization (optional; needs the signing cert too):
# NOTARY_APPLE_ID Apple ID email
# NOTARY_TEAM_ID team id (Y6FT52BKDA)
# NOTARY_PASSWORD app-specific password for that Apple ID
set -euo pipefail
cd "$(dirname "$0")/.."

VERSION="${VERSION:-0.0.0-dev}"
APP="dist/LP-700-App.app"
APPEX_NAME="LP700Extension.appex"
ENTITLEMENTS="Xcode/Extension/LP700.entitlements"
PROJECT="Xcode/LP700Plugin.xcodeproj"
SCHEME="LP700Extension"
TMP="${RUNNER_TEMP:-$(mktemp -d)}"
# SwiftPM's bare-repo cache trips a common global git setting under xcodebuild.
export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=safe.bareRepository GIT_CONFIG_VALUE_0=all

echo "==> Building standalone app (v$VERSION)"
VERSION="$VERSION" ./scripts/build-app.sh

echo "==> Building the extension (.appex)"
( cd Xcode && xcodegen generate >/dev/null )
DERIVED="$(mktemp -d)"
xcodebuild -project "$PROJECT" -scheme "$SCHEME" -configuration Release \
-destination 'platform=macOS' -derivedDataPath "$DERIVED" CODE_SIGNING_ALLOWED=NO build >/dev/null
APPEX="$(find "$DERIVED/Build/Products" -name "$APPEX_NAME" | head -1)"

echo "==> Embedding $APPEX_NAME under Contents/Extensions/"
mkdir -p "$APP/Contents/Extensions"
rm -rf "$APP/Contents/Extensions/$APPEX_NAME"
cp -R "$APPEX" "$APP/Contents/Extensions/"
rm -rf "$DERIVED"

# --- import the signing cert into a throwaway keychain (CI) -----------------------------
IDENTITY=""
if [ -n "${MACOS_CERT_P12_BASE64:-}" ]; then
echo "==> Importing Developer ID certificate into a temporary keychain"
KC="$TMP/ars-signing.keychain-db"
KCPW="${KEYCHAIN_PASSWORD:-ars-ci-temp}"
security create-keychain -p "$KCPW" "$KC"
security set-keychain-settings -lut 21600 "$KC"
security unlock-keychain -p "$KCPW" "$KC"
echo "$MACOS_CERT_P12_BASE64" | base64 --decode > "$TMP/cert.p12"
security import "$TMP/cert.p12" -k "$KC" -P "${MACOS_CERT_PASSWORD:-}" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$KCPW" "$KC" >/dev/null
# Make the temp keychain searchable (keep the existing ones too).
security list-keychains -d user -s "$KC" $(security list-keychains -d user | tr -d '"')
rm -f "$TMP/cert.p12"
# In the fresh keychain there is exactly one Developer ID identity, so name-selection is unambiguous.
IDENTITY="$(security find-identity -v -p codesigning "$KC" | sed -n 's/.*"\(Developer ID Application: .*\)"/\1/p' | head -1)"
fi

# --- sign inside-out (extension first, then the app) ------------------------------------
if [ -n "$IDENTITY" ]; then
echo "==> Signing with: $IDENTITY"
codesign --force -s "$IDENTITY" -o runtime --timestamp \
--entitlements "$ENTITLEMENTS" "$APP/Contents/Extensions/$APPEX_NAME"
codesign --force -s "$IDENTITY" -o runtime --timestamp "$APP"
else
echo "==> WARNING: no MACOS_CERT_P12_BASE64 — ad-hoc signing (extension will NOT register on other Macs)"
codesign --force -s - --deep "$APP"
fi
codesign --verify --strict --verbose=2 "$APP"

echo "==> Building DMG"
VERSION="$VERSION" ./scripts/make-dmg.sh
DMG="dist/LP-700-App-${VERSION}.dmg"

# --- notarize + staple the DMG ----------------------------------------------------------
if [ -n "$IDENTITY" ] && [ -n "${NOTARY_APPLE_ID:-}" ] && [ -n "${NOTARY_PASSWORD:-}" ]; then
echo "==> Notarizing $DMG (a few minutes)…"
xcrun notarytool submit "$DMG" \
--apple-id "$NOTARY_APPLE_ID" --team-id "${NOTARY_TEAM_ID:-}" --password "$NOTARY_PASSWORD" --wait
xcrun stapler staple "$DMG"
echo "==> Notarized + stapled."
else
echo "==> Skipping notarization (no NOTARY_* secrets) — DMG signed but not notarized."
fi
echo "==> Done: $DMG"