build: reuse shared mac release tooling
Some checks failed
macOS CI / SwiftLint (core + CLI) (push) Blocked by required conditions
macOS CI / PeekabooCore build & tests (push) Waiting to run
macOS CI / Peekaboo CLI build & tests (push) Blocked by required conditions
macOS CI / Tachikoma build & tests (push) Blocked by required conditions
macOS CI / Build macOS apps (Peekaboo + Inspector) (push) Blocked by required conditions
Website (GitHub Pages) / build (push) Has been cancelled
Website (GitHub Pages) / deploy (push) Has been cancelled

This commit is contained in:
Peter Steinberger 2026-05-21 22:00:27 +01:00
parent a9725f89e6
commit 3089e05110
No known key found for this signature in database
4 changed files with 157 additions and 9 deletions

21
.mac-release.env Normal file
View File

@ -0,0 +1,21 @@
MAC_RELEASE_APP_NAME=Peekaboo
MAC_RELEASE_REPO=openclaw/Peekaboo
MAC_RELEASE_BUNDLE_ID=boo.peekaboo.mac
MAC_RELEASE_VERSION_FILE=/dev/null
MARKETING_VERSION=$(node -p "require('./package.json').version")
MAC_RELEASE_APPCAST=appcast.xml
MAC_RELEASE_INFO_PLIST=Apps/Mac/Peekaboo/Info.plist
MAC_RELEASE_SUPUBLIC_ED_KEY=AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj/Qs67XI=
MAC_RELEASE_SIGNING_KEY_FILE='$HOME/Library/CloudStorage/Dropbox/Backup/Sparkle/sparkle-private-key-OBSOLETE-not-for-BlackBar-publickey-AGCY8w5vHirVfGGDGc8Szc5iuOqupZSh9pMj_Qs67XI-2026-05-21.txt'
MAC_RELEASE_APP_ZIP='${RELEASE_DIR:-release}/Peekaboo-${MARKETING_VERSION}.app.zip'
MAC_RELEASE_ARTIFACT_PREFIX='Peekaboo-'
MAC_RELEASE_REQUIRE_DSYM=0
MAC_RELEASE_FEED_URL='https://raw.githubusercontent.com/openclaw/Peekaboo/main/appcast.xml'
MAC_RELEASE_DOWNLOAD_URL_PREFIX='https://github.com/openclaw/Peekaboo/releases/download/v${MARKETING_VERSION}/'
MAC_RELEASE_PRECHECK='node scripts/prepare-release.js'
MAC_RELEASE_PACKAGE_CMD='scripts/release-macos-app.sh --no-appcast'
MAC_RELEASE_TAG_SIGNED=0
MAC_RELEASE_TAG_FORCE=0
MAC_RELEASE_TAG_ANNOTATED=0

View File

@ -48,7 +48,7 @@ The main release script runs this step automatically. Use this section only to d
- [ ] Ensure `Apps/Mac/Peekaboo/Info.plist` has `SUFeedURL`, `SUPublicEDKey`, and `SUEnableAutomaticChecks` set (defaults are already wired to the repo appcast).
- [ ] Ensure release credentials are available:
- Developer ID Application certificate in the login keychain.
- Sparkle EdDSA private key at `~/Library/CloudStorage/Dropbox/Backup/Sparkle/sparkle-private-key-KEEP-SECURE.txt` or `SPARKLE_PRIVATE_KEY_FILE`.
- Sparkle EdDSA private key from `.mac-release.env` or `SPARKLE_PRIVATE_KEY_FILE`.
- Notarization credentials via `NOTARYTOOL_PROFILE` or `APP_STORE_CONNECT_KEY_ID`, `APP_STORE_CONNECT_ISSUER_ID`, and `APP_STORE_CONNECT_API_KEY_P8`.
- [ ] Optional local dry run before touching Apple/GitHub/appcast:
- `pnpm run release:mac-app -- --dry-run`

