Files
yattee/scripts/sparkle/README.md
Arkadiusz Fal 29900b758d Point Sparkle feed at dl.yattee.stream custom domain
Bake https://dl.yattee.stream/appcast.xml into SUFeedURL and align the
appcast template and Sparkle setup notes so the gh-pages branch (served
under the custom Cloudflare-CNAMEd domain) is the single place to look
up the feed. Domain is reserved as a generic distribution surface for
Sparkle today and AltStore / other channels later.
2026-04-23 05:50:52 +02:00

3.1 KiB

Sparkle appcast pipeline

This directory contains the scripts and templates that sign Sparkle updates and maintain appcast.xml on the gh-pages branch. The feed is served at https://dl.yattee.stream/appcast.xml and consumed by Sparkle inside the Developer ID build of Yattee (see Yattee/Services/Updates/SparkleUpdater.swift).

One-time setup

  1. Generate the EdDSA signing key on a trusted machine (once, ever):

    # Location inside your Xcode DerivedData, after building Yattee once.
    SPARKLE=~/Library/Developer/Xcode/DerivedData/Yattee-*/SourcePackages/artifacts/sparkle/Sparkle/bin
    $SPARKLE/generate_keys                    # creates the keypair in Keychain
    $SPARKLE/generate_keys -p                 # prints the public key; also visible in Info.plist (SUPublicEDKey)
    
  2. Export the private key for CI:

    $SPARKLE/generate_keys -x /tmp/sparkle_ed_private.key
    cat /tmp/sparkle_ed_private.key           # copy the single-line base64 contents
    rm /tmp/sparkle_ed_private.key            # DO NOT commit, DO NOT leave on disk
    

    Paste the contents into GitHub → Settings → Secrets and variables → Actions → New repository secret named SPARKLE_ED_PRIVATE_KEY.

  3. Enable GitHub Pages: repository Settings → Pages → Source: Deploy from a branch → Branch: gh-pages / (root). The first publish_appcast workflow run creates the branch with the seed appcast.xml.

  4. Confirm the public key matches Info.plist: Yattee/Info.plist's SUPublicEDKey value must equal the output of generate_keys -p. Any mismatch and Sparkle refuses to install updates signed with the CI's private key.

Per-release flow (automated)

The .github/workflows/release.yml publish_appcast job:

  1. Downloads the mac-notarized-build artifact (contains both .zip and .dmg).
  2. Locates Sparkle's sign_update binary from the resolved SPM dependency.
  3. Writes SPARKLE_ED_PRIVATE_KEY to a tempfile (scrubbed in always() step).
  4. Invokes ./scripts/sparkle/update_appcast.rb which:
    • Signs the .zipsparkle:edSignature + length.
    • Prepends a new <item> into gh-pages/appcast.xml (de-duplicating if the same version+build already exists).
    • Tags beta items with <sparkle:channel>beta</>; stable items are untagged (Sparkle's default).
  5. Commits and pushes gh-pages.

The release_channel workflow input (beta | stable, default beta) controls:

  • <sparkle:channel> in the appcast item
  • GitHub Release prerelease flag
  • Release tag shape: 2.0.1-beta.261 vs 2.0.1-261

Manual ad-hoc signing

./scripts/sparkle/update_appcast.rb \
  --zip path/to/Yattee-2.0.1-macOS.zip \
  --version 2.0.1 \
  --build 261 \
  --channel beta \
  --tag 2.0.1-beta.261 \
  --sign-update-bin ~/Library/Developer/Xcode/DerivedData/Yattee-*/SourcePackages/artifacts/sparkle/Sparkle/bin/sign_update \
  --ed-key-file /tmp/sparkle_ed_private.key \
  --appcast appcast.xml \
  --repo yattee/yattee

Verification

# After a release:
curl -sSL https://dl.yattee.stream/appcast.xml | xmllint --noout -