diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 14627a9..e240813 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,11 +1,9 @@ name: Build macOS Release - on: push: tags: - "v*" workflow_dispatch: - permissions: contents: write @@ -18,14 +16,11 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Install Rust toolchain (both macOS targets) + - name: Install Rust toolchain uses: dtolnay/rust-toolchain@stable with: targets: aarch64-apple-darwin,x86_64-apple-darwin - - name: Install cargo-bundle - run: cargo install cargo-bundle - - name: Cache cargo registry uses: actions/cache@v4 with: @@ -44,24 +39,45 @@ jobs: path: target key: ${{ runner.os }}-universal-cargo-build-${{ hashFiles('**/Cargo.lock') }} - - name: Build release bundles (aarch64 + x86_64) - run: | - cargo bundle --release --target aarch64-apple-darwin - cargo bundle --release --target x86_64-apple-darwin + - name: Build macOS app (universal) + run: bash scripts/build-macos.sh + + - name: Sign macOS app + env: + MACOS_CERTIFICATE: ${{ secrets.MACOS_CERTIFICATE }} + MACOS_CERTIFICATE_PWD: ${{ secrets.MACOS_CERTIFICATE_PWD }} + MACOS_CERTIFICATE_NAME: ${{ secrets.MACOS_CERTIFICATE_NAME }} + MACOS_CI_KEYCHAIN_PWD: ${{ secrets.MACOS_CI_KEYCHAIN_PWD }} + MACOS_NOTARIZATION_APPLE_ID: ${{ secrets.MACOS_NOTARIZATION_APPLE_ID }} + MACOS_NOTARIZATION_TEAM_ID: ${{ secrets.MACOS_NOTARIZATION_TEAM_ID }} + MACOS_NOTARIZATION_PWD: ${{ secrets.MACOS_NOTARIZATION_PWD }} + run: bash scripts/sign-macos.sh + + - name: Package DMG + run: bash scripts/package-macos.sh + # Homebrew-style artifact from the built .app - name: Create artifacts directory run: mkdir -p artifacts - - name: Package app bundles + - name: Zip universal .app for Homebrew + # APP_BUNDLE_PATH is set by scripts/build-macos.sh run: | - cd target/aarch64-apple-darwin/release/bundle/osx - zip -r ../../../../../artifacts/rustcast-aarch64-apple-darwin.app.zip *.app + if [ -z "$APP_BUNDLE_PATH" ]; then + echo "APP_BUNDLE_PATH not set by build-macos.sh" >&2 + exit 1 + fi + APP_NAME=$(basename "$APP_BUNDLE_PATH") + zip -r "artifacts/${APP_NAME%.app}-universal-macos.app.zip" "$APP_BUNDLE_PATH" - cd - >/dev/null - cd target/x86_64-apple-darwin/release/bundle/osx - zip -r ../../../../../artifacts/rustcast-x86_64-apple-darwin.app.zip *.app + - name: Upload DMG artifact + uses: actions/upload-artifact@v4 + with: + name: rustcast-macos-dmg + path: ${{ env.DMG_PATH }} + retention-days: 7 - - name: Upload artifacts + - name: Upload .app zip artifact (Homebrew) uses: actions/upload-artifact@v4 with: name: macos-bundles @@ -81,7 +97,10 @@ jobs: - name: Create release uses: softprops/action-gh-release@v1 with: - files: artifacts/**/*.zip + # Include both DMG and .app zip(s) + files: | + artifacts/**/*.dmg + artifacts/**/*.zip draft: false prerelease: false env: diff --git a/Cargo.toml b/Cargo.toml index 4c807da..3297d66 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -24,21 +24,3 @@ rayon = "1.11.0" serde = { version = "1.0.228", features = ["derive"] } tokio = { version = "1.48.0", features = ["full"] } toml = "0.9.8" - -[package.metadata.bundle] -name = "RustCast" -identifier = "com.umangsurana.rustcast" -icon = ["bundling/icon.icns"] -version = "1.0.0" -resources = [] -copyright = "Copyright Umang Surana (c) 2025" -category = "Developer Tool" -short_description = "An open source alternative to Raycast, and in rust" -osx_minimum_system_version = "10.15" - -[package.metadata.bundle.osx] -info_plist_path = "bundling/Info.plist" - -[package.metadata.bundle.osx.info] -LSUIElement = true -NSHighResolutionCapable = true diff --git a/bundling/Info.plist b/assets/macos/RustCast.app/Contents/Info.plist similarity index 94% rename from bundling/Info.plist rename to assets/macos/RustCast.app/Contents/Info.plist index 4b666fd..298774f 100644 --- a/bundling/Info.plist +++ b/assets/macos/RustCast.app/Contents/Info.plist @@ -4,38 +4,29 @@ CFBundleDevelopmentRegion en - CFBundleExecutable rustcast - CFBundleIdentifier com.umangsurana.rustcast - CFBundleInfoDictionaryVersion 6.0 - CFBundleName RustCast - CFBundlePackageType APPL - CFBundleShortVersionString 1.0 - CFBundleVersion 1 - LSUIElement - NSHighResolutionCapable - NSHumanReadableCopyright Copyright © 2025 Umang Surana. All rights reserved. - NSInputMonitoringUsageDescription RustCast needs to monitor keyboard input to detect global shortcuts and control casting. + CFBundleIconFile + icon diff --git a/bundling/icon.icns b/assets/macos/RustCast.app/Contents/Resources/icon.icns similarity index 100% rename from bundling/icon.icns rename to assets/macos/RustCast.app/Contents/Resources/icon.icns diff --git a/bundling/entitlements.plist b/bundling/entitlements.plist deleted file mode 100644 index f62dcc8..0000000 --- a/bundling/entitlements.plist +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.security.app-sandbox - - com.apple.security.cs.allow-jit - - com.apple.security.cs.allow-unsigned-executable-memory - - com.apple.security.cs.disable-library-validation - - - diff --git a/scripts/build-macos.sh b/scripts/build-macos.sh new file mode 100755 index 0000000..a57ad39 --- /dev/null +++ b/scripts/build-macos.sh @@ -0,0 +1,52 @@ +#!/usr/bin/env -S bash -e + +TARGET="rustcast" # your Cargo binary name +ASSETS_DIR="assets" +RELEASE_DIR="target/release" +APP_NAME="Rustcast.app" +APP_TEMPLATE="$ASSETS_DIR/macos/$APP_NAME" +APP_TEMPLATE_PLIST="$APP_TEMPLATE/Contents/Info.plist" +APP_DIR="$RELEASE_DIR/macos" +APP_BINARY="$RELEASE_DIR/$TARGET" +APP_BINARY_DIR="$APP_DIR/$APP_NAME/Contents/MacOS" +APP_EXTRAS_DIR="$APP_DIR/$APP_NAME/Contents/Resources" +DMG_NAME="rustcast.dmg" +DMG_DIR="$RELEASE_DIR/macos" + +VERSION="{$APP_VERSION}" +BUILD=$(git describe --always --dirty --exclude='*') + +# Update version/build in Info.plist +cp "$APP_TEMPLATE_PLIST" "$APP_TEMPLATE_PLIST.tmp" +sed -i '' -e "s/{{ VERSION }}/$VERSION/g" "$APP_TEMPLATE_PLIST.tmp" +sed -i '' -e "s/{{ BUILD }}/$BUILD/g" "$APP_TEMPLATE_PLIST.tmp" +mv "$APP_TEMPLATE_PLIST.tmp" "$APP_TEMPLATE_PLIST" + +export MACOSX_DEPLOYMENT_TARGET="11.0" + +# Ensure both targets exist +rustup target add x86_64-apple-darwin +rustup target add aarch64-apple-darwin + +# Build both archs +cargo build --release --locked --target=x86_64-apple-darwin +cargo build --release --locked --target=aarch64-apple-darwin + +# Create universal binary +lipo \ + "target/x86_64-apple-darwin/release/$TARGET" \ + "target/aarch64-apple-darwin/release/$TARGET" \ + -create -output "$APP_BINARY" + +# Build app bundle +rm -rf "$APP_DIR/$APP_NAME" +mkdir -p "$APP_BINARY_DIR" +mkdir -p "$APP_EXTRAS_DIR" +cp -fRp "$APP_TEMPLATE" "$APP_DIR" +cp -fp "$APP_BINARY" "$APP_BINARY_DIR" +touch -r "$APP_BINARY" "$APP_DIR/$APP_NAME" + +echo "Created '$APP_NAME' in '$APP_DIR'" +echo "APP_BUNDLE_PATH=$APP_DIR/$APP_NAME" >> "$GITHUB_ENV" +echo "DMG_NAME=$DMG_NAME" >> "$GITHUB_ENV" +echo "DMG_DIR=$DMG_DIR" >> "$GITHUB_ENV" diff --git a/scripts/package-macos.sh b/scripts/package-macos.sh new file mode 100755 index 0000000..1efad67 --- /dev/null +++ b/scripts/package-macos.sh @@ -0,0 +1,27 @@ +#!/usr/bin/env -S bash -e + +APP_BUNDLE_PATH="${APP_BUNDLE_PATH:?APP_BUNDLE_PATH not set}" +DMG_NAME="${DMG_NAME:?DMG_NAME not set}" +DMG_DIR="${DMG_DIR:?DMG_DIR not set}" + +VOLUME_NAME="Rustcast" # visible volume name +STAGING_DIR="$DMG_DIR/dmg-staging" + +rm -rf "$STAGING_DIR" +mkdir -p "$STAGING_DIR" + +# Copy app and Applications symlink into staging dir +cp -R "$APP_BUNDLE_PATH" "$STAGING_DIR/" +ln -s /Applications "$STAGING_DIR/Applications" + +# Remove old DMG +rm -f "$DMG_DIR/$DMG_NAME" + +# Create DMG +hdiutil create -volname "$VOLUME_NAME" \ + -srcfolder "$STAGING_DIR" \ + -ov -format UDZO \ + "$DMG_DIR/$DMG_NAME" + +echo "Created DMG at $DMG_DIR/$DMG_NAME" +echo "DMG_PATH=$DMG_DIR/$DMG_NAME" >> "$GITHUB_ENV" diff --git a/scripts/sign-macos.sh b/scripts/sign-macos.sh new file mode 100755 index 0000000..64a18a1 --- /dev/null +++ b/scripts/sign-macos.sh @@ -0,0 +1,37 @@ +#!/usr/bin/env -S bash -e + +APP_BUNDLE_PATH="${APP_BUNDLE_PATH:?APP_BUNDLE_PATH not set}" + +# 1. Create a temporary keychain and import certificate +KEYCHAIN=build.keychain-db + +if security list-keychains | grep -q "$KEYCHAIN"; then + echo "Keychain $KEYCHAIN already exists, using existing keychain." +else + security create-keychain -p "$MACOS_CI_KEYCHAIN_PWD" "$KEYCHAIN" +fi + +security default-keychain -s "$KEYCHAIN" +security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" "$KEYCHAIN" +security set-keychain-settings "$KEYCHAIN" +security default-keychain -s "$KEYCHAIN" +security unlock-keychain -p "$MACOS_CI_KEYCHAIN_PWD" "$KEYCHAIN" +security set-keychain-settings "$KEYCHAIN" + +# Import certificate (Base64 encoded .p12 in secret MACOS_CERTIFICATE) +echo "$MACOS_CERTIFICATE" | base64 --decode > certificate.p12 +security import certificate.p12 \ + -k "$KEYCHAIN" \ + -P "$MACOS_CERTIFICATE_PWD" \ + -T /usr/bin/codesign + +security set-key-partition-list -S apple-tool:,apple:,codesign: \ + -s -k "$MACOS_CI_KEYCHAIN_PWD" "$KEYCHAIN" + +# 2. Sign app bundle +codesign --deep --force --options runtime --timestamp \ + --sign "$MACOS_CERTIFICATE_NAME" \ + "$APP_BUNDLE_PATH" + +codesign --verify --deep --strict --verbose=2 "$APP_BUNDLE_PATH" +echo "Signed app at $APP_BUNDLE_PATH"