24
scripts/mac-release Executable file
View File

@ -0,0 +1,24 @@
#!/usr/bin/env bash
set -euo pipefail
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
ROOT=$(cd "$SCRIPT_DIR/.." && pwd)
cd "$ROOT"
if [[ -n "${MAC_RELEASE_TOOL:-}" ]]; then
exec "$MAC_RELEASE_TOOL" "$@"
fi
for candidate in \
"$ROOT/../agent-scripts/skills/mac-app-release/scripts/mac-release" \
"$HOME/Projects/agent-scripts/skills/mac-app-release/scripts/mac-release"; do
if [[ -x "$candidate" ]]; then
exec "$candidate" "$@"
fi
done
cat >&2 <<'EOF'
Missing mac-release helper.
Clone agent-scripts next to this repo or set MAC_RELEASE_TOOL=/path/to/mac-release.
EOF
exit 127

View File

@ -4,21 +4,103 @@
set -euo pipefail
ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
ROOT="${ROOT:-$ROOT_DIR}"
MAC_RELEASE_MANIFEST="${MAC_RELEASE_MANIFEST:-$ROOT_DIR/.mac-release.env}"
MAC_RELEASE_MANIFEST_LOADED=false
if [[ -f "$MAC_RELEASE_MANIFEST" ]]; then
pushd "$ROOT_DIR" >/dev/null
# shellcheck source=/Users/steipete/Projects/Peekaboo/.mac-release.env
source "$MAC_RELEASE_MANIFEST"
popd >/dev/null
MAC_RELEASE_MANIFEST_LOADED=true
fi
MAC_RELEASE_HELPER_LOADED=false
for candidate in \
"${MAC_RELEASE_LIB:-}" \
"$ROOT_DIR/../agent-scripts/skills/mac-app-release/scripts/lib/mac_release.sh" \
"$HOME/Projects/agent-scripts/skills/mac-app-release/scripts/lib/mac_release.sh"; do
if [[ -n "$candidate" && -f "$candidate" ]]; then
# shellcheck source=/Users/steipete/Projects/agent-scripts/skills/mac-app-release/scripts/lib/mac_release.sh
source "$candidate"
MAC_RELEASE_HELPER_LOADED=true
break
fi
done
if [[ "$MAC_RELEASE_HELPER_LOADED" == true && "$MAC_RELEASE_MANIFEST_LOADED" == true ]]; then
mac_release_load
else
MAC_RELEASE_HELPER_LOADED=false
fi
version_to_build_number() {
if declare -F mac_release_build_number >/dev/null; then
mac_release_build_number "$1"
return
fi
local version=${1:?"version required"} core prerelease major minor patch suffix prerelease_label prerelease_number
core=${version%%-*}
prerelease=
if [[ "$version" == *-* ]]; then
prerelease=${version#*-}
fi
IFS=. read -r major minor patch <<<"$core"
if [[ ! "$major" =~ ^[0-9]+$ || ! "$minor" =~ ^[0-9]+$ || ! "$patch" =~ ^[0-9]+$ ]]; then
echo "ERROR: Version must be numeric semver: $version" >&2
exit 1
fi
if ((10#$minor > 99 || 10#$patch > 99)); then
echo "ERROR: Minor and patch versions must be <= 99 for generated build numbers: $version" >&2
exit 1
fi
suffix=99
if [[ -n "$prerelease" ]]; then
prerelease_label=${prerelease%%.*}
prerelease_label=${prerelease_label%%-*}
prerelease_label=${prerelease_label%%[0-9]*}
prerelease_label=${prerelease_label,,}
if [[ "$prerelease" =~ ([0-9]+)$ ]]; then
prerelease_number=${BASH_REMATCH[1]}
else
prerelease_number=1
fi
if ((10#$prerelease_number < 1 || 10#$prerelease_number > 29)); then
echo "ERROR: Prerelease number must be 1..29 for generated build numbers: $version" >&2
exit 1
fi
case "$prerelease_label" in
alpha | a) suffix=$((10#$prerelease_number)) ;;
beta | b) suffix=$((30 + 10#$prerelease_number)) ;;
rc) suffix=$((60 + 10#$prerelease_number)) ;;
*)
echo "ERROR: Prerelease label must be alpha, beta, or rc for generated build numbers: $version" >&2
exit 1
;;
esac
fi
printf '%d\n' $((((10#$major * 100 + 10#$minor) * 100 + 10#$patch) * 100 + 10#$suffix))
}
MARKETING_VERSION="${MARKETING_VERSION:-$(node -p "require('$ROOT_DIR/package.json').version")}"
BUILD_NUMBER="${BUILD_NUMBER:-$(version_to_build_number "$MARKETING_VERSION")}"
WORKSPACE="${WORKSPACE:-$ROOT_DIR/Apps/Peekaboo.xcworkspace}"
SCHEME="${SCHEME:-Peekaboo}"
CONFIGURATION="${CONFIGURATION:-Release}"
DESTINATION="${DESTINATION:-platform=macOS,arch=arm64}"
DERIVED_DATA_PATH="${DERIVED_DATA_PATH:-/tmp/peekaboo-macos-app-release}"
RELEASE_DIR="${RELEASE_DIR:-$ROOT_DIR/release}"
APP_NAME="${APP_NAME:-Peekaboo}"
APP_NAME="${APP_NAME:-${MAC_RELEASE_APP_NAME:-Peekaboo}}"
SIGN_IDENTITY="${SIGN_IDENTITY:-Developer ID Application: Peter Steinberger (Y5PE65HELJ)}"
SPARKLE_PRIVATE_KEY_FILE="${SPARKLE_PRIVATE_KEY_FILE:-$HOME/Library/CloudStorage/Dropbox/Backup/Sparkle/sparkle-private-key-KEEP-SECURE.txt}"
APPCAST_PATH="${APPCAST_PATH:-$ROOT_DIR/appcast.xml}"
APPCAST="${APPCAST:-${MAC_RELEASE_APPCAST:-appcast.xml}}"
APPCAST_PATH="${APPCAST_PATH:-$ROOT_DIR/$APPCAST}"
MINIMUM_SYSTEM_VERSION="${MINIMUM_SYSTEM_VERSION:-15.0}"
REPOSITORY_SLUG="${REPOSITORY_SLUG:-openclaw/Peekaboo}"
REPOSITORY_SLUG="${REPOSITORY_SLUG:-${MAC_RELEASE_REPO:-openclaw/Peekaboo}}"
ENTITLEMENTS_PATH="${ENTITLEMENTS_PATH:-$ROOT_DIR/Apps/Mac/Peekaboo/Peekaboo.entitlements}"
VERSION="$(node -p "require('$ROOT_DIR/package.json').version")"
VERSION="${VERSION:-$MARKETING_VERSION}"
TAG="v${VERSION}"
UPDATE_APPCAST=true
UPLOAD=false
@ -62,6 +144,7 @@ while [[ $# -gt 0 ]]; do
--version)
VERSION="$2"
TAG="v${VERSION}"
BUILD_NUMBER="$(version_to_build_number "$VERSION")"
shift 2
;;
--tag)
@ -153,6 +236,7 @@ VERIFY_DIR="$(mktemp -d /tmp/peekaboo-zip-verify.XXXXXX)"
cleanup() {
rm -rf "$NOTARY_DIR" "$VERIFY_DIR"
[[ -z "${SPARKLE_KEY_FILE:-}" ]] || rm -f "$SPARKLE_KEY_FILE"
if [[ "$DRY_RUN" == true ]]; then
rm -rf "$RELEASE_DIR"
fi
@ -241,8 +325,24 @@ verify_developer_id_signature() {
if [[ -z "$VERIFY_ONLY_ZIP" ]]; then
[[ -d "$WORKSPACE" ]] || fail "Workspace not found: $WORKSPACE"
[[ -f "$SPARKLE_PRIVATE_KEY_FILE" ]] || fail "Sparkle private key not found: $SPARKLE_PRIVATE_KEY_FILE"
mkdir -p "$RELEASE_DIR"
SPARKLE_KEY_ARGS=()
SPARKLE_KEY_FILE=""
if [[ "$MAC_RELEASE_HELPER_LOADED" == true ]]; then
SAVED_VERSION="$VERSION"
SAVED_TAG="$TAG"
SAVED_BUILD_NUMBER="$BUILD_NUMBER"
mac_release_key_args_and_validate SPARKLE_KEY_ARGS SPARKLE_KEY_FILE
VERSION="$SAVED_VERSION"
TAG="$SAVED_TAG"
BUILD_NUMBER="$SAVED_BUILD_NUMBER"
else
if [[ -z "${SPARKLE_PRIVATE_KEY_FILE:-}" && -n "${MAC_RELEASE_SIGNING_KEY_FILE:-}" ]]; then
SPARKLE_PRIVATE_KEY_FILE="$(eval "printf '%s' \"$MAC_RELEASE_SIGNING_KEY_FILE\"")"
fi
[[ -f "${SPARKLE_PRIVATE_KEY_FILE:-}" ]] || fail "Sparkle private key not found; set SPARKLE_PRIVATE_KEY_FILE or install agent-scripts helper."
SPARKLE_KEY_ARGS=(--ed-key-file "$SPARKLE_PRIVATE_KEY_FILE")
fi
fi
verify_zip() {
@ -282,6 +382,8 @@ else
-destination "$DESTINATION" \
-derivedDataPath "$DERIVED_DATA_PATH" \
-quiet \
MARKETING_VERSION="$VERSION" \
CURRENT_PROJECT_VERSION="$BUILD_NUMBER" \
build
fi
@ -347,7 +449,7 @@ ZIP_LENGTH="$(stat -f%z "$ZIP_PATH")"
ZIP_SHA256="$(shasum -a 256 "$ZIP_PATH" | awk '{print $1}')"
log "Signing Sparkle update"
SIGN_OUTPUT="$(sign_update --ed-key-file "$SPARKLE_PRIVATE_KEY_FILE" "$ZIP_PATH" 2>&1)"
SIGN_OUTPUT="$(sign_update "${SPARKLE_KEY_ARGS[@]}" "$ZIP_PATH" 2>&1)"
printf '%s\n' "$SIGN_OUTPUT"
ED_SIGNATURE="$(printf '%s\n' "$SIGN_OUTPUT" | sed -n 's/.*sparkle:edSignature="\([^"]*\)".*/\1/p' | tail -1)"
[[ -n "$ED_SIGNATURE" ]] || fail "Could not parse sparkle:edSignature from sign_update output"
@ -369,6 +471,7 @@ if [[ "$UPDATE_APPCAST" == true ]]; then
VERSION="$VERSION" \
RELEASE_URL="$RELEASE_URL" \
ASSET_URL="$ASSET_URL" \
BUILD_NUMBER="$BUILD_NUMBER" \
ZIP_LENGTH="$ZIP_LENGTH" \
ED_SIGNATURE="$ED_SIGNATURE" \
MINIMUM_SYSTEM_VERSION="$MINIMUM_SYSTEM_VERSION" \
@ -385,7 +488,7 @@ const item = ` <item>
<pubDate>${new Date().toUTCString().replace("GMT", "+0000")}</pubDate>
<enclosure
url="${process.env.ASSET_URL}"
sparkle:version="1"
sparkle:version="${process.env.BUILD_NUMBER}"
sparkle:shortVersionString="${version}"
sparkle:minimumSystemVersion="${process.env.MINIMUM_SYSTEM_VERSION}"
length="${process.env.ZIP_LENGTH}"