95 lines
4.5 KiB
Bash
Executable File
95 lines
4.5 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
#
|
|
# Generate (or refresh) the Sparkle appcast for a channel from the DMGs in dist/.
|
|
#
|
|
# scripts/generate-appcast.sh {canary|beta|rc|stable}
|
|
#
|
|
# Sparkle's `generate_appcast` scans a folder of updates (our signed+notarized DMGs),
|
|
# writes an appcast XML describing them, and EdDSA-signs each entry with the private
|
|
# update key stored in your login keychain (created once by Sparkle's `generate_keys`
|
|
# — see signing/README.md). The enclosure (DMG) URLs are rewritten to NUCLEIC_FEED_BASE/<channel>/
|
|
# (the R2 host, channel-foldered for tidy download links), so the emitted appcast uploads as-is.
|
|
#
|
|
# Output: dist/appcast-<channel>.xml (upload it + the channel's DMG(s) to the R2 bucket
|
|
# whose download base is NUCLEIC_FEED_BASE — scripts/upload-r2.sh does this).
|
|
#
|
|
# Env:
|
|
# NUCLEIC_FEED_BASE download base for enclosure URLs
|
|
# (default: https://updates.nucleic.blakeslee.xyz)
|
|
# GENERATE_APPCAST path to the generate_appcast tool (default: auto-detect)
|
|
|
|
set -euo pipefail
|
|
|
|
CHANNEL="${1:?usage: generate-appcast.sh (canary|beta|rc|stable)}"
|
|
case "$CHANNEL" in
|
|
canary) SLUG="Nucleic-Canary" ;;
|
|
beta) SLUG="Nucleic-Beta" ;;
|
|
rc) SLUG="Nucleic-RC" ;;
|
|
stable) SLUG="Nucleic" ;;
|
|
*) echo "usage: $0 {canary|beta|rc|stable}" >&2; exit 2 ;;
|
|
esac
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
FEED_BASE="${NUCLEIC_FEED_BASE:-https://updates.nucleic.blakeslee.xyz}"
|
|
|
|
# Locate generate_appcast: explicit env, then PATH, then the Sparkle SwiftPM artifact.
|
|
GEN="${GENERATE_APPCAST:-}"
|
|
[ -z "$GEN" ] && GEN="$(command -v generate_appcast || true)"
|
|
if [ -z "$GEN" ]; then
|
|
GEN="$(find "$ROOT/.build" -type f -name generate_appcast -perm -u+x 2>/dev/null | head -1 || true)"
|
|
fi
|
|
if [ -z "$GEN" ] || [ ! -x "$GEN" ]; then
|
|
cat >&2 <<EOF
|
|
generate-appcast: 'generate_appcast' tool not found.
|
|
|
|
It ships with Sparkle. Get it via either:
|
|
• a normal build that resolves Sparkle, then it's under .build/ (auto-detected), or
|
|
• the Sparkle release tarball / 'brew install --cask sparkle' (then set GENERATE_APPCAST).
|
|
EOF
|
|
exit 1
|
|
fi
|
|
|
|
# Stage just this channel's DMG(s) so generate_appcast doesn't mix channels into one feed.
|
|
STAGE="$ROOT/dist/appcast/$CHANNEL"
|
|
rm -rf "$STAGE"; mkdir -p "$STAGE"
|
|
shopt -s nullglob
|
|
found=0
|
|
for dmg in "$ROOT/dist/${SLUG}-"*.dmg; do
|
|
cp "$dmg" "$STAGE/"; found=$((found + 1))
|
|
done
|
|
shopt -u nullglob
|
|
[ "$found" -gt 0 ] || { echo "generate-appcast: no ${SLUG}-*.dmg in dist/ — build a release first" >&2; exit 1; }
|
|
|
|
echo "▸ Generating appcast for $CHANNEL from $found DMG(s)"
|
|
# Enclosure URLs land under the channel folder → updates.nucleic.blakeslee.xyz/<channel>/<dmg>
|
|
# (scripts/upload-r2.sh uploads the DMGs to that same key prefix). Trailing slash is required.
|
|
#
|
|
# generate_appcast mounts each DMG to read its version/build metadata, and Sparkle's compiled tool
|
|
# still does that through the deprecated `hdiutil attach -mountpoint -nobrowse` path — printing a
|
|
# cosmetic "…is deprecated, use diskutil image attach…" warning we can't silence at the source (it
|
|
# lives inside the Sparkle binary; fixing it is upstream's job). Filter just those hdiutil
|
|
# deprecation lines out of its stderr; all other output and the tool's exit status pass through
|
|
# unchanged. fd 3 carries stdout past the grep; `|| true` stops grep-filters-everything from
|
|
# tripping pipefail, while a real generate_appcast failure still propagates (pipefail).
|
|
{ "$GEN" --download-url-prefix "$FEED_BASE/$CHANNEL/" "$STAGE" 2>&1 1>&3 \
|
|
| { grep -vE '^hdiutil: WARNING:.*deprecated' >&2 || true; } ; } 3>&1
|
|
|
|
OUT="$ROOT/dist/appcast-$CHANNEL.xml"
|
|
# generate_appcast names its output after the app's embedded SUFeedURL basename — here
|
|
# appcast-<channel>.xml (SUFeedURL = .../appcast-<channel>.xml), NOT the bare appcast.xml it
|
|
# defaults to when no feed URL is embedded. Prefer that exact name, else take whichever single
|
|
# appcast XML it wrote, so this keeps working regardless of the feed-URL naming.
|
|
if [ -f "$STAGE/appcast-$CHANNEL.xml" ]; then
|
|
GENERATED="$STAGE/appcast-$CHANNEL.xml"
|
|
else
|
|
GENERATED="$(find "$STAGE" -maxdepth 1 -name 'appcast*.xml' | head -1)"
|
|
fi
|
|
[ -n "${GENERATED:-}" ] && [ -f "$GENERATED" ] || {
|
|
echo "generate-appcast: generate_appcast produced no appcast XML in $STAGE" >&2; exit 1; }
|
|
cp "$GENERATED" "$OUT"
|
|
echo "✓ $OUT"
|
|
echo " Upload to the R2 host '$FEED_BASE' (scripts/upload-r2.sh $CHANNEL):"
|
|
echo " • $(basename "$OUT") → $FEED_BASE/appcast-$CHANNEL.xml"
|
|
echo " • each ${SLUG}-*.dmg referenced → $FEED_BASE/$CHANNEL/${SLUG}-<ver>.dmg"
|
|
echo " (the embedded SUFeedURL points the app at $FEED_BASE/appcast-$CHANNEL.xml)"
|