Description
cairn reconciles your iPhone photo library against your own Immich server. When you delete a photo on your phone, cairn moves the matching photo on Immich to Trash.
That's the whole job. cairn doesn't upload photos, show albums, edit metadata, or run AI — the Immich app already does those things. cairn closes the one loop the Immich app doesn't: photos that live on the server after you've deleted them from your phone.
HOW IT WORKS
— Content identity is SHA1 (the same identifier Immich uses server-side).
— cairn subscribes to iOS's deletion log, so it sees soft-deletes as you make them.
— A configurable quarantine window holds confirmed deletions before anything moves, so an accidental mass-offload has time to be caught.
— Every run shows you the candidate list first. Nothing happens on the server until you confirm.
SAFETY MODEL
— Trash, not delete. cairn moves assets into your Immich Trash — Immich retains them for 30 days, and restore is one tap.
— Percent cap + floor. If a single run would move more than a threshold of your matched photos to Trash, it aborts without touching the server.
— Breadcrumbs. Every run is tagged on Immich (cairn/v1/run/[id]) so you can find it server-side.
— Forensic journal. Local append-only log records every step — planned, tagged, trashed, restored, failed.
— Exclusions. Protect specific photos from ever being flagged.
— Indexing scope. Restrict cairn to a specific set of Photos albums. Photos outside the scope are silently ignored — never hashed, never proposed for trash. Useful if you want to manage just one album and leave synced family albums alone.
PRIVACY
— No analytics, no telemetry, no crash reporting, no ads.
— No cairn backend. Your iPhone talks directly to your Immich server using your API key.
— Credentials stay in the iOS Keychain. Nothing leaves your device besides requests you configured.
REQUIREMENTS
— An Immich server you run or control.
— An Immich API key. cairn requests these scopes: asset.read, asset.view, asset.download, asset.delete, tag.create, tag.asset, tag.read. The Setup screen lists them when you paste your key.
cairn is not affiliated with the Immich project. It talks to Immich over its public API; compatibility only.
Source is MIT-licensed at github.com/glarue/cairn.
What's new (v0.3.2)
Sync detail: per-asset hashing visibility. When a sync stalls on a large iCloud video, the sync detail sheet now shows the asset being downloaded — filename, total bytes, elapsed time once it crosses ~3s, plus a 0–100% download progress bar while iCloud is fetching the bytes. Replaces the previous "N of M hashed" counter that gave no signal about what was actually slow.
Reliability: photos you viewed in Photos.app no longer pile up in the deferred queue. PhotoKit bumps the modificationDate when iOS materializes an asset from iCloud for display, and cairn previously read any modDate change as needing a re-hash — a chain of view → re-hash → iCloud re-download → 60s timeout → defer with "timed out." Cairn now skips the re-hash when the asset's primary resource size hasn't changed; real edits still re-hash because the editor adds an adjustment resource that changes the size.
Reliability: transient onboarding-screen flash on launch. iOS occasionally returns "item not found" for Keychain reads that should succeed during the brief post-launch window — most commonly after a force-quit + quick relaunch. Cairn previously routed straight to onboarding on the first "not found"; the retry now covers "not found" for ~400ms before treating it as truly missing. Fresh installs and sign-outs still route correctly, just after the brief retry.