A Bash script for backing up a macOS home directory with rsync.
- Regular files and folders are copied unless
backup.ignoreexcludes them, except inside keep-only areas. - Hidden files and folders are skipped unless
backup.allowincludes them. backup.ignoretakes precedence overbackup.allow.
The script also supports dry runs, traversal limits, resumable partial transfers, and per-run logs.
- macOS
- The Bash version included with macOS
- rsync 3.1 or newer
Install a newer rsync with:
brew install rsyncProtected locations such as Desktop and Documents may require Full Disk Access for the terminal running the script.
./backup.sh ~ "/Volumes/Backup Disk/home"
./backup.sh -n ~ "/Volumes/Backup Disk/home"
./backup.sh --print-rules ~ /tmp/xRunning the same command again updates an existing backup. Files removed from the source are not deleted from the target.
Symlinks are copied as symlinks, mounted filesystems below the source are not traversed, and a target inside the source is excluded automatically.
| Option | Description |
|---|---|
-n, --dry-run |
Copy nothing and write the logs for the files that would be copied. |
-i, --ignore FILE |
Use a different ignore list. |
-a, --allow FILE |
Use a different allow list. |
--print-rules |
Print the generated rsync filter rules and exit. |
--no-local |
Leave out the rules in .local/. |
-h, --help |
Show command-line help. |
Both filter files contain one entry per line. Lines beginning with # are comments.
backup.ignore contains paths and patterns that should not be copied:
*.log
node_modules/
/Downloads/
backup.allow lists hidden files and directories that should be copied:
.gitignore
/.zshrc
.git/
/.local/bin/
A nested anchored allow entry such as /.local/bin/ creates a keep-only area: the parent is traversed only far enough to retain the listed subtree. ~/Library uses this mechanism to keep selected data without copying the entire directory.
A .local/ folder next to the script may contain its own backup.ignore and backup.allow. Their entries are added to the lists in the repository, so they apply to that machine only. The folder is listed in .gitignore.
As with the repository lists, an ignore entry takes precedence over an allow entry, so a local ignore entry can exclude something the repository allows.
Use --no-local to leave the local rules out. The rules shown by --print-rules include them.
Each run creates logs/<date>_<time>/.
extensions.csvlists file types transferred during the run.progress.logrecords a file whenever the transferred file type changes, so repeated transitions such as*.txt → *.jpg → *.txtare preserved.files.txtis created during a dry run and lists what rsync would copy.errors.txtis created when rsync reports copy errors.
Use a dry run to review the effective backup before copying data:
./backup.sh -n ~ "/Volumes/Backup Disk/home"Before copying, the source is scanned once.
| Variable | Default |
|---|---|
MAX_DEPTH |
64 |
MAX_ENTRIES_PER_DIRECTORY |
100000 |
MAX_TOTAL_ENTRIES |
5000000 |
MAX_PREFLIGHT_SECONDS |
3600 |
If a limit is exceeded, the run stops before copying starts. Limits can be overridden for a single run:
MAX_TOTAL_ENTRIES=8000000 ./backup.sh ...The allow list includes locations that may contain credentials or other sensitive data, including SSH and GPG keys, .env files, cloud credentials, keychains, and shell history.
Protect the backup destination accordingly.
tests/run.shFor the slower traversal test:
tests/run.sh --slowThis repository is open to contributions. If you find a bug, an edge case, or a way to improve the robustness or overall quality of the backup script, feel free to open a pull request. Improvements are welcome.
See LICENSE.