2026-09-19 00:39:04 +02:00
|
|
|
#!/bin/zsh
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
script_dir=${0:A:h}
|
|
|
|
|
project="$script_dir/VoiceCat.Mac/VoiceCat.Mac.csproj"
|
|
|
|
|
configuration=Release
|
|
|
|
|
dry_run=0
|
|
|
|
|
dotnet_host=${VOICECAT_DOTNET:-}
|
|
|
|
|
|
|
|
|
|
if [[ -z "$dotnet_host" ]]; then
|
|
|
|
|
if [[ -x /usr/local/share/dotnet/dotnet ]]; then
|
|
|
|
|
dotnet_host=/usr/local/share/dotnet/dotnet
|
|
|
|
|
else
|
|
|
|
|
dotnet_host=$(command -v dotnet)
|
|
|
|
|
fi
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
if [[ ${1:-} == "--dry-run" ]]; then
|
|
|
|
|
dry_run=1
|
|
|
|
|
fi
|
|
|
|
|
|
2026-09-21 02:14:24 +02:00
|
|
|
publish_properties=(-p:ArchiveOnBuild=false)
|
|
|
|
|
if (( dry_run )); then
|
|
|
|
|
# Hardened-runtime library validation requires the executable and every bundled dylib to
|
|
|
|
|
# have the same real Team ID. Ad-hoc signatures have no Team ID, so a hardened ad-hoc app
|
|
|
|
|
# passes codesign verification but is rejected by dyld at launch. Keep local validation
|
|
|
|
|
# ad-hoc and reserve the hardened runtime for the Developer ID distribution path below.
|
|
|
|
|
publish_properties+=(-p:UseHardenedRuntime=false)
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
"$dotnet_host" publish "$project" -c "$configuration" --no-restore "${publish_properties[@]}"
|
2026-09-19 00:39:04 +02:00
|
|
|
built_app="$script_dir/VoiceCat.Mac/bin/Release/net10.0-macos27.0/osx-arm64/VoiceCat.app"
|
|
|
|
|
if [[ ! -d "$built_app" ]]; then
|
|
|
|
|
print -u2 "VoiceCat.app was not produced at $built_app"
|
|
|
|
|
exit 1
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
# Signing mutates every nested runtime binary. Work on a disposable distribution copy so a
|
|
|
|
|
# validation run can never poison MSBuild's incremental output or its signing caches.
|
|
|
|
|
distribution="$script_dir/VoiceCat.Mac/bin/Release/distribution"
|
|
|
|
|
app="$distribution/VoiceCat.app"
|
|
|
|
|
if [[ -e "$app" ]]; then
|
|
|
|
|
rm -rf "$app"
|
|
|
|
|
fi
|
|
|
|
|
mkdir -p "$distribution"
|
|
|
|
|
ditto "$built_app" "$app"
|
|
|
|
|
|
2026-09-21 02:53:27 +02:00
|
|
|
entitlements="$script_dir/VoiceCat.Mac/VoiceCat.Mac.entitlements"
|
|
|
|
|
|
|
|
|
|
sign_nested_code() {
|
|
|
|
|
local identity=$1
|
|
|
|
|
local timestamp_option=${2:-}
|
|
|
|
|
local runtime_option=${3:-}
|
|
|
|
|
local nested
|
|
|
|
|
local sign_options=(--force)
|
|
|
|
|
|
|
|
|
|
[[ -n "$timestamp_option" ]] && sign_options+=("$timestamp_option")
|
|
|
|
|
[[ -n "$runtime_option" ]] && sign_options+=("$runtime_option")
|
|
|
|
|
|
|
|
|
|
# Do not use codesign --deep for signing. It is intended as an emergency repair operation
|
|
|
|
|
# and can apply the app's entitlements to nested code. Sign each bundled Mach-O library first,
|
|
|
|
|
# then sign the outer app once with its own entitlements.
|
|
|
|
|
while IFS= read -r -d '' nested; do
|
|
|
|
|
codesign "${sign_options[@]}" --sign "$identity" "$nested"
|
|
|
|
|
done < <(find "$app/Contents" -type f \( -name '*.dylib' -o -name '*.so' \) -print0)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
install_verified_primary_bundle() {
|
|
|
|
|
# dotnet publish must create the workload bundle before it can be normalized. Replace that
|
|
|
|
|
# misleading intermediate with the verified result so both documented output paths work.
|
|
|
|
|
rm -rf "$built_app"
|
|
|
|
|
ditto "$app" "$built_app"
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 00:39:04 +02:00
|
|
|
if (( dry_run )); then
|
2026-09-21 02:53:27 +02:00
|
|
|
sign_nested_code -
|
|
|
|
|
codesign --force --entitlements "$entitlements" --sign - "$app"
|
2026-09-19 00:39:04 +02:00
|
|
|
codesign --verify --deep --strict "$app"
|
|
|
|
|
spctl --assess --type execute "$app" 2>/dev/null || true
|
2026-09-21 02:53:27 +02:00
|
|
|
install_verified_primary_bundle
|
2026-09-19 00:39:04 +02:00
|
|
|
print "Validated ad-hoc build: $app"
|
|
|
|
|
exit 0
|
|
|
|
|
fi
|
|
|
|
|
|
|
|
|
|
identity=${VOICECAT_CODESIGN_IDENTITY:-}
|
|
|
|
|
if [[ -z "$identity" ]]; then
|
|
|
|
|
print -u2 "Set VOICECAT_CODESIGN_IDENTITY to a Developer ID Application identity."
|
|
|
|
|
exit 2
|
|
|
|
|
fi
|
|
|
|
|
|
2026-09-21 02:53:27 +02:00
|
|
|
sign_nested_code "$identity" --timestamp --options=runtime
|
|
|
|
|
codesign --force --timestamp --options runtime \
|
|
|
|
|
--entitlements "$entitlements" \
|
2026-09-19 00:39:04 +02:00
|
|
|
--sign "$identity" "$app"
|
|
|
|
|
codesign --verify --deep --strict --verbose=2 "$app"
|
2026-09-21 02:53:27 +02:00
|
|
|
install_verified_primary_bundle
|
2026-09-19 00:39:04 +02:00
|
|
|
|
|
|
|
|
archive="$distribution/VoiceCat-macOS-arm64.zip"
|
|
|
|
|
ditto -c -k --keepParent "$app" "$archive"
|
|
|
|
|
|
|
|
|
|
if [[ -n ${APPLE_ID:-} && -n ${APPLE_TEAM_ID:-} && -n ${APPLE_APP_PASSWORD:-} ]]; then
|
|
|
|
|
xcrun notarytool submit "$archive" --wait \
|
|
|
|
|
--apple-id "$APPLE_ID" --team-id "$APPLE_TEAM_ID" --password "$APPLE_APP_PASSWORD"
|
|
|
|
|
xcrun stapler staple "$app"
|
|
|
|
|
xcrun stapler validate "$app"
|
|
|
|
|
spctl --assess --type execute --verbose=2 "$app"
|
|
|
|
|
ditto -c -k --keepParent "$app" "$archive"
|
|
|
|
|
print "Signed, notarized, and stapled: $archive"
|
|
|
|
|
else
|
|
|
|
|
print "Signed archive produced without notarization: $archive"
|
|
|
|
|
print "Set APPLE_ID, APPLE_TEAM_ID, and APPLE_APP_PASSWORD to submit it."
|
|
|
|
|
fi
|