Skip to content

fix: add layout to hdiutil for Applications drag prompt #77

fix: add layout to hdiutil for Applications drag prompt

fix: add layout to hdiutil for Applications drag prompt #77

Workflow file for this run

name: Build and Release Tauri App
on:
push:
branches:
- main
workflow_dispatch:
inputs:
tag:
description: 'Version tag (e.g., v0.3.0)'
required: false
type: string
env:
NODE_VERSION: '20'
RUST_VERSION: 'stable'
LUMIS_REPO: 'melandlabs/lumis'
jobs:
get-version:
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
checkout_ref: ${{ steps.version.outputs.checkout_ref }}
steps:
- id: version
run: |
if [ "${{ github.event.inputs.tag }}" != "" ]; then
echo "version=${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
echo "checkout_ref=refs/tags/${{ github.event.inputs.tag }}" >> $GITHUB_OUTPUT
else
echo "version=0.0.0-test" >> $GITHUB_OUTPUT
echo "checkout_ref=refs/heads/main" >> $GITHUB_OUTPUT
fi
build-tauri-macos:
needs: get-version
if: needs.get-version.outputs.version != ''
runs-on: macos-latest
steps:
- name: Checkout release repo
uses: actions/checkout@v4
- name: Clone lumis source code
run: |
git clone https://x-access-token:${{ secrets.LUMIS_TOKEN }}@github.com/${{ env.LUMIS_REPO }}.git lumis
cd lumis
git checkout ${{ needs.get-version.outputs.checkout_ref }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 9
- name: Add pnpm to PATH
run: |
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "PNPM_HOME=$HOME/.local" >> $GITHUB_ENV
- name: Setup Python
uses: actions/setup-python@v5
with:
python-version: '3.12'
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}
- name: Install Rust targets
run: |
rustup target add aarch64-apple-darwin
rustup target add x86_64-apple-darwin
- name: Get pnpm store directory
run: |
echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV
- name: Setup pnpm cache
uses: actions/cache@v3
with:
path: ${{ env.STORE_PATH }}
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('lumis/**/pnpm-lock.yaml') }}
restore-keys: |
${{ runner.os }}-pnpm-store-
- name: Install dependencies
working-directory: ./lumis
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Rebuild native modules for Tauri
working-directory: ./lumis
run: |
pnpm rebuild better-sqlite3
cd node_modules/@photon-ai/imessage-kit && pnpm rebuild
- name: Create .env file
working-directory: ./lumis/apps/web
run: |
cp .env.example .env
# Remove POSTGRES_URL to skip database migration in CI
grep -v '^POSTGRES_URL=' .env > .env.tmp && mv .env.tmp .env
- name: Import Apple Certificate and Intermediate Certificates
env:
APPLE_CERTIFICATE: "${{ secrets.APPLE_CERTIFICATE }}"
APPLE_CERTIFICATE_PASSWORD: "${{ secrets.APPLE_CERTIFICATE_PASSWORD }}"
run: |
# Create a new keychain
KEYCHAIN_PATH=$RUNNER_TEMP/app-signing.keychain-db
security create-keychain -p "" $KEYCHAIN_PATH
security set-keychain-settings -lut 21600 $KEYCHAIN_PATH
security unlock-keychain -p "" $KEYCHAIN_PATH
# Import Apple certificate
echo "$APPLE_CERTIFICATE" | base64 --decode > cert.p12
security import cert.p12 -P "$APPLE_CERTIFICATE_PASSWORD" -A -k $KEYCHAIN_PATH
rm cert.p12
# Download and import Apple intermediate certificates (not available in CI)
curl -s -o /tmp/AppleWWDRCAG3.cer https://www.apple.com/certificateauthority/AppleWWDRCAG3.cer
curl -s -o /tmp/DeveloperIDG2CA.cer https://www.apple.com/certificateauthority/DeveloperIDG2CA.cer
security import /tmp/AppleWWDRCAG3.cer -A -k $KEYCHAIN_PATH
security import /tmp/DeveloperIDG2CA.cer -A -k $KEYCHAIN_PATH
# List keychains to ensure system can access certificates
security list-keychain -d user -s $KEYCHAIN_PATH
- name: Build Tauri app (Apple Silicon)
working-directory: ./lumis/apps/web
run: pnpm tauri:build
env:
APPLE_CERTIFICATE: "${{ secrets.APPLE_CERTIFICATE }}"
APPLE_CERTIFICATE_PASSWORD: "${{ secrets.APPLE_CERTIFICATE_PASSWORD }}"
APPLE_SIGNING_IDENTITY: "${{ secrets.APPLE_SIGNING_IDENTITY }}"
# Don't set APPLE_ID/APPLE_PASSWORD to prevent Tauri from auto-notarizing
# Notarization will be done manually in a separate step
SIGNING_IDENTITY: "${{ secrets.APPLE_SIGNING_IDENTITY }}"
- name: Ensure all binaries are signed (fallback)
env:
APPLE_SIGNING_IDENTITY: "${{ secrets.APPLE_SIGNING_IDENTITY }}"
run: |
set -e
SIGN_ID="${APPLE_SIGNING_IDENTITY}"
APP_BUNDLE="./lumis/apps/web/src-tauri/target/release/bundle/macos/Lumis.app"
echo "🔐 Fallback: Signing all binaries in $APP_BUNDLE"
echo " Using SIGN_ID: $SIGN_ID"
# Remove extended attributes from all binaries
find "$APP_BUNDLE" -type f \( -name "*.node" -o -name "*.dylib" \) -print0 | xargs -0 xattr -cr 2>/dev/null || true
# Sign all .node files with Developer ID, timestamp, and hardened runtime
echo " Signing .node files..."
find "$APP_BUNDLE" -type f -name "*.node" -print0 | xargs -0 codesign --force --sign "$SIGN_ID" --options runtime --timestamp || true
# Sign all .dylib files with Developer ID, timestamp, and hardened runtime
echo " Signing .dylib files..."
find "$APP_BUNDLE" -type f -name "*.dylib" -print0 | xargs -0 codesign --force --sign "$SIGN_ID" --options runtime --timestamp || true
# Sign specific problematic executables with Developer ID, timestamp, and hardened runtime
echo " Signing specific executables..."
for exe in \
"$APP_BUNDLE/Contents/Resources/_up_/cli-bundle/node" \
"$APP_BUNDLE/Contents/Resources/_up_/cli-bundle/vendor/ripgrep/arm64-darwin/rg" \
"$APP_BUNDLE/Contents/Resources/_up_/.next/standalone/apps/web/cli-bundle/node" \
"$APP_BUNDLE/Contents/Resources/_up_/.next/standalone/apps/web/cli-bundle/vendor/ripgrep/arm64-darwin/rg"
do
if [ -f "$exe" ]; then
echo " Signing: $exe"
xattr -cr "$exe" 2>/dev/null || true
codesign --force --sign "$SIGN_ID" --options runtime --timestamp "$exe"
fi
done
# Verify signatures
echo " Verifying signatures..."
for file in \
"$APP_BUNDLE/Contents/Resources/_up_/.next/standalone/apps/web/cli-bundle/node" \
"$APP_BUNDLE/Contents/Resources/_up_/.next/standalone/apps/web/cli-bundle/vendor/ripgrep/arm64-darwin/rg"
do
if [ -f "$file" ]; then
echo " Verifying: $file"
codesign -dv "$file" 2>&1 | head -10
fi
done
echo "✅ Fallback signing complete"
- name: Sign app bundle with Developer ID (deep)
env:
APPLE_SIGNING_IDENTITY: "${{ secrets.APPLE_SIGNING_IDENTITY }}"
run: |
APP_BUNDLE="./lumis/apps/web/src-tauri/target/release/bundle/macos/Lumis.app"
echo "Deep signing app bundle: $APP_BUNDLE"
# Use --deep to recursively sign all nested binaries
codesign --deep --force --sign "$APPLE_SIGNING_IDENTITY" --options runtime --timestamp "$APP_BUNDLE"
echo "✅ App bundle deep signed"
- name: Create DMG manually from signed app bundle
run: |
# Remove old DMG
rm -f ./lumis/apps/web/src-tauri/target/release/bundle/dmg/Lumis*.dmg
# Get the app bundle path
APP_BUNDLE="./lumis/apps/web/src-tauri/target/release/bundle/macos/Lumis.app"
DMG_PATH="./lumis/apps/web/src-tauri/target/release/bundle/dmg/Lumis_0.3.0_aarch64.dmg"
# Create DMG from the signed app bundle using hdiutil
# Use UDZO (compressed) and -ov (overwrite) to match Tauri behavior
# Add -layout to show "Drag to Applications" prompt
hdiutil create -volname "Lumis" -fs HFS+ -srcfolder "$APP_BUNDLE" -ov -layout "UDBZ" "$DMG_PATH"
echo "✅ DMG created from signed app bundle"
- name: Sign DMG with Developer ID
env:
APPLE_SIGNING_IDENTITY: "${{ secrets.APPLE_SIGNING_IDENTITY }}"
run: |
DMG_PATH=$(ls ./lumis/apps/web/src-tauri/target/release/bundle/dmg/Lumis*.dmg)
echo "Signing DMG with Developer ID: $DMG_PATH"
# Re-sign the DMG with Developer ID certificate
codesign --force --sign "$APPLE_SIGNING_IDENTITY" --options runtime --timestamp "$DMG_PATH"
echo "✅ DMG re-signed"
- name: Notarize DMG
timeout-minutes: 60
run: |
DMG_PATH=$(ls ./lumis/apps/web/src-tauri/target/release/bundle/dmg/Lumis*.dmg)
echo "Notarizing: $DMG_PATH"
# Submit without waiting
OUTPUT=$(xcrun notarytool submit "$DMG_PATH" \
--apple-id "${{ secrets.APPLE_ID }}" \
--password "${{ secrets.APPLE_PASSWORD }}" \
--team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1)
echo "$OUTPUT"
# Extract submission ID
SUBMISSION_ID=$(echo "$OUTPUT" | grep "id:" | head -1 | awk '{print $2}')
if [ -z "$SUBMISSION_ID" ]; then
echo "❌ Failed to get submission ID"
exit 1
fi
echo "Submission ID: $SUBMISSION_ID"
# Poll for status (max 50 minutes)
echo "Waiting for notarization to complete..."
MAX_ATTEMPTS=100
attempt=0
while [ $attempt -lt $MAX_ATTEMPTS ]; do
attempt=$((attempt + 1))
STATUS_OUTPUT=$(xcrun notarytool list \
--apple-id "${{ secrets.APPLE_ID }}" \
--password "${{ secrets.APPLE_PASSWORD }}" \
--team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || echo "")
if echo "$STATUS_OUTPUT" | grep -q "Accepted.*$SUBMISSION_ID"; then
echo "✅ Notarization accepted!"
exit 0
elif echo "$STATUS_OUTPUT" | grep -q "Invalid.*$SUBMISSION_ID"; then
echo "❌ Notarization failed!"
xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "${{ secrets.APPLE_ID }}" \
--password "${{ secrets.APPLE_PASSWORD }}" \
--team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || true
exit 1
fi
echo " Status: In Progress... (attempt $attempt/$(($MAX_ATTEMPTS)) - $(date +%H:%M:%S))"
sleep 30
done
echo "⚠️ Notarization timed out after $((MAX_ATTEMPTS * 30)) seconds, checking final status..."
# Try to get the log which contains the final status
FINAL_LOG=$(xcrun notarytool log "$SUBMISSION_ID" \
--apple-id "${{ secrets.APPLE_ID }}" \
--password "${{ secrets.APPLE_PASSWORD }}" \
--team-id "${{ secrets.APPLE_TEAM_ID }}" 2>&1 || echo "")
echo "$FINAL_LOG"
if echo "$FINAL_LOG" | grep -q '"status": "Accepted"'; then
echo "✅ Notarization accepted!"
else
echo "❌ Notarization may have failed"
exit 1
fi
- name: Staple DMG
run: |
DMG_PATH=$(ls ./lumis/apps/web/src-tauri/target/release/bundle/dmg/Lumis*.dmg)
echo "Stapling: $DMG_PATH"
xcrun stapler staple "$DMG_PATH"
- name: Verify Staple
run: |
DMG_PATH=$(ls ./lumis/apps/web/src-tauri/target/release/bundle/dmg/Lumis*.dmg)
xcrun stapler validate "$DMG_PATH"
- name: Find and rename DMG file
id: dmg
working-directory: ./lumis/apps/web/src-tauri/target/release/bundle/dmg
run: |
# Get original filename
ORIGINAL=$(ls Lumis*.dmg)
# Extract version and arch, add macOS platform
NEW=$(echo "$ORIGINAL" | sed 's/Lumis_\(.*\)_aarch64\.dmg/Lumis_\1_macOS_aarch64.dmg/')
mv "$ORIGINAL" "$NEW"
echo "path=$NEW" >> $GITHUB_OUTPUT
echo "full_path=$(pwd)/$NEW" >> $GITHUB_OUTPUT
- name: Upload Apple Silicon DMG
uses: actions/upload-artifact@v4
with:
name: lumis-dmg-arm64
path: ${{ steps.dmg.outputs.full_path }}
build-tauri-linux:
needs: get-version
if: needs.get-version.outputs.version != ''
runs-on: ubuntu-22.04
steps:
- name: Checkout release repo
uses: actions/checkout@v4
- name: Clone lumis source code
run: |
git clone https://x-access-token:${{ secrets.LUMIS_TOKEN }}@github.com/${{ env.LUMIS_REPO }}.git lumis
cd lumis
git checkout ${{ needs.get-version.outputs.checkout_ref }}
- name: Setup Node.js
uses: actions/setup-node@v4
with:
node-version: ${{ env.NODE_VERSION }}
- name: Install pnpm
uses: pnpm/action-setup@v2
with:
version: 9
- name: Add pnpm to PATH
run: |
echo "$HOME/.local/bin" >> $GITHUB_PATH
echo "PNPM_HOME=$HOME/.local" >> $GITHUB_ENV
- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
toolchain: ${{ env.RUST_VERSION }}
- name: Install Rust target
run: |
rustup target add x86_64-unknown-linux-gnu
- name: Install dependencies
working-directory: ./lumis
run: pnpm install --frozen-lockfile --ignore-scripts
- name: Rebuild native modules for Tauri
working-directory: ./lumis
run: |
pnpm rebuild better-sqlite3
cd node_modules/@photon-ai/imessage-kit && pnpm rebuild
- name: Create .env file
working-directory: ./lumis/apps/web
run: |
cp .env.example .env
# Remove POSTGRES_URL to skip database migration in CI
grep -v '^POSTGRES_URL=' .env > .env.tmp && mv .env.tmp .env
- name: Install Linux build dependencies
run: |
sudo apt-get update
sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf
- name: Build Tauri app (Linux)
working-directory: ./lumis/apps/web
run: pnpm tauri:build
env:
# Try to build only deb to avoid AppImage issues
TAURI_BUNDLE_TARGET: deb
continue-on-error: true
- name: Verify deb package exists
run: |
DEB_FILE=$(ls ./lumis/apps/web/src-tauri/target/release/bundle/deb/*.deb 2>/dev/null || echo "")
if [ -z "$DEB_FILE" ]; then
echo "❌ Error: deb package not found!"
ls -la ./lumis/apps/web/src-tauri/target/release/bundle/ 2>/dev/null || true
exit 1
fi
echo "✅ deb package found: $DEB_FILE"
- name: Find deb file
id: deb
working-directory: ./lumis/apps/web/src-tauri/target/release/bundle/deb
run: |
ORIGINAL=$(ls *.deb)
NEW=$(echo "$ORIGINAL" | sed 's/Lumis_\(.*\)/Lumis_\1_linux/')
mv "$ORIGINAL" "$NEW"
echo "path=$NEW" >> $GITHUB_OUTPUT
- name: Upload Linux deb
uses: actions/upload-artifact@v4
with:
name: lumis-linux
path: ./lumis/apps/web/src-tauri/target/release/bundle/deb/${{ steps.deb.outputs.path }}
release:
needs: [get-version, build-tauri-macos, build-tauri-linux]
if: needs.get-version.outputs.version != ''
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Download Apple Silicon DMG
uses: actions/download-artifact@v4
with:
name: lumis-dmg-arm64
- name: Download Linux deb
uses: actions/download-artifact@v4
with:
name: lumis-linux
- name: Create Release
uses: softprops/action-gh-release@v1
with:
tag_name: ${{ needs.get-version.outputs.version }}
name: 'Lumis ${{ needs.get-version.outputs.version }}'
body: 'See assets to download this version and install.'
draft: true
files: |
Lumis*.dmg
*.deb
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Update Homebrew Cask
run: |
# Find DMG file
DMG_FILE=$(ls Lumis*macOS*.dmg)
# Extract version from filename (e.g., Lumis_0.3.0_macOS_aarch64.dmg -> 0.3.0)
VERSION=$(echo "$DMG_FILE" | sed -E 's/Lumis_([0-9.]+)_macOS_aarch64.dmg/\1/')
# Calculate SHA256
SHA256=$(sha256sum "$DMG_FILE" | awk '{print $1}')
# Update cask file
sed -i "s/version \"[^\"]*\"/version \"$VERSION\"/" Casks/lumis.rb
sed -i "s/sha256 \"[^\"]*\"/sha256 \"$SHA256\"/" Casks/lumis.rb
# Commit changes
git config --local user.email "github-actions[bot]@users.noreply.github.com"
git config --local user.name "github-actions[bot]"
git add Casks/lumis.rb
git commit -m "chore: update Homebrew cask to v$VERSION" || echo "No changes to commit"
- name: Push changes
uses: ad-m/github-push-action@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}