Skip to content

Commit 06ad70f

Browse files
committed
v2.2.1: Add Homebrew support and macOS Gatekeeper fix script
1 parent c73ea17 commit 06ad70f

8 files changed

Lines changed: 342 additions & 36 deletions

File tree

.github/workflows/release.yml

Lines changed: 200 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -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"

CHANGELOG.md

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,29 @@ All notable changes to Local Lens will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [2.2.1] - 2025-12-31
9+
10+
### Added
11+
12+
- **Homebrew Cask Support**: macOS users can now install via `brew install ashesbloom/locallens/local-lens`
13+
- Homebrew automatically handles Gatekeeper - no manual steps needed
14+
- Auto-generated cask formula included in each release
15+
- **macOS Gatekeeper Fix Script**: `Fix_Local_Lens.command` included in releases
16+
- Double-click to automatically remove quarantine, apply ad-hoc signature, and set permissions
17+
- Supports both `/Applications` and `~/Applications` install locations
18+
- Improved release notes with clear macOS installation instructions
19+
20+
### Changed
21+
22+
- Updated README with Quick Install section for all platforms
23+
- Release workflow now generates Homebrew cask formula automatically
24+
- Enhanced macOS installation documentation with multiple fix options
25+
26+
### Fixed
27+
28+
- Fixed macOS "App is damaged" error by providing proper workarounds
29+
- Fixed path escaping issues in terminal commands (use quotes instead of backslashes)
30+
831
## [2.2.0] - 2025-12-24
932

1033
### Added

README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,30 @@
2929
</div>
3030
<br />
3131

32+
## � Quick Install
33+
34+
### macOS (Homebrew) — Recommended
35+
```bash
36+
brew install ashesbloom/locallens/local-lens
37+
```
38+
> Homebrew handles Gatekeeper automatically — no extra steps needed!
39+
40+
### macOS (Manual DMG)
41+
1. Download the `.dmg` from [Releases](https://github.com/ashesbloom/LocalLens/releases/latest)
42+
2. Drag **Local Lens** to Applications
43+
3. **Fix Gatekeeper** (required for unsigned apps):
44+
- Download and double-click `Fix_Local_Lens.command` from the release
45+
- Or: Right-click the app → Open → Click "Open"
46+
- Or run in Terminal:
47+
```bash
48+
xattr -cr "/Applications/Local Lens.app" && codesign --force --deep --sign - "/Applications/Local Lens.app"
49+
```
50+
51+
### Windows
52+
Download and run the `.msi` or `.exe` installer from [Releases](https://github.com/ashesbloom/LocalLens/releases/latest).
53+
54+
<br />
55+
3256
## 🖥️ Software Preview
3357

3458
See how Local Lens organizes thousands of photos in seconds:

0 commit comments

Comments
 (0)