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.

Add release workflow, Homebrew tap, and installable CLI

+142 -11
+50
.github/workflows/release.yml
··· 1 + name: Release 2 + 3 + on: 4 + push: 5 + tags: 6 + - "v*" 7 + 8 + permissions: 9 + contents: write 10 + 11 + jobs: 12 + release: 13 + runs-on: macos-latest 14 + steps: 15 + - uses: actions/checkout@v4 16 + 17 + - uses: denoland/setup-deno@v2 18 + with: 19 + deno-version: v2.x 20 + 21 + - name: Run tests 22 + run: deno task test 23 + 24 + - name: Get version from tag 25 + run: echo "VERSION=${GITHUB_REF_NAME#v}" >> "$GITHUB_ENV" 26 + 27 + - name: Compile binaries 28 + run: | 29 + deno compile -A --target aarch64-apple-darwin --output attic-aarch64-apple-darwin cli/mod.ts 30 + deno compile -A --target x86_64-apple-darwin --output attic-x86_64-apple-darwin cli/mod.ts 31 + 32 + - name: Package archives 33 + run: | 34 + tar czf "attic-${VERSION}-aarch64-apple-darwin.tar.gz" attic-aarch64-apple-darwin 35 + tar czf "attic-${VERSION}-x86_64-apple-darwin.tar.gz" attic-x86_64-apple-darwin 36 + 37 + - name: Generate checksums 38 + run: | 39 + shasum -a 256 attic-"${VERSION}"-*.tar.gz > checksums.txt 40 + cat checksums.txt 41 + 42 + - name: Create GitHub Release 43 + env: 44 + GH_TOKEN: ${{ github.token }} 45 + run: | 46 + gh release create "$GITHUB_REF_NAME" \ 47 + --generate-notes \ 48 + "attic-${VERSION}-aarch64-apple-darwin.tar.gz" \ 49 + "attic-${VERSION}-x86_64-apple-darwin.tar.gz" \ 50 + checksums.txt
+37 -10
README.md
··· 16 16 [Hetzner](https://www.hetzner.com/storage/object-storage), and 17 17 [OVH](https://www.ovhcloud.com/en/public-cloud/object-storage/). 18 18 19 + ## Install 20 + 21 + ### Homebrew (recommended) 22 + 23 + ```bash 24 + brew install tijs/tap/attic 25 + ``` 26 + 27 + ### From source (requires Deno v2+) 28 + 29 + ```bash 30 + git clone https://github.com/tijs/attic.git 31 + cd attic 32 + deno task install 33 + ``` 34 + 35 + This installs `attic` to `~/.deno/bin/`. Make sure that's on your PATH. 36 + 19 37 ## Prerequisites 20 38 21 - - [Deno](https://deno.land/) (v2+) 22 39 - The [ladder](https://github.com/tijs/ladder) binary. Ladder is a separate 23 40 Swift tool that uses PhotoKit to export original photo/video files from the 24 41 Photos library. ··· 30 47 Run the interactive setup: 31 48 32 49 ```bash 33 - deno task init 50 + attic init 34 51 ``` 35 52 36 53 This prompts for your S3 endpoint, region, bucket name, and credentials. Config ··· 48 65 49 66 ## Commands 50 67 51 - All commands are run via `deno task`: 52 - 53 68 ### init 54 69 55 70 Interactive setup — configure S3 connection and store credentials. 56 71 57 72 ```bash 58 - deno task init 73 + attic init 59 74 ``` 60 75 61 76 ### scan ··· 64 79 vs iCloud-only). 65 80 66 81 ```bash 67 - deno task scan 82 + attic scan 68 83 ``` 69 84 70 85 ### status ··· 73 88 assets are backed up vs pending. 74 89 75 90 ```bash 76 - deno task status 91 + attic status 77 92 ``` 78 93 79 94 ### backup ··· 81 96 Export pending assets via ladder and upload originals + metadata JSON to S3. 82 97 83 98 ```bash 84 - deno task backup 99 + attic backup 85 100 ``` 86 101 87 102 | Flag | Description | ··· 99 114 Verify backup integrity by checking S3 objects against the manifest. 100 115 101 116 ```bash 102 - deno task verify 117 + attic verify 103 118 ``` 104 119 105 120 | Flag | Description | ··· 114 129 original files. Useful after adding new metadata fields or enrichments. 115 130 116 131 ```bash 117 - deno task refresh-metadata 132 + attic refresh-metadata 118 133 ``` 119 134 120 135 | Flag | Description | ··· 149 164 `scan` and `status` work without config (they only read Photos.sqlite). `backup` 150 165 and `verify` require config and will tell you to run `attic init` if it's 151 166 missing. 167 + 168 + ## Development 169 + 170 + If you're working on attic itself, use `deno task` to run commands from source: 171 + 172 + ```bash 173 + deno task check # Type check 174 + deno task test # Run tests 175 + deno task lint # Lint 176 + deno task fmt # Format 177 + deno task compile # Build standalone binary 178 + ``` 152 179 153 180 ## Testing 154 181
+3 -1
deno.json
··· 11 11 "status": "deno run --allow-read --allow-write --allow-env --allow-ffi cli/mod.ts status", 12 12 "backup": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-ffi --allow-net --allow-run cli/mod.ts backup", 13 13 "verify": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-net --allow-run=security cli/mod.ts verify", 14 - "refresh-metadata": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-ffi --allow-net --allow-run=security cli/mod.ts refresh-metadata" 14 + "refresh-metadata": "deno run --allow-read --allow-write --allow-env --allow-sys --allow-ffi --allow-net --allow-run=security cli/mod.ts refresh-metadata", 15 + "install": "deno install -g -A --name attic cli/mod.ts", 16 + "compile": "deno compile -A --output attic cli/mod.ts" 15 17 } 16 18 }
+52
scripts/update-homebrew.sh
··· 1 + #!/bin/bash 2 + # Update the Homebrew formula after a new release. 3 + # Usage: ./scripts/update-homebrew.sh 0.1.0 4 + 5 + set -euo pipefail 6 + 7 + VERSION="${1:?Usage: $0 <version>}" 8 + REPO="tijs/attic" 9 + TAP_REPO="tijs/homebrew-tap" 10 + FORMULA="Formula/attic.rb" 11 + 12 + echo "Fetching checksums for v${VERSION}..." 13 + 14 + ARM_URL="https://github.com/${REPO}/releases/download/v${VERSION}/attic-${VERSION}-aarch64-apple-darwin.tar.gz" 15 + X86_URL="https://github.com/${REPO}/releases/download/v${VERSION}/attic-${VERSION}-x86_64-apple-darwin.tar.gz" 16 + 17 + ARM_SHA=$(curl -sL "$ARM_URL" | shasum -a 256 | cut -d' ' -f1) 18 + X86_SHA=$(curl -sL "$X86_URL" | shasum -a 256 | cut -d' ' -f1) 19 + 20 + echo "ARM64 SHA256: ${ARM_SHA}" 21 + echo "x86_64 SHA256: ${X86_SHA}" 22 + 23 + TMPDIR=$(mktemp -d) 24 + gh repo clone "$TAP_REPO" "$TMPDIR" 25 + 26 + cd "$TMPDIR" 27 + 28 + # Update version 29 + sed -i '' "s/version \".*\"/version \"${VERSION}\"/" "$FORMULA" 30 + 31 + # Update ARM64 sha256 (first PLACEHOLDER or sha256 after arm? block) 32 + # Use awk for precise replacement 33 + awk -v arm="$ARM_SHA" -v x86="$X86_SHA" ' 34 + /Hardware::CPU.arm\?/ { in_arm=1 } 35 + /else/ { in_arm=0; in_x86=1 } 36 + /sha256/ && in_arm { sub(/sha256 ".*"/, "sha256 \"" arm "\""); in_arm=0 } 37 + /sha256/ && in_x86 { sub(/sha256 ".*"/, "sha256 \"" x86 "\""); in_x86=0 } 38 + { print } 39 + ' "$FORMULA" > "$FORMULA.tmp" && mv "$FORMULA.tmp" "$FORMULA" 40 + 41 + echo "" 42 + echo "Updated formula:" 43 + cat "$FORMULA" 44 + echo "" 45 + 46 + git add "$FORMULA" 47 + git commit -m "attic ${VERSION}" 48 + git push origin main 49 + 50 + rm -rf "$TMPDIR" 51 + 52 + echo "Done. Homebrew formula updated to v${VERSION}."