Skip to content

fix: restore --deep signing but restore ad-hoc for node after #87

fix: restore --deep signing but restore ad-hoc for node after

fix: restore --deep signing but restore ad-hoc for node after #87

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-14
steps:
- name: Checkout release repo
uses: actions/checkout@v4
- name: Clone alloomi 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: |
cat > .env << 'EOF'
ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }}
# Cloud API Configuration for Tauri Desktop App
CLOUD_API_URL=https://app.uselumis.ai
NEXT_PUBLIC_CLOUD_API_URL=https://app.uselumis.ai
# Local development URL (for Tauri mode)
NEXT_PUBLIC_APP_URL=http://localhost:3415
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_MODEL=google/gemini-3-flash-preview
LLM_REASONING_MODEL=google/gemini-3-flash-preview
LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview
LLM_IMAGE_MODEL=openai/gpt-5-image
LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b
LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1
TELEGRAM_MODE=pooling
# Telegram App Id and Hash. Reference: https://my.telegram.org/
TG_APP_ID=${{ secrets.TG_APP_ID }}
TG_APP_HASH=${{ secrets.TG_APP_HASH }}
# Roles
ROLE_DETECTION_ENABLED=1
ROLE_OVERLAY_ANALYST_JOURNALIST=1
ROLE_OVERLAY_COMMUNITY_MANAGER=1
ROLE_OVERLAY_CUSTOMER_SUCCESS=1
ROLE_OVERLAY_EXECUTIVE=1
ROLE_OVERLAY_FREELANCER=1
ROLE_OVERLAY_INDIE_FOUNDER=1
ROLE_OVERLAY_INVESTOR_ADVISOR=1
ROLE_OVERLAY_REMOTE_WORKER=1
ROLE_OVERLAY_SALES_BIZDEV=1
CLAUDE_CODE_TMPDIR=$HOME/.cache/lumis-tmp
ANTHROPIC_BASE_URL=http://localhost:3415/api/ai
API_TIMEOUT_MS=3000000
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
IS_TAURI=true
# iMessage Backend Mode
IMESSAGE_BACKEND_MODE=sdk
NEXT_PUBLIC_IMESSAGE_BACKEND_MODE=sdk
CLAUDE_DISABLE_URL_SAFETY_CHECK=true
EOF
- 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
# But skip cli-bundle/node which is already signed by bundle-runtime.sh
echo " Signing .node files..."
find "$APP_BUNDLE" -type f -name "*.node" -not -path "*/cli-bundle/*" -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
# Skip cli-bundle/* as they are already signed by bundle-runtime.sh
echo " Signing specific executables..."
for exe in \
"$APP_BUNDLE/Contents/Resources/_up_/cli-bundle/vendor/ripgrep/arm64-darwin/rg" \
"$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/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"
# Restore ad-hoc signing for cli-bundle/node after deep signing
# This is required for Node.js to run properly
codesign --force --sign - --options runtime "$APP_BUNDLE/Contents/Resources/_up_/cli-bundle/node"
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 a temporary directory for DMG contents
TEMP_DMG_DIR=$(mktemp -d)
cp -R "$APP_BUNDLE" "$TEMP_DMG_DIR/"
# Create symlink to /Applications (shows as "Applications" folder in DMG)
ln -s /Applications "$TEMP_DMG_DIR/Applications"
# Create DMG from the temp directory with proper layout
# This creates a DMG with the app on the left and Applications folder on the right
hdiutil create -volname "Lumis" \
-fs HFS+ \
-srcfolder "$TEMP_DMG_DIR" \
-format UDZO \
"$DMG_PATH"
# Clean up temp directory
rm -rf "$TEMP_DMG_DIR"
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"
# Verify DMG exists and check its signature
echo "Verifying DMG..."
codesign -dv "$DMG_PATH" 2>&1 | head -5
xcrun stapler validate "$DMG_PATH" 2>&1 || echo "Staple check: $?"
# 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: |
cat > .env << 'EOF'
ENCRYPTION_KEY=${{ secrets.ENCRYPTION_KEY }}
# Cloud API Configuration for Tauri Desktop App
CLOUD_API_URL=https://app.uselumis.ai
NEXT_PUBLIC_CLOUD_API_URL=https://app.uselumis.ai
# Local development URL (for Tauri mode)
NEXT_PUBLIC_APP_URL=http://localhost:3415
LLM_BASE_URL=https://openrouter.ai/api/v1
LLM_MODEL=google/gemini-3-flash-preview
LLM_REASONING_MODEL=google/gemini-3-flash-preview
LLM_VISION_LANGUAGE_MODEL=google/gemini-3-flash-preview
LLM_IMAGE_MODEL=openai/gpt-5-image
LLM_EMBEDDING_MODEL=qwen/qwen3-embedding-4b
LLM_EMBEDDING_BASE_URL=https://openrouter.ai/api/v1
TELEGRAM_MODE=pooling
# Telegram App Id and Hash. Reference: https://my.telegram.org/
TG_APP_ID=${{ secrets.TG_APP_ID }}
TG_APP_HASH=${{ secrets.TG_APP_HASH }}
# Roles
ROLE_DETECTION_ENABLED=1
ROLE_OVERLAY_ANALYST_JOURNALIST=1
ROLE_OVERLAY_COMMUNITY_MANAGER=1
ROLE_OVERLAY_CUSTOMER_SUCCESS=1
ROLE_OVERLAY_EXECUTIVE=1
ROLE_OVERLAY_FREELANCER=1
ROLE_OVERLAY_INDIE_FOUNDER=1
ROLE_OVERLAY_INVESTOR_ADVISOR=1
ROLE_OVERLAY_REMOTE_WORKER=1
ROLE_OVERLAY_SALES_BIZDEV=1
CLAUDE_CODE_TMPDIR=$HOME/.cache/lumis-tmp
ANTHROPIC_BASE_URL=http://localhost:3415/api/ai
API_TIMEOUT_MS=3000000
CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
IS_TAURI=true
# iMessage Backend Mode
IMESSAGE_BACKEND_MODE=sdk
NEXT_PUBLIC_IMESSAGE_BACKEND_MODE=sdk
CLAUDE_DISABLE_URL_SAFETY_CHECK=true
EOF
- 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)
# Rename Lumis_0.3.0_amd64.deb to Lumis_0.3.0_linux_amd64.deb
NEW=$(echo "$ORIGINAL" | sed 's/_amd64/_linux_amd64/')
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 }}