@@ -129,6 +129,59 @@ jobs:
129129 echo "No DMG found for checksums"
130130 fi
131131
132+ - name : Create macOS Gatekeeper fix script
133+ run : |
134+ cat > "Fix_Local_Lens.command" << 'SCRIPT'
135+ #!/bin/bash
136+ # Local Lens - macOS Gatekeeper Fix
137+ # Double-click this file after copying Local Lens to Applications
138+
139+ echo ""
140+ echo "🔧 Local Lens - Fixing macOS Security Block"
141+ echo "============================================"
142+ echo ""
143+
144+ APP_PATH="/Applications/Local Lens.app"
145+ USER_APP_PATH="$HOME/Applications/Local Lens.app"
146+
147+ if [ -d "$APP_PATH" ]; then
148+ TARGET="$APP_PATH"
149+ echo "Found app in /Applications"
150+ elif [ -d "$USER_APP_PATH" ]; then
151+ TARGET="$USER_APP_PATH"
152+ echo "Found app in ~/Applications"
153+ else
154+ echo "❌ Local Lens not found in Applications!"
155+ echo ""
156+ echo "Please:"
157+ echo "1. Drag 'Local Lens.app' to your Applications folder first"
158+ echo "2. Then run this script again"
159+ echo ""
160+ read -p "Press Enter to close..."
161+ exit 1
162+ fi
163+
164+ echo "Fixing: $TARGET"
165+ echo ""
166+
167+ echo "Step 1/3: Removing quarantine flag..."
168+ xattr -cr "$TARGET"
169+
170+ echo "Step 2/3: Applying ad-hoc signature..."
171+ codesign --force --deep --sign - "$TARGET" 2>/dev/null || echo " (signature step skipped)"
172+
173+ echo "Step 3/3: Setting executable permissions..."
174+ chmod -R +x "$TARGET/Contents/MacOS/"
175+ chmod -R +x "$TARGET/Contents/Resources/backend_server_bundle/" 2>/dev/null || true
176+
177+ echo ""
178+ echo "✅ Done! You can now open Local Lens normally."
179+ echo ""
180+ read -p "Press Enter to close..."
181+ SCRIPT
182+ chmod +x "Fix_Local_Lens.command"
183+ echo "Created Fix_Local_Lens.command"
184+
132185 - name : Upload macOS artifacts
133186 uses : actions/upload-artifact@v4
134187 with :
@@ -138,6 +191,7 @@ jobs:
138191 frontend/src-tauri/target/release/bundle/dmg/checksums-macos.txt
139192 frontend/src-tauri/target/release/bundle/macos/*.app.tar.gz
140193 frontend/src-tauri/target/release/bundle/macos/*.app.tar.gz.sig
194+ Fix_Local_Lens.command
141195 retention-days : 5
142196
143197 # ============================================================================
@@ -323,6 +377,7 @@ jobs:
323377 TARBALL=$(find artifacts/macos -name "*.app.tar.gz" 2>/dev/null | head -1)
324378 TARBALL_SIG=$(find artifacts/macos -name "*.app.tar.gz.sig" 2>/dev/null | head -1)
325379 CHECKSUMS_MAC=$(find artifacts/macos -name "checksums-macos.txt" 2>/dev/null | head -1)
380+ FIX_SCRIPT=$(find artifacts/macos -name "Fix_Local_Lens.command" 2>/dev/null | head -1)
326381
327382 echo "msi_path=$MSI_FILE" >> $GITHUB_OUTPUT
328383 echo "msi_sig_path=$MSI_SIG" >> $GITHUB_OUTPUT
@@ -332,6 +387,14 @@ jobs:
332387 echo "tarball_path=$TARBALL" >> $GITHUB_OUTPUT
333388 echo "tarball_sig_path=$TARBALL_SIG" >> $GITHUB_OUTPUT
334389 echo "checksums_mac_path=$CHECKSUMS_MAC" >> $GITHUB_OUTPUT
390+ echo "fix_script_path=$FIX_SCRIPT" >> $GITHUB_OUTPUT
391+
392+ # Get DMG SHA256 for Homebrew cask
393+ if [ -n "$DMG_FILE" ] && [ -f "$DMG_FILE" ]; then
394+ DMG_SHA256=$(shasum -a 256 "$DMG_FILE" | awk '{print $1}')
395+ echo "dmg_sha256=$DMG_SHA256" >> $GITHUB_OUTPUT
396+ echo "DMG SHA256: $DMG_SHA256"
397+ fi
335398
336399 # Read signatures for latest.json
337400 if [ -n "$MSI_SIG" ] && [ -f "$MSI_SIG" ]; then
@@ -353,13 +416,83 @@ jobs:
353416 win_sig="${{ steps.find_files.outputs.win_signature }}"
354417 mac_sig="${{ steps.find_files.outputs.mac_signature }}"
355418
356- # Generate latest.json using printf to avoid heredoc YAML issues
357- printf '{\n "version": "%s",\n "notes": "See release notes on GitHub",\n "pub_date": "%s",\n "platforms": {\n "windows-x86_64": {\n "signature": "%s",\n "url": "https://github.com/%s/releases/download/%s/Local_Lens_%s_x64_en-US.msi"\n },\n "darwin-aarch64": {\n "signature": "%s",\n "url": "https://github.com/%s/releases/download/%s/Local_Lens.app.tar.gz"\n }\n }\n}\n' \
358- "$version" "$pub_date" "$win_sig" "$repo" "$tag" "$tag" "$mac_sig" "$repo" "$tag" > latest.json
419+ # Get release notes
420+ notes="${{ steps.release_notes.outputs.notes }}"
421+
422+ # Use jq to generate valid JSON with proper escaping
423+ jq -n \
424+ --arg version "$version" \
425+ --arg notes "$notes" \
426+ --arg pub_date "$pub_date" \
427+ --arg win_sig "$win_sig" \
428+ --arg win_url "https://github.com/$repo/releases/download/$tag/Local_Lens_${tag}_x64_en-US.msi" \
429+ --arg mac_sig "$mac_sig" \
430+ --arg mac_url "https://github.com/$repo/releases/download/$tag/Local_Lens.app.tar.gz" \
431+ '{
432+ version: $version,
433+ notes: $notes,
434+ pub_date: $pub_date,
435+ platforms: {
436+ "windows-x86_64": {
437+ signature: $win_sig,
438+ url: $win_url
439+ },
440+ "darwin-aarch64": {
441+ signature: $mac_sig,
442+ url: $mac_url
443+ }
444+ }
445+ }' > latest.json
359446
360447 echo "Generated latest.json:"
361448 cat latest.json
362449
450+ - name : Generate Homebrew Cask formula
451+ run : |
452+ version="${{ steps.get_version.outputs.version }}"
453+ sha256="${{ steps.find_files.outputs.dmg_sha256 }}"
454+
455+ cat > local-lens.rb << EOF
456+ cask "local-lens" do
457+ version "$version"
458+ sha256 "$sha256"
459+
460+ url "https://github.com/${{ github.repository }}/releases/download/v#{version}/Local_Lens_v#{version}_aarch64.dmg",
461+ verified: "github.com/${{ github.repository }}/"
462+ name "Local Lens"
463+ desc "AI-powered offline photo organizer with face recognition"
464+ homepage "https://github.com/${{ github.repository }}"
465+
466+ livecheck do
467+ url :url
468+ strategy :github_latest
469+ end
470+
471+ auto_updates true
472+ depends_on arch: :arm64
473+
474+ app "Local Lens.app"
475+
476+ postflight do
477+ system_command "/usr/bin/xattr",
478+ args: ["-cr", "#{appdir}/Local Lens.app"],
479+ sudo: false
480+ end
481+
482+ zap trash: [
483+ "~/.config/LocalLens",
484+ ]
485+
486+ caveats <<~EOS
487+ Local Lens is not notarized by Apple.
488+ If you see a security warning, right-click the app → Open → Click "Open"
489+ EOS
490+ end
491+ EOF
492+
493+ echo "Generated Homebrew Cask formula:"
494+ cat local-lens.rb
495+
363496 - name : Create Release
364497 id : create_release
365498 uses : actions/create-release@v1
@@ -377,46 +510,58 @@ jobs:
377510
378511 ---
379512
380- ### 🔒 Security & Verification
513+ ### � Installation
381514
382- This release was built automatically using GitHub Actions for Windows and macOS:
515+ #### 🍺 macOS via Homebrew (Recommended)
516+ ```bash
517+ brew install ashesbloom/locallens/local-lens
518+ ```
519+ > Homebrew automatically handles Gatekeeper - no extra steps needed!
383520
384- - **Source Code**: Available in this repository at tag `${{ github.ref_name }}`
385- - **Build Process**: View the complete build log in the [Actions tab](https://github.com/${{ github.repository }}/actions)
386- - **Checksums**: SHA256 checksums provided for file verification
521+ #### 🪟 Windows
522+ 1. Download the `.msi` (enterprise) or `.exe` (individual) installer
523+ 2. Run the installer
524+ 3. Launch Local Lens from the Start Menu
387525
388- ### 📥 Downloads
526+ #### 🍎 macOS Manual Install (DMG)
527+ 1. Download `Local_Lens_${{ github.ref_name }}_aarch64.dmg`
528+ 2. Open the DMG and drag **Local Lens** to Applications
529+ 3. **Fix Gatekeeper block** (choose one):
530+ - **Easy:** Download and double-click `Fix_Local_Lens.command`
531+ - **Manual:** Right-click the app → Open → Click "Open" in the dialog
532+ - **Terminal:**
533+ ```bash
534+ xattr -cr "/Applications/Local Lens.app" && codesign --force --deep --sign - "/Applications/Local Lens.app"
535+ ```
389536
390- **Windows:**
391- - **MSI Installer**: Recommended for enterprise/managed environments
392- - **EXE Installer**: Recommended for individual users
537+ ---
393538
394- **macOS (Apple Silicon):**
395- - **DMG**: Drag and drop to Applications folder
539+ ### ⚠️ macOS Security Note
396540
397- ### ✅ File Verification
541+ Local Lens is **not notarized** with Apple (requires $99/year developer fee).
542+ macOS may show a "damaged" or "unidentified developer" warning - this is normal for open-source apps.
398543
399- **Windows:**
400- ```cmd
401- certutil -hashfile "installer_name" SHA256
402- ```
544+ The `Fix_Local_Lens.command` script automatically:
545+ - Removes the quarantine flag
546+ - Applies an ad-hoc code signature
547+ - Sets correct executable permissions
403548
404- **macOS:**
405- ```bash
406- shasum -a 256 "Local Lens.dmg"
407- ```
549+ ---
550+
551+ ### 🔒 Security & Verification
408552
409- ### 🚀 Installation
553+ - **Source Code**: Available at tag `${{ github.ref_name }}`
554+ - **Build Process**: [View build logs](https://github.com/${{ github.repository }}/actions)
555+ - **Checksums**: SHA256 checksums provided for verification
410556
411- **Windows :**
412- 1. Download your preferred installer format
413- 2. Run the installer (Admin recommended for MSI)
414- 3. Launch Local Lens from Start Menu
557+ **Verify downloads :**
558+ ```bash
559+ # macOS
560+ shasum -a 256 "Local_Lens_${{ github.ref_name }}_aarch64.dmg"
415561
416- **macOS:**
417- 1. Download the DMG file
418- 2. Open the DMG and drag Local Lens to Applications
419- 3. First launch: Right-click → Open (to bypass Gatekeeper)
562+ # Windows
563+ certutil -hashfile "Local_Lens_${{ github.ref_name }}_x64-setup.exe" SHA256
564+ ```
420565
421566 # Upload Windows artifacts
422567 - name : Upload MSI installer
@@ -486,6 +631,27 @@ jobs:
486631 asset_name : checksums-macos.txt
487632 asset_content_type : text/plain
488633
634+ - name : Upload macOS fix script
635+ if : steps.find_files.outputs.fix_script_path
636+ uses : actions/upload-release-asset@v1
637+ env :
638+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
639+ with :
640+ upload_url : ${{ steps.create_release.outputs.upload_url }}
641+ asset_path : ${{ steps.find_files.outputs.fix_script_path }}
642+ asset_name : Fix_Local_Lens.command
643+ asset_content_type : application/x-sh
644+
645+ - name : Upload Homebrew Cask formula
646+ uses : actions/upload-release-asset@v1
647+ env :
648+ GITHUB_TOKEN : ${{ secrets.GITHUB_TOKEN }}
649+ with :
650+ upload_url : ${{ steps.create_release.outputs.upload_url }}
651+ asset_path : local-lens.rb
652+ asset_name : local-lens.rb
653+ asset_content_type : text/x-ruby
654+
489655 # Upload latest.json for Tauri auto-updater
490656 - name : Upload latest.json
491657 uses : actions/upload-release-asset@v1
@@ -502,6 +668,7 @@ jobs:
502668 echo "✅ Release created successfully!"
503669 echo "📦 Uploaded artifacts:"
504670 echo " Windows: MSI, EXE, checksums"
505- echo " macOS: DMG, app tarball, checksums"
671+ echo " macOS: DMG, app tarball, checksums, Fix_Local_Lens.command "
506672 echo "📋 latest.json generated for auto-updater (Windows + macOS)"
673+ echo "🍺 local-lens.rb generated for Homebrew tap"
507674 echo "🔒 Release created as draft - review and publish manually"
0 commit comments