Back up live macOS media libraries into sealed, verifiable archives, on a schedule, without quitting the app that owns the library.
macOS 15+ Β· Apple Silicon Β· MIT licensed
π User guide β install, jobs, formats, encryption, restoring, and troubleshooting.
The hard part of backing up a media library is not the copy. It is taking a consistent point-in-time copy of a library whose database is being written to while the backup runs. If you zip a .photoslibrary while Photos has its database open, you can seal a half-written, corrupt database into the archive. The backup looks fine until the day you need it.
Cryoframe avoids that with APFS snapshots. For each run it freezes the volume, mounts the snapshot read-only, archives the library from that frozen copy, then deletes the snapshot. The library stays open the whole time, and the archive is a consistent moment in time.
It also verifies. Every archive gets a checksum manifest, and the strong mode mounts the finished archive and runs a database integrity check, so you find out a backup is bad now instead of during a restore.
- Consistent snapshots of live libraries using APFS, created and torn down per run.
- Several libraries per job, archived from one snapshot into their own subfolders, so a job captures a consistent set in a single pass.
- Several destinations per job (the 3-2-1 rule): a local drive plus a NAS plus a cloud-sync folder, all from the same snapshot. Sealed archives are compressed once and copied to each. The primary must be reached; a downed secondary finishes the run as a partial backup instead of failing it.
- Three output formats: an incremental sparsebundle mirror that only rewrites the bands that changed (the default), or a sealed zip or DMG β immutable, checksummed, split into volumes when the target caps file size.
- Resumable transfers to network shares and external drives: the archive ships in part files and picks up from the last whole part after a dropped connection or unplugged drive.
- Restore built in: find an archive, verify it, and copy the library back out with its original folder name β beside the live one, or in place over it (staged and verified first, with the old copy moved to the Trash). Browse inside an archive and extract just the files you need.
- Optional AES-256 encryption for sealed-DMG and live-mirror archives, with the passphrase kept in the Keychain so scheduled runs encrypt without prompting.
- Versioned sealed archives with a retention policy β keep all, the last N, or a daily/weekly/monthly scheme β so you can restore a point in time without the disk filling up.
- Verification built in: a checksum manifest on every archive, plus an optional mount-and-open check that confirms the library's database opens clean.
- Archive health monitoring: re-hash existing archives against their manifests on demand or on a weekly/monthly schedule, to catch bit rot before a restore needs them β works on encrypted archives with no passphrase.
- Restore drills: a deeper check that reassembles, mounts or extracts, and reopens each archive (a database integrity check), proving the restore path itself works β not just that the bytes match.
- Remote alerts over ntfy or a webhook (Slack/Discord/custom), so an unattended Mac whose backups are failing can push a message to your phone, independent of local notifications.
- Recovery-key escrow: export every archive passphrase into one master-password-encrypted file (PBKDF2 + AES-GCM), so encrypted backups survive a lost Mac.
- Storage overview: per-job, per-version sizes against the free space on each destination volume.
- In-app updates over an Ed25519-signed appcast, and a first-run walkthrough for the helper and Full Disk Access.
- Targets for local disks, network shares, and cloud-sync folders, each with its own size cap and an availability preflight so a run never starts against an unmounted drive.
- Cloud-sync aware: detects OneDrive/Dropbox/Google Drive/Box/iCloud folders, splits sealed archives under the plan's single-file limit, and skips offloaded (placeholder) archives during scheduled checks instead of silently re-downloading them.
- Run jobs concurrently up to a configurable limit, with live progress β speed, time elapsed, and time remaining β and pause, resume, or stop a run in flight.
- Durable run history: every run, manual or scheduled, is recorded with its outcome, per-library detail, duration, size, and any error, and survives quitting the app.
- Scheduling through a launchd agent, with per-job control over what happens if the owning app is open.
- Keeps the Mac awake while a backup runs, and can optionally wake it for a scheduled run, so unattended backups actually finish.
- A menu-bar status item and notifications, so you know at a glance whether the last run β including scheduled ones β succeeded.
- Owns its snapshots end to end. It never touches Time Machine's snapshots.
Built in (fixed locations, detected automatically):
| Library | Location | Owning app |
|---|---|---|
| Photos | ~/Pictures/Photos Library.photoslibrary |
Photos |
| Apple Music | ~/Music/Music/Music Library.musiclibrary |
Music |
| iMovie | ~/Movies/iMovie Library.imovielibrary |
iMovie |
| GarageBand | ~/Music/GarageBand |
GarageBand |
| Messages | ~/Library/Messages |
Messages |
~/Library/Mail |
||
| Microsoft Outlook | default Outlook profile | Outlook |
Templates (you point at the library, since these live anywhere β often on external drives):
- Final Cut Pro libraries
- Lightroom Classic catalogs
- Capture One catalogs
- Logic Pro projects
Anything else: point at any folder with "Add library", and it is treated as static content.
A built-in library kept somewhere other than its default location β an external drive, say β can be repointed in Settings βΈ Libraries. The owning app and integrity check stay attached.
Grab Cryoframe-x.y.z.dmg from Releases. It is signed with a Developer ID and notarized, so it opens with no Gatekeeper warnings. Open the DMG and drag Cryoframe to Applications.
brew install xcodegen
git clone https://github.com/breed007/Cryoframe.git
cd Cryoframe
xcodegen generate
open Cryoframe.xcodeproj
Set DEVELOPMENT_TEAM in project.yml to your own Team ID first β the privileged helper will not register without a Developer ID. The .xcodeproj is generated and gitignored; edit project.yml, never the project file. To build, sign, and install in one step:
./scripts/build-and-install.sh
Three one-time steps, shown at the top of the window:
- Enable the helper. This installs the background service that takes snapshots. Approve it in System Settings βΈ Login Items when asked, and authenticate (installing a root service needs admin).
- Grant Full Disk Access to Cryoframe, then relaunch. The dot turns green once it can read protected libraries. Full Disk Access is required because a snapshot of Photos content is still Photos content as far as macOS privacy controls are concerned.
- Enable the schedule if you want jobs to run in the background.
Press New Job and pick a library, a destination, a format, and how often to run. Every field has a tooltip.
Formats:
- Live mirror (default). A sparsebundle with about 8 MB bands. The first run copies everything; later runs only write the bands that changed.
- Sealed zip or DMG. One immutable, checksummed file for cold storage. Larger than the target's cap splits into volumes, so it fits cloud single-file limits.
Verification:
- Checksum hashes every archive after writing. Always on. A manifest (
cryoframe-manifest.json) is written next to the archive. - Mount and open also mounts the finished archive and runs a SQLite integrity check on the library's database. This is the strong check for live-database libraries.
Schedule and run policy:
- Daily at a set time, every N hours, once, or manual.
- "If app is open" controls what happens when the owning app is running. The default is proceed, because the snapshot is already consistent. Choose warn or defer if you would rather skip a run while the library is in use.
The Help button in the app has worked examples for Apple Photos and Apple Music.
app (you, Full Disk Access) helper (root LaunchDaemon)
βββββββββββββββββββββββββββ ββββββββββββββββββββββββββ
create snapshot ββββ XPC βββββΆ freeze the Data volume
mount snapshot ββββ XPC βββββΆ mount read-only
βββββ MountRef ββββββββββββ
read the frozen library, archive it
unmount + delete ββββ XPC βββββΆ tear down
The privilege split is the core of the design. A root helper takes, mounts, and deletes the snapshot. The app reads the frozen library and writes the archive, running as you with Full Disk Access. Root does not bypass macOS privacy controls, so the reader needs Full Disk Access; the helper rides the app's grant for the snapshot mount.
Snapshot create runs through tmutil localsnapshot, which needs no special entitlement. The raw fs_snapshot_create syscall would need an Apple-granted entitlement that root alone does not satisfy, so Cryoframe uses the path that works for everyone. The snapshot is mounted immediately after creation, which pins it for the run regardless of how Time Machine thins its own snapshots.
CryoframeSharedβ the XPC contract and shared typesCryoframeKitβ the engine: snapshot backends, content-type registry, archive engines, verification, targets, scheduling (covered by unit tests that run with fakes, so no root or snapshot is needed)CryoframeHelperβ the root LaunchDaemonCryoframeβ the SwiftUI appspike/β the throwaway spikes that proved the snapshot and syscall approachdocs/guide/β the user guide;docs/also holds design notesscripts/β build, install, icon, notarize, and DMG scripts
xcodegen generate
xcodebuild test -scheme Cryoframe-Core -destination 'platform=macOS'
The engine is fully unit-tested without root. The archive and strong-verify tests shell out to hdiutil, ditto, and sqlite3 against tiny fixtures, so the suite takes about half a minute.
Maintainer steps to cut a notarized release:
xcrun notarytool store-credentials cryoframe-notary \
--apple-id <your-apple-id> --team-id <your-team-id> --password <app-specific-password> # once
./scripts/notarize.sh # signed Release build, notarize, staple the app
./scripts/make-dmg.sh # wrap in a DMG, notarize and staple the DMG
The build number is stamped with the build time as YYYYMMDD.HHMM on every build. The marketing version is set by hand in project.yml (MARKETING_VERSION).
Cryoframe runs a root LaunchDaemon and reads protected libraries, so it asks for real trust. What it does and does not do:
- The helper runs as root only to create, mount, and delete APFS snapshots. It never reads library contents.
- The app and helper verify each other's code signature on every XPC connection, so only the signed app can talk to the helper.
- Cryoframe writes archives to wherever you point it. It does not upload anything. Cloud backup happens because you wrote the archive into a cloud-sync folder and the sync client uploads it.
- Intel or universal builds. Apple Silicon only.
- The Mac App Store. The root helper, Full Disk Access, and snapshot mounts are incompatible with the App Sandbox.
- A built-in cloud uploader (direct S3/OAuth). Write to a cloud-sync folder instead β Cryoframe detects the provider and splits to its single-file limit.
MIT. See LICENSE.




