Skip to content
Merged
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
143 changes: 143 additions & 0 deletions .github/workflows/snapshots-sign-check.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Manual check that the Apple secrets sign and notarize Plannotator Snapshots
# before a release depends on them. Run it from the Actions tab (Run workflow);
# it builds the app from the chosen ref, signs it with the Developer ID,
# notarizes and staples it, verifies all three, and uploads the zip. It makes
# no release and pushes nothing. The release job in release.yml does the same
# signing on tag pushes.
#
# Secrets, in the protected GitHub Environment `apple-signing`: APPLE_DEVELOPER_ID_P12 (base64 .p12), APPLE_DEVELOPER_ID_P12_PASSWORD,
# APPLE_DEVELOPER_ID_IDENTITY (name or SHA-1 of "Developer ID Application: …"),
# APPLE_NOTARY_KEY_P8 (base64 App Store Connect API key), APPLE_NOTARY_KEY_ID,
# APPLE_NOTARY_ISSUER_ID.
name: Snapshots sign check

on:
workflow_dispatch:
inputs:
ref:
description: Branch or tag of this repository to build
required: true
default: feat/shots-macos

permissions:
contents: read

concurrency:
group: snapshots-sign-check
cancel-in-progress: true

jobs:
sign-and-notarize:
runs-on: macos-latest
timeout-minutes: 45
# The Apple secrets live in this protected environment, not as repo secrets.
environment: apple-signing
steps:
- name: Accept only a branch or tag of this repository
id: ref
env:
REF: ${{ inputs.ref }}
REPO: ${{ github.repository }}
run: |
case "$REF" in
""|-*|*[!A-Za-z0-9._/-]*) echo "::error::Not a branch or tag name: $REF"; exit 1 ;;
esac
# ls-remote matches patterns against the END of ref names, so keep only
# exact names, and refuse a name that is both a branch and a tag.
lines=$(git ls-remote --heads --tags "https://github.com/$REPO.git" "refs/heads/$REF" "refs/tags/$REF")
branch=$(printf '%s\n' "$lines" | awk -v r="refs/heads/$REF" '$2 == r {print $1}')
tag=$(printf '%s\n' "$lines" | awk -v r="refs/tags/$REF^{}" '$2 == r {print $1}')
[ -n "$tag" ] || tag=$(printf '%s\n' "$lines" | awk -v r="refs/tags/$REF" '$2 == r {print $1}')
if [ -n "$branch" ] && [ -n "$tag" ]; then
echo "::error::$REF names both a branch and a tag of $REPO; refusing to guess"
exit 1
fi
sha="${branch:-$tag}"
if [ -z "$sha" ]; then
echo "::error::$REF is not a branch or tag of $REPO"
exit 1
fi
echo "Building $REF at $sha"
echo "sha=$sha" >> "$GITHUB_OUTPUT"

- uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0
with:
# The commit the first step resolved, so the ref cannot move in between.
ref: ${{ steps.ref.outputs.sha }}
persist-credentials: false

- name: Require the Apple secrets
env:
P12: ${{ secrets.APPLE_DEVELOPER_ID_P12 }}
P12_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_P12_PASSWORD }}
IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_IDENTITY }}
NOTARY_KEY_P8: ${{ secrets.APPLE_NOTARY_KEY_P8 }}
NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
REF: ${{ inputs.ref }}
run: |
missing=""
for name in P12 P12_PASSWORD IDENTITY NOTARY_KEY_P8 NOTARY_KEY_ID NOTARY_ISSUER; do
[ -n "${!name}" ] || missing="$missing $name"
done
if [ -n "$missing" ]; then
echo "::error::Missing Apple secrets:$missing"
exit 1
fi
test -x apps/snapshots-macos/build.sh || { echo "::error::apps/snapshots-macos/build.sh not found at $REF"; exit 1; }

- name: Import the Developer ID certificate
env:
P12: ${{ secrets.APPLE_DEVELOPER_ID_P12 }}
P12_PASSWORD: ${{ secrets.APPLE_DEVELOPER_ID_P12_PASSWORD }}
run: |
keychain="$RUNNER_TEMP/snapshots.keychain-db"
password="$(openssl rand -hex 16)"
trap 'rm -f "$RUNNER_TEMP/cert.p12"' EXIT
echo "$P12" | base64 --decode > "$RUNNER_TEMP/cert.p12"
security create-keychain -p "$password" "$keychain"
security set-keychain-settings -lut 3600 "$keychain"
security unlock-keychain -p "$password" "$keychain"
security import "$RUNNER_TEMP/cert.p12" -k "$keychain" -P "$P12_PASSWORD" -T /usr/bin/codesign
security set-key-partition-list -S apple-tool:,apple: -s -k "$password" "$keychain"
# The existing keychains, one argument each (word splitting intended).
# shellcheck disable=SC2046
security list-keychains -d user -s "$keychain" $(security list-keychains -d user | tr -d '"')
rm -f "$RUNNER_TEMP/cert.p12"
security find-identity -v -p codesigning "$keychain" | grep -c "Developer ID Application" | xargs -I{} echo "Developer ID identities in the keychain: {}"

- name: Build, sign and notarize Plannotator Snapshots.app
env:
SNAPSHOTS_SIGN_IDENTITY: ${{ secrets.APPLE_DEVELOPER_ID_IDENTITY }}
NOTARY_KEY_P8: ${{ secrets.APPLE_NOTARY_KEY_P8 }}
SNAPSHOTS_NOTARY_KEY_ID: ${{ secrets.APPLE_NOTARY_KEY_ID }}
SNAPSHOTS_NOTARY_ISSUER: ${{ secrets.APPLE_NOTARY_ISSUER_ID }}
run: |
trap 'rm -f "$RUNNER_TEMP/notary.p8"' EXIT
echo "$NOTARY_KEY_P8" | base64 --decode > "$RUNNER_TEMP/notary.p8"
export SNAPSHOTS_NOTARY_KEY="$RUNNER_TEMP/notary.p8"
VERSION=$(jq -r .version package.json)
SNAPSHOTS_BUILD="$VERSION.$GITHUB_RUN_NUMBER" apps/snapshots-macos/build.sh "$VERSION"
rm -f "$RUNNER_TEMP/notary.p8"

- name: Verify the signature, notarization and staple
run: |
app="apps/snapshots-macos/dist/Plannotator Snapshots.app"
codesign --verify --deep --strict --verbose=2 "$app"
codesign -dv --verbose=2 "$app" 2>&1 | grep -E "Identifier|Authority|TeamIdentifier|flags"
spctl --assess --type execute -vv "$app" 2>&1 | tee "$RUNNER_TEMP/spctl.txt"
grep -q "Notarized Developer ID" "$RUNNER_TEMP/spctl.txt" || { echo "::error::Gatekeeper does not see a notarized Developer ID app"; exit 1; }
xcrun stapler validate "$app"

- name: Remove the temporary keychain
if: always()
run: security delete-keychain "$RUNNER_TEMP/snapshots.keychain-db" 2>/dev/null || true

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: snapshots-signed
path: |
apps/snapshots-macos/dist/PlannotatorSnapshots.zip
apps/snapshots-macos/dist/build.txt
if-no-files-found: error
retention-days: 7
Loading