Keep using Photos.app like you always do. Attic quietly backs up your originals and edits to an S3 bucket you control. One-way, append-only.
3
fork

Configure Feed

Select the types of activity you want to include in your feed.

Swift 99.7%
Other 0.3%
78 1 25

Clone this repository

https://tangled.org/tijs.org/attic https://tangled.org/did:plc:aq7owa5y7ndc2hzjz37wy7ma/attic
git@tangled.org:tijs.org/attic git@tangled.org:did:plc:aq7owa5y7ndc2hzjz37wy7ma/attic

For self-hosted knots, clone URLs may differ based on your setup.

Download tar.gz
README.md

attic logo

Attic#

Back up your iCloud Photos library to S3-compatible storage.

Attic reads your Photos library via PhotoKit, enriches metadata from Photos.sqlite, exports originals with SHA-256 hashing, and uploads them to an S3-compatible bucket. A manifest on S3 tracks what has already been backed up so subsequent runs only upload new assets.

Uses LadderKit for PhotoKit access and AppleScript fallback for iCloud-only assets.

Works with any S3-compatible provider. EU-friendly options include Scaleway, Hetzner, and OVH.

Install#

brew install tijs/tap/attic

From source (requires Swift 6.x, macOS 14+)#

git clone https://github.com/tijs/attic.git
cd attic
swift build -c release
sudo cp .build/release/AtticCLI /usr/local/bin/attic

Prerequisites#

  • macOS 14+ (Sonoma), Apple Silicon
  • An S3-compatible storage bucket and API credentials

Permissions#

On first run, macOS will show permission dialogs for:

  • Photos library access — required to read your photo/video assets
  • Keychain access — required to read stored S3 credentials

Both are one-time prompts. Click "Allow" or "Always Allow" to proceed. These permissions can be reviewed in System Settings → Privacy & Security.

Setup#

Run the interactive setup:

attic init

This prompts for your S3 endpoint, region, bucket name, and credentials. Config is saved to ~/.attic/config.json and credentials are stored in the macOS Keychain.

Commands#

init#

Interactive setup — configure S3 connection and store credentials.

attic init

scan#

Scan the Photos library and print statistics (asset counts, types, favorites, edits).

attic scan

status#

Compare the Photos library against the S3 manifest to show how many assets are backed up vs pending.

attic status

backup#

Export pending assets and upload originals + metadata JSON to S3.

attic backup
Flag Description
--dry-run Show what would be uploaded without uploading
--limit N Stop after N assets (useful for test runs)
--batch-size N Assets per export batch (default: 50)
--type photo|video Only back up photos or videos

During a backup, a live-updating terminal dashboard shows progress, speed, current file, and elapsed time. Non-TTY output (pipes, CI) falls back to line-by-line progress.

verify#

Verify backup integrity by confirming every manifest entry exists in S3.

attic verify
Flag Description
--concurrency N Concurrent requests (default: 20)

refresh-metadata#

Re-upload metadata JSON for already backed-up assets without re-uploading the original files. Useful after adding new metadata fields.

attic refresh-metadata
Flag Description
--dry-run Show what would be uploaded
--concurrency N Concurrent uploads (default: 20)

rebuild#

Rebuild the manifest from S3 metadata files (disaster recovery).

attic rebuild

Configuration#

Attic stores its configuration at ~/.attic/config.json:

{
  "endpoint": "https://s3.fr-par.scw.cloud",
  "region": "fr-par",
  "bucket": "my-photo-backup",
  "pathStyle": true,
  "keychain": {
    "accessKeyService": "attic-s3-access-key",
    "secretKeyService": "attic-s3-secret-key"
  }
}

The keychain section is optional and defaults to the service names shown above. Credentials are always stored in the macOS Keychain, never in config files or environment variables.

scan works without config (it only reads the Photos library). All other commands require config and S3 credentials — run attic init if missing.

Architecture#

Photos Library → PhotoKit (LadderKit) → AssetInfo[]
                                           ↓
                               BackupPipeline (AtticCore)
                                   ↓              ↓
                             S3 upload      Manifest update

The project is a Swift package with three targets:

  • AtticCore — shared library: S3 provider, manifest, config, keychain, metadata, backup/verify/refresh pipelines. Designed for reuse by both the CLI and a planned macOS menu bar app.
  • AtticCLI — executable: ArgumentParser commands, terminal renderer
  • AtticCoreTests — tests using Swift Testing framework

All external dependencies are behind protocols (S3Providing, ManifestStoring, ConfigProviding, KeychainProviding, ExportProviding) for testability.

Development#

swift build                    # Build
swift test                     # Run tests
swift build -c release         # Release build
swift test --filter "testName" # Run single test

Tests use dependency injection with mock implementations (MockS3Provider, MockExportProvider) — no external services or credentials needed.

Dependencies#

Documentation#

  • Architecture — How attic works: the backup pipeline, photo library access, manifest lifecycle, and design boundaries
  • Asset Metadata — Schema reference for the per-asset JSON uploaded to S3