# Mac Dropbox rsync backup — prompt for Claude Code

> **How to use this file:** open it, edit the **Configuration** block to
> match your setup, then copy the whole thing and paste it into Claude
> Code or Codex on your Mac. It needs to be Claude Code or Codex (or another AI that can
> run shell commands on your machine) — a chat-only AI can't actually do
> the install for you.
>If you don't use Dropbox, then do a search and replace in this prompt file and replace `Dropbox` with `Google Drive` or `OneDrive`. 

---

```
## What I want

I want to set up an automatic, scheduled, one-way backup of a folder on
my Mac to my Dropbox folder, using `rsync` and `launchd`. The backup
should:

- run every couple of hours, automatically, even if I'm not actively
  using my Mac
- mirror the folder one-way (with `--delete`), so files I remove from
  the source also disappear from the destination — Dropbox's delete history is my safety net
- write a log I can spot-check whenever I want
- not show up as a flashing Dock icon every time it runs

I'm a bit beyond a beginner but I'm not a developer. I want the simplest
working approach, using only tools that are already built into macOS —
no Homebrew, no third-party apps.

## Configuration — fill these in before you paste this prompt

- **Source folder to back up:** `/Users/<my-username>/<folder-to-back-up>`
- **Destination folder in Dropbox:** `/Users/<my-username>/Library/CloudStorage/Dropbox/<where-to-mirror-it>`
- **How often:** every `2` hours (or whatever interval I want — in seconds for `StartInterval`)
- **Friendly name for the wrapper app:** something like `My Obsidian Folder Backup`
- **Reverse-DNS bundle identifier for the launchd job:** something like `com.<my-name>.<JobName>`

## Important things a friend learned doing this — please don't repeat these dead ends

A friend of mine set this up first and walked into three traps in a row.
The fix is well known once you know it, but it cost him a couple of
hours of "why is my FDA grant not working?" Here's the summary so we
don't go in circles:

### 1. Modern Dropbox lives under `~/Library/CloudStorage/`, which is TCC-protected

On macOS Ventura and later, the Dropbox folder is at
`~/Library/CloudStorage/Dropbox/...`. That whole `CloudStorage` tree is
protected by macOS's TCC (Privacy & Security) layer. A plain shell
script run by `launchd` cannot write into it. You'll see
`rsync error: ...: open: Operation not permitted` in the log every run.

### 2. You **cannot** grant Full Disk Access to `/bin/bash`

macOS deliberately blocks the shells in `/bin`, `/usr/bin`, `/sbin`, and
`/usr/sbin` from being added to Full Disk Access via the System Settings
GUI. Don't try — it's blocked on purpose to prevent privilege
escalation. Don't waste time on it.

### 3. The fix is to wrap the script in a tiny AppleScript `.app`

`osacompile` will turn a one-line AppleScript that says
`do shell script "/path/to/your/rsync-script.sh"` into a real `.app`
bundle. `.app` bundles **can** be added to Full Disk Access, and the
FDA grant propagates to the rsync child process. This is the standard
workaround; it works.

### 4. Two non-obvious gotchas with the wrapper `.app`

These are the ones that actually cost the time. Both must be done or
the FDA grant is silently ignored even though the toggle in System
Settings looks "on":

- **`osacompile` does NOT set a `CFBundleIdentifier`.** Without one, TCC
  can't reliably tie the FDA grant to the app. Add one with
  `/usr/libexec/PlistBuddy` after compiling — something like
  `com.<my-name>.<JobName>`.

- **The default ad-hoc signature does NOT bind the `Info.plist`.** The
  moment you modify the `Info.plist` (e.g. to add `LSBackgroundOnly`
  so it doesn't flash a Dock icon), the signature is invalidated and
  macOS treats the app as having an inconsistent identity. Re-sign
  with `codesign --force --deep --sign -` AFTER the Info.plist edits,
  to bind the signature to the modified Info.plist.

The symptom of skipping either of those: the app appears in the FDA
list with the toggle on, you reload the launchd job, and rsync still
fails with `Operation not permitted`. Maddening to debug. Just do them.

## What I want you to actually build

Please set up everything end-to-end. Specifically:

1. Write a small `rsync` shell script at `~/Scripts/<bundle-id>.sh` that
   uses `rsync -av --delete --exclude '.DS_Store'` from the source to
   the destination, with a log file at `~/Scripts/<bundle-id>.log.txt`.
2. Build a wrapper app at `~/Applications/<friendly name>.app` with
   `osacompile -e 'do shell script "..."'`.
3. Add a stable `CFBundleIdentifier` and `LSBackgroundOnly = true` to
   the app's `Info.plist` using `PlistBuddy`.
4. Re-sign the app with `codesign --force --deep --sign -` so the
   signature binds the modified `Info.plist`.
5. Register the app with Launch Services (`lsregister -f <app>`) so
   System Settings can see it for FDA.
6. Write the launchd plist at
   `~/Library/LaunchAgents/<bundle-id>.plist`. The `ProgramArguments`
   should point directly at the `.app`'s
   `Contents/MacOS/applet` binary — **not** at `/bin/bash <script>`. Use
   `StartInterval` for the schedule and `RunAtLoad: true`.
7. Load the job with
   `launchctl bootout` (idempotent — to clear any prior version) then
   `launchctl bootstrap gui/$(id -u) <plist>`.
8. Open System Settings → Privacy & Security → Full Disk Access for me
   with
   `open "x-apple.systempreferences:com.apple.preference.security?Privacy_AllFiles"`,
   open `~/Applications` in Finder, and tell me exactly what to drag
   in. Wait for me to confirm I've granted FDA before testing.
9. Once I confirm, kickstart a test run with
   `launchctl kickstart -k gui/$(id -u)/<bundle-id>` and tail
   `~/Scripts/<bundle-id>.log.txt` to verify there are no
   `Operation not permitted` errors.

## What success looks like

The log file should end with something like:

`sent 75182 bytes  received 308 bytes  1255 bytes/sec
total size is 130526  speedup is 1.73
--- Sync finished: <date> ---`


with **no** `Operation not permitted` lines. After that, it should run
on its own on the schedule and I should be able to forget about it.

## What I don't want

- Don't have launchd run `/bin/bash <script>` directly — that's the
  setup that doesn't work for CloudStorage destinations.
- Don't disable SIP or run anything as root. The `.app` wrapper is the
  right approach.
- Don't suggest moving Dropbox out of `~/Library/CloudStorage/` — modern
  Dropbox doesn't support relocating its folder.
- Don't install Homebrew, third-party rsync, or any helper apps. Use
  what ships with macOS: `rsync`, `osacompile`, `codesign`,
  `PlistBuddy`, `lsregister`, `launchctl`.

DO NOT start working yet. First, ask me clarifying questions so we can define the approach together. Only begin once we’ve aligned. 
```
