From c9492c90ea47bdd34d7955a6c9b11dba69f51569 Mon Sep 17 00:00:00 2001 From: George Brancovici Date: Wed, 13 May 2026 12:18:56 -0400 Subject: [PATCH] Add code signing, notarization, and DMG packaging scripts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Enables distributing Galaxy.app to end users without Gatekeeper warnings. Uses Samsung Developer ID Application certificate with hardened runtime. Pipeline: cargo bundle → sign.sh → notarize.sh → package.sh → Galaxy.dmg Entitlements: - network.client (AWS Bedrock API calls) - automation.apple-events (osascript for CLI install) - cs.allow-unsigned-executable-memory (Metal shader compilation) Orchestration scripts: - build-debug.sh: full pipeline with debug build - build-release.sh: full pipeline with release build Co-Authored-By: Claude Opus 4.6 (1M context) --- BuildSupport/Galaxy.entitlements | 13 ++++++ build-debug.sh | 14 +++++++ build-release.sh | 14 +++++++ notarize.sh | 49 +++++++++++++++++++++++ package.sh | 69 ++++++++++++++++++++++++++++++++ sign.sh | 56 ++++++++++++++++++++++++++ 6 files changed, 215 insertions(+) create mode 100644 BuildSupport/Galaxy.entitlements create mode 100755 build-debug.sh create mode 100755 build-release.sh create mode 100755 notarize.sh create mode 100755 package.sh create mode 100755 sign.sh diff --git a/BuildSupport/Galaxy.entitlements b/BuildSupport/Galaxy.entitlements new file mode 100644 index 00000000..5aada797 --- /dev/null +++ b/BuildSupport/Galaxy.entitlements @@ -0,0 +1,13 @@ + + + + + com.apple.security.network.client + + com.apple.security.automation.apple-events + + com.apple.security.cs.allow-unsigned-executable-memory + + + diff --git a/build-debug.sh b/build-debug.sh new file mode 100755 index 00000000..ce414e09 --- /dev/null +++ b/build-debug.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")" + +echo "=== Galaxy Debug Build + Sign + Notarize + Package ===" +echo "" + +cargo bundle --bin galaxy-oss --package galaxy +./sign.sh +./notarize.sh +./package.sh + +echo "" +echo "=== Done. Distributable DMG: target/debug/bundle/osx/Galaxy.dmg ===" diff --git a/build-release.sh b/build-release.sh new file mode 100755 index 00000000..04d3079e --- /dev/null +++ b/build-release.sh @@ -0,0 +1,14 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")" + +echo "=== Galaxy Release Build + Sign + Notarize + Package ===" +echo "" + +cargo bundle --release --bin galaxy-oss --package galaxy +./sign.sh --release +./notarize.sh --release +./package.sh --release + +echo "" +echo "=== Done. Distributable DMG: target/release/bundle/osx/Galaxy.dmg ===" diff --git a/notarize.sh b/notarize.sh new file mode 100755 index 00000000..cf748387 --- /dev/null +++ b/notarize.sh @@ -0,0 +1,49 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")" + +if [ "$1" = "--release" ]; then + APP_PATH="target/release/bundle/osx/Galaxy.app" + ZIP_PATH="target/release/bundle/osx/Galaxy.zip" +else + APP_PATH="target/debug/bundle/osx/Galaxy.app" + ZIP_PATH="target/debug/bundle/osx/Galaxy.zip" +fi + +trap 'rm -f "$ZIP_PATH"' EXIT + +if [ ! -d "$APP_PATH" ]; then + echo "Error: $APP_PATH not found." + exit 1 +fi + +echo "Verifying app is signed before notarization..." +codesign --verify --strict "$APP_PATH" || { + echo "Error: App is not properly signed. Run ./sign.sh first." + exit 1 +} + +echo "Creating zip for notarization submission..." +rm -f "$ZIP_PATH" +ditto -c -k --keepParent "$APP_PATH" "$ZIP_PATH" + +echo "Submitting to Apple notary service (this may take a few minutes)..." +RESULT=$(xcrun notarytool submit "$ZIP_PATH" \ + --keychain-profile notarytool-profile \ + --wait 2>&1) +echo "$RESULT" + +if ! echo "$RESULT" | grep -q "status: Accepted"; then + echo "" + echo "Error: Notarization was not accepted. Check output above." + exit 1 +fi + +echo "Stapling notarization ticket to app..." +xcrun stapler staple "$APP_PATH" + +echo "Validating stapled ticket..." +xcrun stapler validate "$APP_PATH" + +echo "" +echo "Done. Galaxy.app is notarized and stapled." diff --git a/package.sh b/package.sh new file mode 100755 index 00000000..d1cd8abc --- /dev/null +++ b/package.sh @@ -0,0 +1,69 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")" + +SIGNING_IDENTITY="Developer ID Application: SAMSUNG ELECTRONICS AMERICA, INC. (3JU72Z7Y3J)" + +if [ "$1" = "--release" ]; then + APP_PATH="target/release/bundle/osx/Galaxy.app" + DMG_PATH="target/release/bundle/osx/Galaxy.dmg" + STAGING_DIR="target/release/bundle/osx/dmg-staging" +else + APP_PATH="target/debug/bundle/osx/Galaxy.app" + DMG_PATH="target/debug/bundle/osx/Galaxy.dmg" + STAGING_DIR="target/debug/bundle/osx/dmg-staging" +fi + +if [ ! -d "$APP_PATH" ]; then + echo "Error: $APP_PATH not found." + exit 1 +fi + +echo "Verifying app is notarized..." +xcrun stapler validate "$APP_PATH" || { + echo "Error: App does not have a valid notarization ticket. Run ./sign.sh and ./notarize.sh first." + exit 1 +} + +echo "Preparing DMG staging directory..." +rm -rf "$STAGING_DIR" +mkdir -p "$STAGING_DIR" +cp -R "$APP_PATH" "$STAGING_DIR/" +ln -s /Applications "$STAGING_DIR/Applications" + +echo "Creating DMG..." +hdiutil detach /Volumes/Galaxy -force 2>/dev/null || true +rm -f "$DMG_PATH" +hdiutil create -volname "Galaxy" \ + -srcfolder "$STAGING_DIR" \ + -ov -format UDZO \ + "$DMG_PATH" + +echo "Signing DMG..." +codesign --force --sign "$SIGNING_IDENTITY" --timestamp "$DMG_PATH" + +echo "Submitting DMG to Apple notary service..." +RESULT=$(xcrun notarytool submit "$DMG_PATH" \ + --keychain-profile notarytool-profile \ + --wait 2>&1) +echo "$RESULT" + +if ! echo "$RESULT" | grep -q "status: Accepted"; then + echo "" + echo "Error: DMG notarization was not accepted. Check output above." + exit 1 +fi + +echo "Stapling notarization ticket to DMG..." +xcrun stapler staple "$DMG_PATH" + +echo "Cleaning up staging directory..." +rm -rf "$STAGING_DIR" + +echo "" +echo "Verifying final DMG..." +xcrun stapler validate "$DMG_PATH" + +echo "" +echo "Done. Galaxy.dmg is ready for distribution at:" +echo " $DMG_PATH" diff --git a/sign.sh b/sign.sh new file mode 100755 index 00000000..30cb780f --- /dev/null +++ b/sign.sh @@ -0,0 +1,56 @@ +#!/bin/bash +set -e +cd "$(dirname "$0")" + +SIGNING_IDENTITY="Developer ID Application: SAMSUNG ELECTRONICS AMERICA, INC. (3JU72Z7Y3J)" +ENTITLEMENTS="BuildSupport/Galaxy.entitlements" + +if [ "$1" = "--release" ]; then + APP_PATH="target/release/bundle/osx/Galaxy.app" +else + APP_PATH="target/debug/bundle/osx/Galaxy.app" +fi + +if [ ! -d "$APP_PATH" ]; then + echo "Error: $APP_PATH not found." + echo "Run first: cargo bundle --bin galaxy-oss --package galaxy" + exit 1 +fi + +if [ ! -f "$ENTITLEMENTS" ]; then + echo "Error: $ENTITLEMENTS not found." + exit 1 +fi + +JUNK=$(find "$APP_PATH" -maxdepth 1 -not -name "Contents" -not -path "$APP_PATH" 2>/dev/null || true) +if [ -n "$JUNK" ]; then + echo "Removing unsealed items from bundle root: $JUNK" + echo "$JUNK" | xargs rm -rf +fi + +echo "Removing .DS_Store files..." +find "$APP_PATH" -name '.DS_Store' -delete 2>/dev/null || true + +echo "Stripping extended attributes..." +xattr -cr "$APP_PATH" + +echo "Signing $APP_PATH..." +codesign --force --sign "$SIGNING_IDENTITY" \ + --entitlements "$ENTITLEMENTS" \ + --options runtime \ + --timestamp \ + "$APP_PATH/Contents/MacOS/galaxy-oss" + +codesign --force --sign "$SIGNING_IDENTITY" \ + --entitlements "$ENTITLEMENTS" \ + --options runtime \ + --timestamp \ + "$APP_PATH" + +echo "Verifying signature..." +codesign --verify --strict "$APP_PATH" +codesign -dvvv "$APP_PATH" 2>&1 | grep -E "Authority|TeamIdentifier|Runtime" +echo "Architecture: $(lipo -archs "$APP_PATH/Contents/MacOS/galaxy-oss")" + +echo "" +echo "Done. Galaxy.app is signed and ready for notarization."