Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
xcrun llvm-cov export \
-format=lcov \
-instr-profile "$BIN/codecov/default.profdata" \
"$BIN/lifsaverPackageTests.xctest/Contents/MacOS/lifsaverPackageTests" \
"$BIN/LifsaverPackageTests.xctest/Contents/MacOS/LifsaverPackageTests" \
-ignore-filename-regex='(Tests|\.build)/' > coverage.lcov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7
Expand Down Expand Up @@ -124,7 +124,7 @@ jobs:
run: |
./scripts/release/bundle_app.sh
plutil -lint dist/Lifsaver.app/Contents/Info.plist
test -x dist/Lifsaver.app/Contents/Helpers/lifsaver
test -x dist/Lifsaver.app/Contents/MacOS/Lifsaver
- name: Package installer
run: ./scripts/release/make_pkg.sh
- name: Upload installer for per-OS install tests
Expand Down Expand Up @@ -169,16 +169,15 @@ jobs:
path: dist
- name: Install pkg
run: sudo installer -pkg dist/lifsaver_installer_macos.pkg -target /
- name: Verify installed layout, signature, and CLI
- name: Verify installed layout, signature, and version
run: |
echo "→ $(sw_vers -productVersion) $(uname -m)"
test -d /Applications/Lifsaver.app
test -L /usr/local/bin/lifsaver
/usr/bin/codesign --verify --strict /Applications/Lifsaver.app
VERSION="$(./scripts/version.sh)"
INSTALLED="$(/usr/local/bin/lifsaver --version)"
INSTALLED="$(plutil -extract CFBundleShortVersionString raw /Applications/Lifsaver.app/Contents/Info.plist)"
if [ "$INSTALLED" != "$VERSION" ]; then
echo "ERROR: installed CLI reports $INSTALLED, expected $VERSION" >&2
echo "ERROR: installed app reports $INSTALLED, expected $VERSION" >&2
exit 1
fi
- name: Launch app and verify it stays up
Expand Down
11 changes: 5 additions & 6 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -120,15 +120,14 @@ jobs:
run: |
brew tap lucuma13/dit
brew install --cask lucuma13/dit/lifsaver
- name: Smoke test CLI and app bundle
- name: Smoke test
env:
VERSION: ${{ needs.build-and-upload.outputs.version }}
run: |
echo "→ lifsaver --version"
INSTALLED="$(lifsaver --version)"
test -d /Applications/Lifsaver.app
/usr/bin/codesign --verify --strict /Applications/Lifsaver.app
INSTALLED="$(plutil -extract CFBundleShortVersionString raw /Applications/Lifsaver.app/Contents/Info.plist)"
if [ "$INSTALLED" != "$VERSION" ]; then
echo "ERROR: installed CLI reports $INSTALLED, expected $VERSION" >&2
echo "ERROR: installed app reports $INSTALLED, expected $VERSION" >&2
exit 1
fi
test -d /Applications/Lifsaver.app
/usr/bin/codesign --verify --strict /Applications/Lifsaver.app
3 changes: 1 addition & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -113,8 +113,7 @@ fastlane/Preview.html
fastlane/screenshots/**/*.png
fastlane/test_output

# Local Xcode/SwiftPM user state. Package.resolved is committed, so the
# template's blanket ".swiftpm" suggestion is not used as-is.
# Local Xcode/SwiftPM user state.
.swiftpm/
DerivedData/

Expand Down
5 changes: 5 additions & 0 deletions .swiftlint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,11 @@ included:
line_length:
warning: 120
ignores_urls: true
# --strict promotes warnings to errors, so raise the warning threshold itself.
file_length:
warning: 1000
type_body_length:
warning: 350
identifier_name:
excluded: [ok, fs] # mount-outcome case/counters; filesystem-type local
disabled_rules:
Expand Down
15 changes: 0 additions & 15 deletions Package.resolved

This file was deleted.

26 changes: 6 additions & 20 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,17 @@
import PackageDescription

