258 lines
8 KiB
Bash
Executable file
258 lines
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"
|
|
bundle_id="${BURROW_APPLE_BUNDLE_ID:-net.burrow.app}"
|
|
network_extension_bundle_id="${BURROW_APPLE_NETWORK_EXTENSION_BUNDLE_ID:-${bundle_id}.network}"
|
|
notarize_macos="${BURROW_NOTARIZE_MACOS:-false}"
|
|
sparkle_sign_with_kms="${BURROW_SPARKLE_SIGN_WITH_KMS:-true}"
|
|
|
|
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
|
|
}
|
|
|
|
safe_profile_slug() {
|
|
python3 - "$1" <<'PY'
|
|
import re
|
|
import sys
|
|
|
|
print(re.sub(r"[^A-Za-z0-9]", "", sys.argv[1]).lower())
|
|
PY
|
|
}
|
|
|
|
profile_for() {
|
|
local identifier="$1"
|
|
local suffix="$2"
|
|
local slug path
|
|
slug="$(safe_profile_slug "$identifier")"
|
|
for path in \
|
|
".forgejo/actions/export/${slug}${suffix}.provisionprofile" \
|
|
".forgejo/actions/export/${slug}${suffix}.mobileprovision" \
|
|
"Apple/Profiles/${slug}${suffix}.provisionprofile" \
|
|
"Apple/Profiles/${slug}${suffix}.mobileprovision"; do
|
|
if [[ -s "$path" ]]; then
|
|
printf '%s\n' "$path"
|
|
return 0
|
|
fi
|
|
done
|
|
return 1
|
|
}
|
|
|
|
require_tool() {
|
|
if ! command -v "$1" >/dev/null 2>&1; then
|
|
echo "error: missing required tool: $1" >&2
|
|
exit 1
|
|
fi
|
|
}
|
|
|
|
extract_profile_entitlements() {
|
|
local profile="$1"
|
|
local output="$2"
|
|
Scripts/apple/profile-entitlements.py "$profile" --output "$output"
|
|
}
|
|
|
|
sign_bundle() {
|
|
local family="$1"
|
|
local bundle_path="$2"
|
|
local entitlements="$3"
|
|
local runtime_flag=()
|
|
if [[ "$family" == "developer-id" ]]; then
|
|
runtime_flag=(--runtime)
|
|
fi
|
|
Scripts/apple/sign-with-google-kms.sh \
|
|
--family "$family" \
|
|
--path "$bundle_path" \
|
|
--entitlements "$entitlements" \
|
|
"${runtime_flag[@]}"
|
|
}
|
|
|
|
write_notary_api_key_json() {
|
|
local out="$1"
|
|
if [[ -z "${ASC_API_KEY_ID:-}" || -z "${ASC_API_ISSUER_ID:-}" || -z "${ASC_API_KEY_PATH:-}" ]]; then
|
|
echo "macOS notarization requires ASC_API_KEY_ID, ASC_API_ISSUER_ID, and ASC_API_KEY_PATH" >&2
|
|
exit 1
|
|
fi
|
|
rcodesign encode-app-store-connect-api-key \
|
|
"$ASC_API_ISSUER_ID" \
|
|
"$ASC_API_KEY_ID" \
|
|
"$ASC_API_KEY_PATH" \
|
|
"$out"
|
|
}
|
|
|
|
sign_ios() {
|
|
local unsigned_ipa app_profile ext_profile work_dir app ext app_entitlements ext_entitlements output_ipa
|
|
unsigned_ipa="${apple_root}/Burrow-iOS-${build_number}.unsigned.ipa"
|
|
if [[ ! -s "$unsigned_ipa" ]]; then
|
|
echo "error: missing staged unsigned iOS IPA: $unsigned_ipa" >&2
|
|
exit 1
|
|
fi
|
|
|
|
app_profile="$(profile_for "$bundle_id" "appstore")" || {
|
|
echo "error: missing iOS App Store provisioning profile for ${bundle_id}" >&2
|
|
exit 1
|
|
}
|
|
ext_profile="$(profile_for "$network_extension_bundle_id" "appstore")" || {
|
|
echo "error: missing iOS App Store provisioning profile for ${network_extension_bundle_id}" >&2
|
|
exit 1
|
|
}
|
|
|
|
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-ios-kms-sign.XXXXXX")"
|
|
trap 'rm -rf "${work_dir:-}"' RETURN
|
|
unzip -q "$unsigned_ipa" -d "$work_dir"
|
|
app="${work_dir}/Payload/Burrow.app"
|
|
ext="${app}/PlugIns/BurrowNetworkExtension.appex"
|
|
if [[ ! -d "$app" || ! -d "$ext" ]]; then
|
|
echo "error: unsigned IPA is missing Burrow.app or BurrowNetworkExtension.appex" >&2
|
|
exit 1
|
|
fi
|
|
|
|
cp "$app_profile" "${app}/embedded.mobileprovision"
|
|
cp "$ext_profile" "${ext}/embedded.mobileprovision"
|
|
app_entitlements="${work_dir}/app-entitlements.plist"
|
|
ext_entitlements="${work_dir}/extension-entitlements.plist"
|
|
extract_profile_entitlements "$app_profile" "$app_entitlements"
|
|
extract_profile_entitlements "$ext_profile" "$ext_entitlements"
|
|
|
|
rm -rf "${app}/_CodeSignature" "${ext}/_CodeSignature"
|
|
sign_bundle ios "$ext" "$ext_entitlements"
|
|
sign_bundle ios "$app" "$app_entitlements"
|
|
|
|
output_ipa="${apple_root}/Burrow-iOS-${build_number}.ipa"
|
|
rm -f "$output_ipa"
|
|
(cd "$work_dir" && zip -qry "$output_ipa" Payload)
|
|
sha256_file "$output_ipa"
|
|
}
|
|
|
|
write_sparkle_appcast() {
|
|
local artifact="$1"
|
|
local channel sparkle_dir length pubdate url
|
|
channel="${SPARKLE_CHANNEL:-build-${build_number}}"
|
|
sparkle_dir="${out_root}/sparkle/${channel}"
|
|
mkdir -p "$sparkle_dir"
|
|
cp "$artifact" "$sparkle_dir/"
|
|
length="$(wc -c < "$artifact" | 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 "$artifact")"
|
|
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
|
|
}
|
|
|
|
sign_macos() {
|
|
local unsigned_zip app_profile ext_profile work_dir app ext app_entitlements ext_entitlements output_zip notary_zip key_json
|
|
unsigned_zip="${apple_root}/Burrow-macOS-${build_number}.unsigned.zip"
|
|
if [[ ! -s "$unsigned_zip" ]]; then
|
|
echo "error: missing staged unsigned macOS ZIP: $unsigned_zip" >&2
|
|
exit 1
|
|
fi
|
|
|
|
app_profile="$(profile_for "$bundle_id" "developerid")" || {
|
|
echo "error: missing macOS Developer ID provisioning profile for ${bundle_id}" >&2
|
|
exit 1
|
|
}
|
|
ext_profile="$(profile_for "$network_extension_bundle_id" "developerid")" || {
|
|
echo "error: missing macOS Developer ID provisioning profile for ${network_extension_bundle_id}" >&2
|
|
exit 1
|
|
}
|
|
|
|
work_dir="$(mktemp -d "${TMPDIR:-/tmp}/burrow-macos-kms-sign.XXXXXX")"
|
|
trap 'rm -rf "${work_dir:-}"' RETURN
|
|
unzip -q "$unsigned_zip" -d "$work_dir"
|
|
app="$(find "$work_dir" -maxdepth 2 -type d -name 'Burrow.app' -print | head -n 1)"
|
|
if [[ -z "$app" || ! -d "$app" ]]; then
|
|
echo "error: unsigned macOS ZIP does not contain Burrow.app" >&2
|
|
exit 1
|
|
fi
|
|
|
|
ext="${app}/Contents/PlugIns/BurrowNetworkExtension.appex"
|
|
cp "$app_profile" "${app}/Contents/embedded.provisionprofile"
|
|
app_entitlements="${work_dir}/app-entitlements.plist"
|
|
extract_profile_entitlements "$app_profile" "$app_entitlements"
|
|
|
|
if [[ -d "$ext" ]]; then
|
|
cp "$ext_profile" "${ext}/Contents/embedded.provisionprofile"
|
|
ext_entitlements="${work_dir}/extension-entitlements.plist"
|
|
extract_profile_entitlements "$ext_profile" "$ext_entitlements"
|
|
rm -rf "${ext}/Contents/_CodeSignature"
|
|
sign_bundle developer-id "$ext" "$ext_entitlements"
|
|
fi
|
|
|
|
rm -rf "${app}/Contents/_CodeSignature"
|
|
sign_bundle developer-id "$app" "$app_entitlements"
|
|
|
|
if truthy "$notarize_macos"; then
|
|
key_json="${work_dir}/notary-api-key.json"
|
|
notary_zip="${work_dir}/Burrow.notary.zip"
|
|
write_notary_api_key_json "$key_json"
|
|
(cd "$(dirname "$app")" && zip -qry "$notary_zip" "$(basename "$app")")
|
|
rcodesign notary-submit --api-key-file "$key_json" --wait "$notary_zip"
|
|
rcodesign staple "$app"
|
|
fi
|
|
|
|
output_zip="${apple_root}/Burrow-macOS-${build_number}.zip"
|
|
rm -f "$output_zip"
|
|
(cd "$(dirname "$app")" && zip -qry "$output_zip" "$(basename "$app")")
|
|
sha256_file "$output_zip"
|
|
write_sparkle_appcast "$output_zip"
|
|
}
|
|
|
|
require_tool python3
|
|
require_tool unzip
|
|
require_tool zip
|
|
require_tool rcodesign
|
|
require_tool gcloud
|
|
mkdir -p "$apple_root"
|
|
|
|
case "$platform" in
|
|
all)
|
|
sign_ios
|
|
sign_macos
|
|
;;
|
|
ios)
|
|
sign_ios
|
|
;;
|
|
macos)
|
|
sign_macos
|
|
;;
|
|
*)
|
|
echo "unknown Apple KMS signing platform: $platform" >&2
|
|
exit 64
|
|
;;
|
|
esac
|
|
|
|
find "$out_root" -type f -print | sort
|