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"