···11# Attic
2233-Deno/TypeScript CLI for backing up iCloud Photos to Scaleway S3. Part of the photo-cloud system (companion: [ladder](https://github.com/tijs/ladder)).
33+Deno/TypeScript CLI for backing up iCloud Photos to S3-compatible storage. Part of the photo-cloud system (companion: [ladder](https://github.com/tijs/ladder)).
4455## Commands
6677```bash
88deno task check # Type check
99-deno task test # Run tests (44 tests)
99+deno task test # Run tests (54 tests)
1010deno task lint # Lint
1111deno task fmt # Format
1212deno task fmt:check # Check formatting
···16161717```
1818shared/ # @attic/shared — PhotoAsset type, S3 path helpers
1919-cli/ # @attic/cli — commands, storage, manifest, export
2020- src/commands/ # scan, status, backup, verify, rebuild
2121- src/storage/ # S3 client + Keychain credential loading
1919+cli/ # @attic/cli — commands, config, storage, manifest, export
2020+ src/commands/ # init, scan, status, backup, verify, rebuild
2121+ src/config/ # Config file (load, validate, write)
2222+ src/storage/ # Generic S3 client + Keychain credential loading
2223 src/manifest/ # Local JSON manifest with atomic writes
2324 src/export/ # Exporter interface + ladder subprocess integration
2425```
+39-15
README.md
···4455# Attic
6677-Back up your iCloud Photos library to Scaleway Object Storage (S3-compatible).
77+Back up your iCloud Photos library to S3-compatible storage.
8899-Attic reads the Photos.sqlite database directly, exports originals via a companion Swift tool called [ladder](https://github.com/tijs/ladder), and uploads them to a Scaleway S3 bucket. A local manifest tracks what has already been backed up so subsequent runs only upload new assets.
99+Attic reads the Photos.sqlite database directly, exports originals via a companion Swift tool called [ladder](https://github.com/tijs/ladder), and uploads them to an S3-compatible bucket. A local manifest tracks what has already been backed up so subsequent runs only upload new assets.
1010+1111+Works with any S3-compatible provider. EU-friendly options include [Scaleway](https://www.scaleway.com/en/object-storage/), [Hetzner](https://www.hetzner.com/storage/object-storage), and [OVH](https://www.ovhcloud.com/en/public-cloud/object-storage/).
10121113## Prerequisites
12141315- [Deno](https://deno.land/) (v2+)
1416- The [ladder](https://github.com/tijs/ladder) binary. Ladder is a separate Swift tool that uses PhotoKit to export original photo/video files from the Photos library.
1515-- A Scaleway Object Storage bucket and API credentials
1717+- An S3-compatible storage bucket and API credentials
1618- macOS (Photos.sqlite access and Keychain are macOS-only)
17191820## Setup
19212020-Store your Scaleway S3 credentials in the macOS Keychain:
2222+Run the interactive setup:
21232224```bash
2323-security add-generic-password -s attic-s3-access-key -a attic -w "<your-access-key>"
2424-security add-generic-password -s attic-s3-secret-key -a attic -w "<your-secret-key>"
2525+deno task init
2526```
26272828+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.
2929+2730Build the ladder binary (see [ladder](https://github.com/tijs/ladder) for details):
28312932```bash
···36393740All commands are run via `deno task`:
38413939-### scan
4242+### init
40434141-Scan the Photos library and print statistics (asset counts, sizes, types, local vs iCloud-only).
4444+Interactive setup — configure S3 connection and store credentials.
42454346```bash
4444-deno task scan
4747+deno task init
4548```
46494747-Optionally pass a custom database path:
5050+### scan
5151+5252+Scan the Photos library and print statistics (asset counts, sizes, types, local vs iCloud-only).
48534954```bash
5050-deno task scan /path/to/Photos.sqlite
5555+deno task scan
5156```
52575358### status
···6671deno task backup
6772```
68736969-Flags (append after `--`):
7070-7174| Flag | Description |
7275|---|---|
7376| `--dry-run` | Show what would be uploaded without uploading |
7477| `--limit N` | Back up at most N assets |
7578| `--batch-size N` | Assets per ladder export batch (default: 50) |
7679| `--type photo\|video` | Only back up photos or videos |
7777-| `--bucket NAME` | S3 bucket name (default: `photo-cloud-storage`) |
8080+| `--bucket NAME` | Override bucket from config |
7881| `--ladder PATH` | Path to the ladder binary (or set `LADDER_PATH` env var) |
7982| `--db PATH` | Path to Photos.sqlite |
8083···9093|---|---|
9194| `--deep` | Download each object and re-verify SHA-256 checksum (slow) |
9295| `--rebuild-manifest` | Reconstruct the local manifest from S3 metadata files |
9393-| `--bucket NAME` | S3 bucket name (default: `photo-cloud-storage`) |
9696+| `--bucket NAME` | Override bucket from config |
9797+9898+## Configuration
9999+100100+Attic stores its configuration at `~/.attic/config.json`:
101101+102102+```json
103103+{
104104+ "endpoint": "https://s3.fr-par.scw.cloud",
105105+ "region": "fr-par",
106106+ "bucket": "my-photo-backup",
107107+ "pathStyle": true,
108108+ "keychain": {
109109+ "accessKeyService": "attic-s3-access-key",
110110+ "secretKeyService": "attic-s3-secret-key"
111111+ }
112112+}
113113+```
114114+115115+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.
116116+117117+`scan` and `status` work without config (they only read Photos.sqlite). `backup` and `verify` require config and will tell you to run `attic init` if it's missing.
9411895119## Testing
96120
+8-2
docs/architecture.md
···93939494Both modes use a bounded concurrency pool (default 50 workers). Errors are capped at 1,000 to prevent unbounded memory growth.
95959696+## Configuration
9797+9898+Attic reads its configuration from `~/.attic/config.json`. The config file specifies the S3 endpoint, region, bucket, path-style preference, and Keychain service names. It's created by `attic init` or manually.
9999+100100+`scan` and `status` work without config (they only read Photos.sqlite). `backup` and `verify` require config and fail fast with a clear message if it's missing or invalid.
101101+96102## Credentials
971039898-S3 credentials are stored in the macOS Keychain under service names `attic-s3-access-key` and `attic-s3-secret-key`. They are read at runtime via `security find-generic-password` — never stored in env vars, config files, or code.
104104+S3 credentials are stored in the macOS Keychain under configurable service names (defaults: `attic-s3-access-key` and `attic-s3-secret-key`). They are read at runtime via `security find-generic-password` — never stored in env vars, config files, or code.
99105100106## Interfaces and testability
101107···103109104110| Interface | Real implementation | Mock |
105111|-----------|-------------------|------|
106106-| `S3Provider` | AWS SDK client for Scaleway | In-memory `Map<string, Uint8Array>` |
112112+| `S3Provider` | AWS SDK client for any S3-compatible endpoint | In-memory `Map<string, Uint8Array>` |
107113| `Exporter` | Ladder subprocess | Returns pre-configured assets from a `Map` |
108114| `ManifestStore` | File-based JSON with atomic writes | Same implementation, pointed at a temp dir |
109115| `PhotosDbReader` | SQLite reader for Photos.sqlite | In-memory SQLite with test fixtures |