diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e2c5a09..659a217 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -96,6 +96,27 @@ jobs: echo "::warning::Gatekeeper rejected this app because the release is ad-hoc signed and not notarized." fi + - name: Generate Sparkle appcast + shell: bash + env: + SPARKLE_PRIVATE_KEY: ${{ secrets.SPARKLE_PRIVATE_KEY }} + run: | + set -euo pipefail + + if [ -z "$SPARKLE_PRIVATE_KEY" ]; then + echo "Missing required GitHub Actions secret: SPARKLE_PRIVATE_KEY" >&2 + exit 1 + fi + + ./scripts/update-appcast.sh \ + --dmg "${{ steps.dmg.outputs.path }}" \ + --output "$RUNNER_TEMP/appcast.xml" \ + --tag "${{ steps.release.outputs.tag }}" + + grep -F 'sparkle:edSignature=' "$RUNNER_TEMP/appcast.xml" + grep -F 'releases/download/${{ steps.release.outputs.tag }}/${{ steps.dmg.outputs.name }}' \ + "$RUNNER_TEMP/appcast.xml" + - name: Create GitHub Release run: npx --yes genereleaselog@latest --output false --assets "${{ steps.dmg.outputs.path }}" env: @@ -110,3 +131,23 @@ jobs: | grep -Fx "${{ steps.dmg.outputs.name }}" env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish Sparkle appcast + shell: bash + run: | + set -euo pipefail + + git fetch origin main + git checkout -B main origin/main + cp "$RUNNER_TEMP/appcast.xml" appcast.xml + + if git diff --quiet -- appcast.xml; then + echo "appcast.xml is already current." + exit 0 + fi + + git config user.name "github-actions[bot]" + git config user.email "41898282+github-actions[bot]@users.noreply.github.com" + git add appcast.xml + git commit -m "chore: update appcast for ${{ steps.release.outputs.tag }}" + git push origin HEAD:main diff --git a/scripts/update-appcast.sh b/scripts/update-appcast.sh index bb128dc..f45240d 100755 --- a/scripts/update-appcast.sh +++ b/scripts/update-appcast.sh @@ -9,6 +9,7 @@ APP_NAME="${APP_NAME:-AgentBar}" DIST_DIR="${DIST_DIR:-$REPO_ROOT/dist}" APPCAST_PATH="${APPCAST_PATH:-$REPO_ROOT/appcast.xml}" SPARKLE_GENERATE_APPCAST="${SPARKLE_GENERATE_APPCAST:-generate_appcast}" +SPARKLE_PRIVATE_KEY="${SPARKLE_PRIVATE_KEY:-}" VERSION="1.0.0" BUILD_NUMBER="1" @@ -35,7 +36,8 @@ Options: Environment overrides: APP_NAME, DIST_DIR, VERSION_ENV_FILE, DMG_PATH, APPCAST_PATH, - RELEASE_TAG, DOWNLOAD_URL_PREFIX, SPARKLE_GENERATE_APPCAST + RELEASE_TAG, DOWNLOAD_URL_PREFIX, SPARKLE_GENERATE_APPCAST, + SPARKLE_PRIVATE_KEY EOF } @@ -113,9 +115,19 @@ trap cleanup EXIT HUP INT TERM cp "$DMG_PATH" "$WORK_DIR/" -"$SPARKLE_GENERATE_APPCAST" \ - --download-url-prefix "$DOWNLOAD_URL_PREFIX" \ - "$WORK_DIR" +if [ -n "$SPARKLE_PRIVATE_KEY" ]; then + # Sparkle recommends passing the private key over standard input in CI so + # it is not exposed in the process arguments or written to disk. + printf '%s' "$SPARKLE_PRIVATE_KEY" \ + | "$SPARKLE_GENERATE_APPCAST" \ + --ed-key-file - \ + --download-url-prefix "$DOWNLOAD_URL_PREFIX" \ + "$WORK_DIR" +else + "$SPARKLE_GENERATE_APPCAST" \ + --download-url-prefix "$DOWNLOAD_URL_PREFIX" \ + "$WORK_DIR" +fi if [ ! -f "$WORK_DIR/appcast.xml" ]; then echo "generate_appcast did not create appcast.xml" >&2