burrow/Scripts/ci/package-apple-artifacts.sh
Conrad Kramer 002bd382e9
Some checks failed
Cache: Publish Nix / Publish Nix Cache (push) Waiting to run
Build Rust / Cargo Test (push) Successful in 4m14s
Build Site / Next.js Build (push) Failing after 6s
Infra: OpenTofu / OpenTofu (grafana) (push) Successful in 5s
Lint Governance / BEP Metadata (push) Successful in 0s
Build: Android / Android Rust Core Stub (push) Failing after 23s
Wire Forge-native release infrastructure
2026-06-07 05:51:12 -07:00

287 lines
8.8 KiB
Bash
Executable file

#!/usr/bin/env bash
set -euo pipefail
repo_root="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"
platform="${1:-all}"
build_number="${BUILD_NUMBER:-${RELEASE_BUILD_NUMBER:-${RELEASE_REF:-manual}}}"
out_root="${BURROW_RELEASE_OUT:-${repo_root}/dist/builds/${build_number}}"
apple_root="${out_root}/apple"
archives_root="${apple_root}/archives"
derived_data="${BURROW_DERIVED_DATA_PATH:-${repo_root}/.derived-data/apple}"
source_packages="${BURROW_SWIFTPM_CACHE_PATH:-${XDG_CACHE_HOME:-${HOME}/.cache}/burrow/swiftpm}"
project="Apple/Burrow.xcodeproj"
scheme="${BURROW_APPLE_SCHEME:-App}"
bundle_id="${BURROW_APPLE_BUNDLE_ID:-com.hackclub.burrow}"
network_extension_bundle_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${bundle_id}.network}"
signing_ready="${BURROW_APPLE_SIGNING_READY:-0}"
require_signing="${BURROW_APPLE_REQUIRE_SIGNING:-false}"
sparkle_feed_url="${BURROW_SPARKLE_FEED_URL:-https://releases.burrow.net/sparkle/appcast.xml}"
sparkle_public_ed_key="${BURROW_SPARKLE_PUBLIC_ED_KEY:-Myv9ZNZT6YGKMtMezh52ra4WqaeEKc4VlvVU0evhJeI=}"
sparkle_sign_with_kms="${BURROW_SPARKLE_SIGN_WITH_KMS:-false}"
mkdir -p "$apple_root" "$archives_root" "$derived_data" "$source_packages"
truthy() {
case "${1:-}" in
1|true|TRUE|True|yes|YES|Yes|y|Y|on|ON|On) return 0 ;;
*) return 1 ;;
esac
}
sha256_file() {
local file="$1"
if command -v shasum >/dev/null 2>&1; then
shasum -a 256 "$file" > "${file}.sha256"
else
sha256sum "$file" > "${file}.sha256"
fi
}
write_version() {
mkdir -p Apple/Configuration
printf 'CURRENT_PROJECT_VERSION = %s\n' "$build_number" > Apple/Configuration/Version.xcconfig
}
xcode_common_args=(
-project "$project"
-scheme "$scheme"
-configuration Release
-derivedDataPath "$derived_data"
-clonedSourcePackagesDirPath "$source_packages"
CURRENT_PROJECT_VERSION="$build_number"
APP_BUNDLE_IDENTIFIER="$bundle_id"
NETWORK_EXTENSION_BUNDLE_IDENTIFIER="$network_extension_bundle_id"
BURROW_SPARKLE_FEED_URL="$sparkle_feed_url"
BURROW_SPARKLE_PUBLIC_ED_KEY="$sparkle_public_ed_key"
)
xcode_auth_args=()
if [[ -n "${ASC_API_KEY_ID:-}" && -n "${ASC_API_ISSUER_ID:-}" && -n "${ASC_API_KEY_PATH:-}" && -f "${ASC_API_KEY_PATH:-}" ]]; then
xcode_auth_args+=(
-authenticationKeyID "$ASC_API_KEY_ID"
-authenticationKeyIssuerID "$ASC_API_ISSUER_ID"
-authenticationKeyPath "$ASC_API_KEY_PATH"
)
fi
if [[ -n "${BURROW_APPLE_KEYCHAIN_PATH:-}" ]]; then
xcode_common_args+=(OTHER_CODE_SIGN_FLAGS="--keychain ${BURROW_APPLE_KEYCHAIN_PATH}")
fi
require_xcodebuild() {
if ! command -v xcodebuild >/dev/null 2>&1; then
echo "xcodebuild is required for Apple release artifacts" >&2
exit 1
fi
xcodebuild -version
xcrun --find swiftc >/dev/null
}
unsigned_note() {
local platform_name="$1"
local artifact_name="$2"
{
echo "Burrow ${platform_name} signing assets were not available."
echo
echo "The lane built an unsigned validation artifact instead of an uploadable release artifact."
echo "Seal the Apple release credentials with agenix and let CI sync provisioning profiles from App Store Connect to produce ${artifact_name}."
} > "${apple_root}/README-${platform_name}-unsigned.txt"
}
ensure_signing_or_note() {
local platform_name="$1"
local artifact_name="$2"
if [[ "$signing_ready" == "1" ]]; then
return 0
fi
if truthy "$require_signing"; then
echo "Apple signing is required for ${platform_name}, but signing assets are not configured." >&2
exit 1
fi
unsigned_note "$platform_name" "$artifact_name"
return 1
}
write_export_options() {
local method="$1"
local path="$2"
cat > "$path" <<EOF
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>method</key>
<string>${method}</string>
<key>destination</key>
<string>export</string>
<key>signingStyle</key>
<string>automatic</string>
<key>stripSwiftSymbols</key>
<true/>
<key>teamID</key>
<string>${DEVELOPMENT_TEAM:-P6PV2R9443}</string>
</dict>
</plist>
EOF
}
build_ios_unsigned() {
local product_dir zip_path
xcodebuild build \
"${xcode_common_args[@]}" \
-destination "generic/platform=iOS Simulator" \
ARCHS=arm64 \
ONLY_ACTIVE_ARCH=YES \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO
product_dir="${derived_data}/Build/Products/Release-iphonesimulator"
zip_path="${apple_root}/Burrow-iOS-simulator-${build_number}.unsigned.zip"
if [[ -d "${product_dir}/Burrow.app" ]]; then
(cd "$product_dir" && zip -qr "$zip_path" Burrow.app)
sha256_file "$zip_path"
fi
}
build_ios_release() {
local archive_path export_dir export_options ipa
if ! ensure_signing_or_note "iOS" "Burrow-iOS-${build_number}.ipa"; then
build_ios_unsigned
return 0
fi
archive_path="${archives_root}/Burrow-iOS-${build_number}.xcarchive"
export_dir="${apple_root}/ios-export"
export_options="${apple_root}/ExportOptions-iOS.plist"
write_export_options "app-store-connect" "$export_options"
xcodebuild archive \
"${xcode_common_args[@]}" \
-destination "generic/platform=iOS" \
-archivePath "$archive_path" \
ARCHS=arm64 \
-allowProvisioningUpdates \
"${xcode_auth_args[@]}"
xcodebuild -exportArchive \
-archivePath "$archive_path" \
-exportPath "$export_dir" \
-exportOptionsPlist "$export_options" \
-allowProvisioningUpdates \
"${xcode_auth_args[@]}"
ipa="$(find "$export_dir" -maxdepth 1 -type f -name '*.ipa' | sort | head -n 1 || true)"
if [[ -z "$ipa" ]]; then
echo "iOS export completed but did not produce an IPA" >&2
exit 1
fi
cp "$ipa" "${apple_root}/Burrow-iOS-${build_number}.ipa"
sha256_file "${apple_root}/Burrow-iOS-${build_number}.ipa"
}
build_macos_unsigned() {
local product_dir zip_path
xcodebuild build \
"${xcode_common_args[@]}" \
-destination "platform=macOS" \
CODE_SIGNING_ALLOWED=NO \
CODE_SIGNING_REQUIRED=NO
product_dir="${derived_data}/Build/Products/Release"
zip_path="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip"
if [[ -d "${product_dir}/Burrow.app" ]]; then
ditto -c -k --keepParent "${product_dir}/Burrow.app" "$zip_path"
sha256_file "$zip_path"
fi
}
build_macos_release() {
local archive_path export_dir export_options app dmg channel sparkle_dir length pubdate url
if ! ensure_signing_or_note "macOS" "Burrow-macOS-${build_number}.dmg"; then
build_macos_unsigned
return 0
fi
archive_path="${archives_root}/Burrow-macOS-${build_number}.xcarchive"
export_dir="${apple_root}/macos-export"
export_options="${apple_root}/ExportOptions-macOS.plist"
write_export_options "${BURROW_MACOS_EXPORT_METHOD:-developer-id}" "$export_options"
xcodebuild archive \
"${xcode_common_args[@]}" \
-destination "generic/platform=macOS" \
-archivePath "$archive_path" \
-allowProvisioningUpdates \
"${xcode_auth_args[@]}"
xcodebuild -exportArchive \
-archivePath "$archive_path" \
-exportPath "$export_dir" \
-exportOptionsPlist "$export_options" \
-allowProvisioningUpdates \
"${xcode_auth_args[@]}"
app="$(find "$export_dir" -maxdepth 2 -type d -name 'Burrow.app' | sort | head -n 1 || true)"
if [[ -z "$app" ]]; then
echo "macOS export completed but did not produce Burrow.app" >&2
exit 1
fi
dmg="${apple_root}/Burrow-macOS-${build_number}.dmg"
rm -f "$dmg"
hdiutil create -volname "Burrow ${build_number}" -srcfolder "$app" -ov -format UDZO "$dmg"
sha256_file "$dmg"
channel="${SPARKLE_CHANNEL:-build-${build_number}}"
sparkle_dir="${out_root}/sparkle/${channel}"
mkdir -p "$sparkle_dir"
cp "$dmg" "$sparkle_dir/"
length="$(wc -c < "$dmg" | tr -d ' ')"
pubdate="$(LC_ALL=C date -u '+%a, %d %b %Y %H:%M:%S +0000')"
url="${SPARKLE_DOWNLOAD_PREFIX:-https://releases.burrow.net/sparkle}/${channel}/$(basename "$dmg")"
cat > "${sparkle_dir}/appcast.xml" <<EOF
<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:sparkle="http://www.andymatuschak.org/xml-namespaces/sparkle">
<channel>
<title>Burrow ${channel}</title>
<item>
<title>Burrow ${build_number}</title>
<pubDate>${pubdate}</pubDate>
<sparkle:version>${build_number}</sparkle:version>
<sparkle:shortVersionString>0.1</sparkle:shortVersionString>
<enclosure url="${url}" sparkle:version="${build_number}" length="${length}" type="application/octet-stream" />
</item>
</channel>
</rss>
EOF
if truthy "$sparkle_sign_with_kms"; then
Scripts/sparkle/sign-appcast-kms.py \
--appcast "${sparkle_dir}/appcast.xml" \
--artifact-dir "$sparkle_dir"
fi
}
require_xcodebuild
write_version
case "$platform" in
all)
build_ios_release
build_macos_release
;;
ios)
build_ios_release
;;
macos)
build_macos_release
;;
*)
echo "unknown Apple release platform: $platform" >&2
exit 64
;;
esac
find "$out_root" -type f -print | sort