diff --git a/.github/workflows/desktop-build.yml b/.github/workflows/desktop-build.yml index 02c3f7aec..2cd88084d 100644 --- a/.github/workflows/desktop-build.yml +++ b/.github/workflows/desktop-build.yml @@ -17,6 +17,7 @@ on: - '!.husky/**' - '!.tx/**' - '!doc/**' + - '!utils/**' - '!webclient/**' - '.github/workflows/desktop-build.yml' - 'CMakeLists.txt' @@ -32,6 +33,7 @@ on: - '!.husky/**' - '!.tx/**' - '!doc/**' + - '!utils/**' - '!webclient/**' - '.github/workflows/desktop-build.yml' - 'CMakeLists.txt' diff --git a/utils/README.md b/utils/README.md new file mode 100644 index 000000000..84cd4f176 --- /dev/null +++ b/utils/README.md @@ -0,0 +1,3 @@ +# Utils + +Helper scripts for local development and testing (not used by CI). diff --git a/utils/macos_fix_codesign.sh b/utils/macos_fix_codesign.sh new file mode 100755 index 000000000..38089c357 --- /dev/null +++ b/utils/macos_fix_codesign.sh @@ -0,0 +1,101 @@ +#!/bin/bash +# Fixes code signing for unsigned macOS app bundles. Use after installing an unsigned build (e.g. from a PR artifact) to `/Applications` so the apps can be opened without Gatekeeper blocking them. +# Run from project root: ./utils/macos_fix_codesign.sh [path/to/app.app ...] +# +# Example usage: +# ./utils/macos_fix_codesign.sh # fixes cockatrice, oracle, and servatrice in /Applications +# ./utils/macos_fix_codesign.sh /Applications/cockatrice.app # fixes only cockatrice +# +# Steps: +# 1. Remove quarantine extended attribute +# 2. Ad-hoc sign all dylibs, frameworks and executables inside the bundle +# 3. Deep sign the app bundle +# +# Use after installing an unsigned build to /Applications. +# With no args, fixes cockatrice, oracle, and servatrice in /Applications. + +set -eo pipefail + +DEFAULT_APPS=( + /Applications/cockatrice.app + /Applications/oracle.app + /Applications/servatrice.app +) + +is_safe_app_bundle() { + local resolved_app="$1" + + [[ "$resolved_app" == *.app ]] || { echo "Error: Path must end with .app (got '$resolved_app')" >&2; return 1; } + + case "$resolved_app" in + / | /Applications/ | /System | /Users | /usr | /Library) + echo "Error: Path '$resolved_app' is a system-critical directory" >&2 + return 1 + ;; + /System/* | /usr/* | /Library/*) + echo "Error: Path '$resolved_app' is under a system-critical directory" >&2 + return 1 + ;; + *) + # Not a system-critical directory; continue validation + ;; + esac + + [[ -d "$resolved_app/Contents" ]] || { echo "Error: Path '$resolved_app' lacks Contents/ (not a valid app bundle)" >&2; return 1; } + + return 0 +} + +fix_app() { + local app="$1" + if [[ ! -d "$app" ]]; then + echo "Skipping (not found): $app" + return 0 + fi + + if [[ -L "$app" ]]; then + echo "Error: Symlinks are not allowed (got '$app'). Use a real path to the app bundle." >&2 + return 1 + fi + + local resolved_app + resolved_app="$(cd -P -- "$app" && pwd -P)" || { + echo "Error: Failed to resolve path '$app'" >&2 + return 1 + } + + if ! is_safe_app_bundle "$resolved_app"; then + echo "Error: '$app' is not a valid app bundle path. Use a path ending in .app (e.g. /Applications/MyApp.app)." >&2 + return 1 + fi + + echo "Fixing code signing for: $app" + + # 1. Remove quarantine + echo " Removing quarantine..." + xattr -dr com.apple.quarantine -- "$resolved_app" + + # 2. Ad-hoc sign all binaries and dylibs + echo " Signing dylibs, frameworks and executables..." + # find .dylib files by name and executable files (binaries, frameworks, etc.) by permission via `-perm +111` + find -- "$resolved_app" -type f \( -name "*.dylib" -o -perm +111 \) -print0 | \ + xargs -0 -I {} codesign -s - -f -- {} + + # 3. Deep sign the app bundle + echo " Deep signing app bundle..." + codesign -s - --force --deep -- "$resolved_app" + + echo " Done." +} + +if [[ $# -gt 0 ]]; then + for app in "$@"; do + fix_app "$app" + done +else + for app in "${DEFAULT_APPS[@]}"; do + fix_app "$app" + done +fi + +echo "All done. Try: open /Applications/cockatrice.app"