Skip to content

Use spaced Elapse Recorder app wrapper #1

Use spaced Elapse Recorder app wrapper

Use spaced Elapse Recorder app wrapper #1

Workflow file for this run

name: Release
on:
push:
tags:
- "v*"
permissions:
contents: write
concurrency:
group: release-${{ github.ref }}
cancel-in-progress: false
# Required repository configuration before pushing a v* tag:
# Variable:
# SPARKLE_PUBLIC_ED_KEY: output from Sparkle's generate_keys tool.
# Secret:
# SPARKLE_PRIVATE_ED_KEY: contents of the file exported by generate_keys -x.
# The public and private Sparkle keys must be generated as one pair.
# Releases are intentionally Apple-unsigned and are authenticated by Sparkle EdDSA.
jobs:
release:
runs-on: macos-15
env:
SPARKLE_PUBLIC_ED_KEY: ${{ vars.SPARKLE_PUBLIC_ED_KEY }}
SPARKLE_PRIVATE_ED_KEY: ${{ secrets.SPARKLE_PRIVATE_ED_KEY }}
GH_TOKEN: ${{ github.token }}
steps:
- uses: actions/checkout@v4
- name: Validate release configuration
shell: bash
run: |
set -euo pipefail
missing=()
for name in SPARKLE_PUBLIC_ED_KEY SPARKLE_PRIVATE_ED_KEY; do
if [[ -z "${!name:-}" ]]; then
missing+=("$name")
fi
done
if (( ${#missing[@]} )); then
printf 'Missing release configuration: %s\n' "${missing[*]}" >&2
exit 1
fi
if [[ "$SPARKLE_PUBLIC_ED_KEY" == "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=" ]]; then
echo "SPARKLE_PUBLIC_ED_KEY must not use the local-development placeholder." >&2
exit 1
fi
decoded_length="$(printf '%s' "$SPARKLE_PUBLIC_ED_KEY" | /usr/bin/base64 -D | wc -c | tr -d ' ')"
[[ "$decoded_length" == "32" ]] || {
echo "SPARKLE_PUBLIC_ED_KEY must decode to 32 bytes, found $decoded_length." >&2
exit 1
}
- name: Verify tag matches app version
shell: bash
run: |
set -euo pipefail
version="$(sed -n 's/.*MARKETING_VERSION = \([^;]*\);/\1/p' ElapseRecorder.xcodeproj/project.pbxproj | sort -u)"
build="$(sed -n 's/.*CURRENT_PROJECT_VERSION = \([^;]*\);/\1/p' ElapseRecorder.xcodeproj/project.pbxproj | sort -u)"
tag_version="${GITHUB_REF_NAME#v}"
[[ "$version" == "$tag_version" ]] || {
echo "Tag $GITHUB_REF_NAME does not match MARKETING_VERSION $version." >&2
exit 1
}
[[ "$build" =~ ^[0-9]+$ ]] || {
echo "CURRENT_PROJECT_VERSION must be one integer, found: $build" >&2
exit 1
}
echo "APP_VERSION=$version" >> "$GITHUB_ENV"
echo "APP_BUILD=$build" >> "$GITHUB_ENV"
- name: Build Apple-unsigned application
shell: bash
run: |
set -euo pipefail
mkdir -p build
env GIT_CONFIG_COUNT=1 \
GIT_CONFIG_KEY_0=safe.bareRepository \
GIT_CONFIG_VALUE_0=all \
xcodebuild build \
-project ElapseRecorder.xcodeproj \
-scheme ElapseRecorder \
-configuration Release \
-destination 'generic/platform=macOS' \
-derivedDataPath "$PWD/build/DerivedData" \
-clonedSourcePackagesDirPath "$PWD/build/SourcePackages" \
-skipMacroValidation \
-skipPackagePluginValidation \
-onlyUsePackageVersionsFromResolvedFile \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO \
CODE_SIGN_INJECT_BASE_ENTITLEMENTS=NO \
ENABLE_HARDENED_RUNTIME=NO \
SPARKLE_PUBLIC_ED_KEY="$SPARKLE_PUBLIC_ED_KEY"
app="$PWD/build/DerivedData/Build/Products/Release/Elapse Recorder.app"
[[ -d "$app" ]] || {
echo "Unsigned app was not produced at $app" >&2
exit 1
}
# Mach-O binaries may receive an automatic linker ad-hoc signature even when Xcode
# code signing is disabled. Remove it so Sparkle uses EdDSA as the sole trust anchor.
codesign --remove-signature "$app"
if codesign -dv "$app" >/dev/null 2>&1; then
echo "Expected an Apple-unsigned host app, but a code signature is present." >&2
exit 1
fi
embedded_key="$(/usr/libexec/PlistBuddy -c 'Print SUPublicEDKey' "$app/Contents/Info.plist")"
[[ "$embedded_key" == "$SPARKLE_PUBLIC_ED_KEY" ]] || {
echo "Built app does not contain the configured Sparkle public key." >&2
exit 1
}
embedded_version="$(/usr/libexec/PlistBuddy -c 'Print CFBundleShortVersionString' "$app/Contents/Info.plist")"
embedded_build="$(/usr/libexec/PlistBuddy -c 'Print CFBundleVersion' "$app/Contents/Info.plist")"
[[ "$embedded_version" == "$APP_VERSION" && "$embedded_build" == "$APP_BUILD" ]] || {
echo "Built app version $embedded_version ($embedded_build) does not match $APP_VERSION ($APP_BUILD)." >&2
exit 1
}
echo "UNSIGNED_APP=$app" >> "$GITHUB_ENV"
- name: Package Sparkle update
shell: bash
run: |
set -euo pipefail
mkdir -p build/updates
archive="build/updates/ElapseRecorder-${APP_VERSION}.zip"
ditto -c -k --sequesterRsrc --keepParent "$UNSIGNED_APP" "$archive"
echo "UPDATE_ARCHIVE=$archive" >> "$GITHUB_ENV"
- name: Create draft GitHub release
shell: bash
run: |
set -euo pipefail
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
gh release upload "$GITHUB_REF_NAME" "$UPDATE_ARCHIVE" --clobber
else
gh release create "$GITHUB_REF_NAME" "$UPDATE_ARCHIVE" \
--draft \
--generate-notes \
--title "ElapseRecorder ${APP_VERSION}"
fi
gh release view "$GITHUB_REF_NAME" --json body --jq .body \
> "build/updates/ElapseRecorder-${APP_VERSION}.md"
- name: Generate signed Sparkle appcast
shell: bash
run: |
set -euo pipefail
tool="$PWD/build/SourcePackages/artifacts/sparkle/Sparkle/bin/generate_appcast"
[[ -x "$tool" ]] || {
echo "Sparkle generate_appcast tool was not resolved at $tool" >&2
exit 1
}
printf '%s' "$SPARKLE_PRIVATE_ED_KEY" | "$tool" \
--ed-key-file - \
--download-url-prefix "https://github.com/deltaiota/ElapseRecorder/releases/download/${GITHUB_REF_NAME}/" \
--embed-release-notes \
--link "https://github.com/deltaiota/ElapseRecorder" \
--maximum-versions 3 \
--maximum-deltas 0 \
build/updates
test -s build/updates/appcast.xml
xmllint --noout build/updates/appcast.xml
grep -q 'sparkle:edSignature=' build/updates/appcast.xml
- name: Publish release and appcast
shell: bash
run: |
set -euo pipefail
gh release upload "$GITHUB_REF_NAME" build/updates/appcast.xml --clobber
gh release edit "$GITHUB_REF_NAME" --draft=false