Fix macOS publish bundle signing
Build and test / test (macos-latest) (push) Canceled after 0s
Build and test / test (ubuntu-24.04) (push) Canceled after 0s
Build and test / test (windows-latest) (push) Canceled after 0s
Build and test / apple-client (push) Canceled after 0s

This commit is contained in:
2026-09-21 02:53:27 +02:00
parent 239e8e9013
commit 4fac7af140
4 changed files with 55 additions and 6 deletions
+5 -2
View File
@@ -15,8 +15,11 @@ dotnet build clients/apple/VoiceCat.Apple.slnx -c Debug --no-restore
Use `publish-macos.sh --dry-run` to validate a local ad-hoc macOS bundle. The dry-run build does
not enable hardened runtime because ad-hoc signatures have no Team ID and cannot satisfy macOS
library validation. Distribution builds remain hardened and require `VOICECAT_CODESIGN_IDENTITY`;
optional notarization uses `APPLE_ID`, `APPLE_TEAM_ID`, and `APPLE_APP_PASSWORD`.
library validation. The script normalizes nested signatures and installs the verified bundle at
both `VoiceCat.Mac/bin/Release/net10.0-macos27.0/osx-arm64/VoiceCat.app` and
`VoiceCat.Mac/bin/Release/distribution/VoiceCat.app`. Distribution builds remain hardened and
require `VOICECAT_CODESIGN_IDENTITY`; optional notarization uses `APPLE_ID`, `APPLE_TEAM_ID`, and
`APPLE_APP_PASSWORD`.
For a physical iOS device, use `build-ios-device.sh` and `deploy-ios-device.sh`. The host and
ReplayKit extension require signing profiles with App Group `group.me.iamtalon.voicecat`.
@@ -2,6 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>com.apple.security.app-sandbox</key><true/>
<key>com.apple.security.cs.allow-jit</key><true/>
<key>com.apple.security.network.client</key><true/>
<key>com.apple.security.device.audio-input</key><true/>
<key>com.apple.security.files.user-selected.read-write</key><true/>
+34 -4
View File
@@ -45,11 +45,39 @@ fi
mkdir -p "$distribution"
ditto "$built_app" "$app"
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"
}
if (( dry_run )); then
# Normalize the workload's mixed nested signatures after bundling.
codesign --force --deep --sign - "$app"
sign_nested_code -
codesign --force --entitlements "$entitlements" --sign - "$app"
codesign --verify --deep --strict "$app"
spctl --assess --type execute "$app" 2>/dev/null || true
install_verified_primary_bundle
print "Validated ad-hoc build: $app"
exit 0
fi
@@ -60,10 +88,12 @@ if [[ -z "$identity" ]]; then
exit 2
fi
codesign --force --deep --timestamp --options runtime \
--entitlements "$script_dir/VoiceCat.Mac/VoiceCat.Mac.entitlements" \
sign_nested_code "$identity" --timestamp --options=runtime
codesign --force --timestamp --options runtime \
--entitlements "$entitlements" \
--sign "$identity" "$app"
codesign --verify --deep --strict --verbose=2 "$app"
install_verified_primary_bundle
archive="$distribution/VoiceCat-macOS-arm64.zip"
ditto -c -k --keepParent "$app" "$archive"
@@ -24,6 +24,21 @@ public class PublishServerScriptTests
Assert.Contains("(?:bin|obj)", script);
}
[Fact]
public async Task MacPublishPreservesAppEntitlementsAndReplacesBrokenWorkloadBundle()
{
string root = FindRoot();
string script = await File.ReadAllTextAsync(Path.Combine(root, "clients", "apple", "publish-macos.sh"));
string entitlements = await File.ReadAllTextAsync(Path.Combine(
root, "clients", "apple", "VoiceCat.Mac", "VoiceCat.Mac.entitlements"));
Assert.Contains("sign_nested_code", script);
Assert.Contains("--entitlements \"$entitlements\"", script);
Assert.Contains("install_verified_primary_bundle", script);
Assert.DoesNotContain("codesign --force --deep", script);
Assert.Contains("com.apple.security.cs.allow-jit", entitlements);
}
[Fact]
public async Task DefaultPublishTargetsWindowsAndLinuxWhileRuntimeCanSelectOne()
{