let package = Package(
name: "lifsaver",
name: "Lifsaver",
platforms: [.macOS(.v13)],
dependencies: [
.package(url: "https://github.com/apple/swift-argument-parser", from: "1.5.0")
],
targets: [
.target(name: "LifsaverCore"),
.target(name: "LifsaverKit"),
.executableTarget(
name: "lifsaver",
dependencies: [
"LifsaverCore",
.product(name: "ArgumentParser", package: "swift-argument-parser"),
]
),
.executableTarget(
name: "LifsaverApp",
dependencies: ["LifsaverCore"]
),
.testTarget(
name: "LifsaverCoreTests",
dependencies: ["LifsaverCore"]
name: "Lifsaver",
dependencies: ["LifsaverKit"]
),
.testTarget(
name: "LifsaverCLITests",
dependencies: ["lifsaver"]
name: "LifsaverKitTests",
dependencies: ["LifsaverKit"]
),
]
)
27 changes: 4 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,13 @@
[![CI](https://github.com/lucuma13/lifsaver/actions/workflows/ci.yml/badge.svg)](https://github.com/lucuma13/lifsaver/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/lucuma13/lifsaver/graph/badge.svg?token=88HT6VMLHO)](https://codecov.io/gh/lucuma13/lifsaver)

<img src="docs/images/lifsaver_logo_name.svg" width="100" height="100" align="left" hspace="10"/>
<img src="docs/images/lifsaver_logo_name.svg" width="110" align="left" hspace="10"/>

`lifsaver` addresses a macOS bug on LIFS (Live Image File System) which prevents multiple cards from mounting when they have the same name (e.g. "Untitled"). It lives in your menu bar and watches for cards that appear but never mount, when that happens you can force-mount the stalled card with two clicks. Please note that whereas this utility has been fully tested, you should use it at your own risk.
`lifsaver` addresses a macOS bug on LIFS (Live Image File System) which prevents multiple cards from mounting when they have the same name (e.g. "Untitled"). It watches for cards that appear but never mount, and let's you force mount them with two clicks. Please note that whereas this utility has been fully tested, you should use it at your own risk.

<br clear="left"/>

<p align="center">
<img src="docs/images/demo.gif" width="800" alt="lifsaver detects a stalled card, then mounts it from the menu bar with one click"/>
</p>
<img src="docs/images/demo.gif" width="100%"/>

### 🚀 Installation

Expand All @@ -24,26 +22,9 @@ brew install --cask lucuma13/dit/lifsaver

The installer is not notarized – macOS will warn on first open: right-click the .pkg → Open, or allow it under System Settings → Privacy & Security.

### 📖 Usage

**Menu bar app**. The app watches disk activity and flags any volume that appeared but never mounted:

- The app icon turns orange and a *"Stalled volume detected"* notification appears. Click it, enter your administrator password in the standard macOS dialog, and the cards appear in Finder.
- The menu shows the same state at any time: *"Mount 2 stalled volumes"* when cards are stuck, *"No stalled volumes detected"* otherwise.

**CLI** — same engine, same safety interlocks:

```bash
lifsaver # scan (read-only), confirm, then mount via sudo
lifsaver --verbose # show the full mount sequence and raw mount errors
```

### 🐞 Reporting bugs

If `lifsaver` misses a stalled card or fails to mount one, please send a diagnostic report:

- **Menu bar app** — choose *Save Diagnostic Report…* from the menu, optionally describe what happened, save the file, then click *Email Report* and send it to me (or open a [GitHub issue](https://github.com/lucuma13/lifsaver/issues/new)).
- **CLI** — run `lifsaver report`; it saves the report to your `~/Downloads` folder, then email it to me (or open a [GitHub issue](https://github.com/lucuma13/lifsaver/issues/new)).
If the app misses a stalled card or fails to mount it, please send a diagnostic report: choose *Send Diagnostic Report* from the menu, optionally describe what happened, save the file, then click *Email Report* and send it to me (or open a [GitHub issue](https://github.com/lucuma13/lifsaver/issues/new)).


### ⚠️ Disclaimer
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import AppKit
import LifsaverCore
import LifsaverKit

/// UI flow behind the "Save Diagnostic Report…" menu item: ask for an
/// optional description, pick a destination, then gather the report and
/// reveal the saved file in Finder.
@MainActor
enum DiagnosticReportFlow {
static func begin(appEvents: [String]) {
static func begin(appEvents: [String], liveLog: [String]) {
// A menu bar app is never frontmost; without activating, the alert
// and save panel open behind whatever the user is working in.
NSApp.activate(ignoringOtherApps: true)
Expand All @@ -20,9 +20,9 @@ enum DiagnosticReportFlow {

Task {
let reporter = DiagnosticsReporter(runner: DefaultProcessRunner())
let report = await reporter.generate(userNote: note, appEvents: appEvents)
let report = await reporter.generate(userNote: note, appEvents: appEvents, liveLog: liveLog)
do {
try report.write(to: url, atomically: true, encoding: .utf8)
try report.jsonString().write(to: url, atomically: true, encoding: .utf8)
NSWorkspace.shared.activateFileViewerSelecting([url])
offerEmail(for: url)
} catch {
Expand Down
Loading