A 3-tier macOS backup system: live rsync mirror + restic compressed cold backup + SHA256 integrity audit.
Fully automated via launchd. Zero cloud. Zero subscription. Listed in awesome-restic.
English | 中文文档 | Contributing
macOS has Time Machine, but it's a single-tier, all-or-nothing solution. If you have multiple external drives — a fast work drive, a large archive drive, and an offline cold backup — you need different strategies for each:
- Work drive → frequent rsync mirror to archive (readable copy, fast recovery)
- Archive drive → SHA256 integrity check (HDD silent bit rot detection)
- Cold backup → restic compressed, deduplicated, tiered by importance (disaster recovery)
This toolkit orchestrates all three with launchd automation, status reporting, and monthly audits.
- Photographers / videographers with terabytes of irreplaceable media on external HDDs
- Developers who want their code projects backed up with compression and dedup
- Data hoarders who already own multiple external drives and want to use them strategically
- Privacy-conscious users who want zero cloud dependency
- Anyone who has experienced HDD bit rot and wants to detect it before it's too late
┌─────────────────────────────────────────────────────────────┐
│ 3-Tier Backup Strategy │
├─────────────────────────────────────────────────────────────┤
│ │
│ Tier 1: Live Mirror (rsync) │
│ ┌──────────────┐ every 30min ┌──────────────┐ │
│ │ Work Drive │ ──── (idle) ────→ │ Archive Drive │ │
│ │ (2TB) │ rsync -a │ /_WorkMirror │ │
│ └──────────────┘ └──────────────┘ │
│ │ │
│ Tier 2: Cold Backup (restic) │ │
│ ┌──────────────┐ on plug-in ┌──────┴───────┐ │
│ │ Work Drive │ ──── WatchPaths ─→│ Backup Drive │ │
│ │ Archive Drive│ (≥6 day throttle) │ (1TB) │ │
│ └──────────────┘ restic backup └──────────────┘ │
│ --compression max │
│ tiered by priority │
│ │
│ Tier 3: Integrity Audit │
│ ┌──────────────┐ monthly ┌──────────────┐ │
│ │ Archive Drive │ ─── shasum -c ───→ │ .sha256 │ │
│ │ │ verify │ manifests │ │
│ └──────────────┘ └──────────────┘ │
│ │
│ + Monthly Audit: checks all tiers, sends notification │
│ if any tier has issues │
└─────────────────────────────────────────────────────────────┘
| Script | Purpose | Trigger |
|---|---|---|
daily-work-mirror.sh |
rsync work content to archive (readable mirror) | launchd every 30min + idle ≥30min |
restic-cold-1tb.sh |
Tiered restic compressed backup to cold drive | launchd WatchPaths (on plug-in), ≥6 day throttle |
restic-prune.sh |
Prune old snapshots (keep 7 daily / 4 weekly / 12 monthly) | launchd daily |
restic-check.sh |
Repository integrity check (read-data-subset 1%) | launchd monthly |
shasum-verify.sh |
SHA256 integrity verification of archive files | launchd monthly |
backup-monthly-audit.sh |
Audit all tiers, send notification if issues found | launchd monthly |
rsync-media-mirror.sh |
Standalone media mirror (optional, for media libraries) | manual or custom launchd |
install.sh |
One-click install (registers all launchd jobs) | manual |
uninstall.sh |
Uninstall all launchd jobs | manual |
# 1. Clone
git clone https://github.com/andersyin/restic-backup-toolkit.git
cd restic-backup-toolkit
# 2. Install restic
brew install restic
# 3. Store repository password in Keychain (do not export RESTIC_PASSWORD)
security add-generic-password -s restic-backup-password -a restic -w 'your-password'
# 4. Edit config.sh — replace the YourWorkDrive / YourArchiveDrive / YourBackupDrive placeholders
nano config.sh
# 5. Install (refuses placeholder paths; inits restic repo if the cold drive is mounted; registers launchd jobs)
bash install.sh
# 6. First run (recommended)
bash shasum-verify.sh gen
DAILY_WORK_MIRROR_FORCE=1 bash daily-work-mirror.sh
# Plug in the cold drive, then:
RESTIC_COLD_FORCE=1 bash restic-cold-1tb.shinstall.sh copies the plist files from this directory into ~/Library/LaunchAgents, substituting __SCRIPT_DIR__ and volume paths. uninstall.sh unloads those agents and removes status/log files; it does not delete restic repositories, mirrors, or the Keychain item.
All paths and settings are in one file:
# Drive paths
WORK_VOLUME="/Volumes/YourWorkDrive"
ARCHIVE_VOLUME="/Volumes/YourArchiveDrive"
COLD_VOLUME="/Volumes/YourBackupDrive"
# Restic repo
COLD_REPO="$COLD_VOLUME/restic-cold"
# What to back up (tiered by importance)
COLD_TIER1_DIRS=("$WORK_VOLUME/Knowledge Base") # Critical work
COLD_TIER2_DIRS=("$ARCHIVE_VOLUME/iPhone_Videos") # Irreplaceable media
COLD_TIER3_DIRS=("$ARCHIVE_VOLUME/视频") # Other archives
# What to mirror
WORK_MIRROR_DIRS=("$WORK_VOLUME/Knowledge Base")
# Restic binary (Apple Silicon default; Intel `/usr/local/bin/restic` is auto-detected)
RESTIC="/opt/homebrew/bin/restic"
# Capacity thresholds
STOP_PCT=92 # Stop cold backup if drive exceeds 92% fulldaily-work-mirror.sh runs every 30 minutes via launchd, but only executes when the system has been idle for ≥30 minutes (or during 23:00-06:00 night window). Uses rsync -a without --delete for space safety — deleted files on the source are preserved in the mirror.
restic-cold-1tb.sh triggers when the cold backup drive is plugged in (via launchd WatchPaths), but only runs if ≥6 days since last successful backup (configurable). Backs up in priority tiers:
- T1 — Critical work (small, must back up)
- T2 — Irreplaceable personal media (photos, videos)
- T3 — Other archives (only if space allows)
Each tier checks disk usage before starting. If the drive exceeds STOP_PCT, remaining tiers are skipped. Uses restic backup --compression max for maximum space efficiency.
shasum-verify.sh generates and verifies SHA256 manifests of archive directories. HDDs can silently corrupt data (bit rot); this is the only way to detect it without cloud redundancy.
Generate manifests (after adding new files to archive):
bash shasum-verify.sh genVerify (monthly, automated):
bash shasum-verify.sh verifybackup-monthly-audit.sh runs on the 2nd of each month and checks:
- Did
restic-checkpass? (repository integrity) - Did
shasum-verifypass? (archive integrity) - Are there enough SHA256 manifests?
- Are there enough restic snapshots?
Sends a macOS notification only if something is wrong. Silent on success.
What it looks like when the scripts run:
$ bash restic-cold-1tb.sh
=== Cold Backup (restic) — 2026-08-12 14:30 ===
Drive usage: 68% (threshold: 92%)
[T1-Work] Backing up: /Volumes/Work/Knowledge Base
creating new snapshot in repo ...
[0:00:23] 1.2 GiB added (15.4 GiB stored)
snapshot 8a3f2c1d saved
[T2-Personal] Backing up: /Volumes/Archive/iPhone_Videos
[0:01:47] 3.8 GiB added (19.2 GiB stored)
snapshot b7e4a9f2 saved
[T3-Archive] Backing up: /Volumes/Archive/视频
Drive at 89%, continuing...
[0:03:12] 5.1 GiB added (24.3 GiB stored)
snapshot c1d8e5a3 saved
=== Done: 3 snapshots, 10.1 GiB added ===
$ cat /tmp/restic-cold-1tb.status
{"status":"ok","tiers_run":3,"snapshots":3,"gb_added":"10.1","ts":"2026-08-12T14:33:51Z"}
$ bash shasum-verify.sh verify
[2026-08-12 10:00:01] VERIFY iPhone_Videos ...
4,832 files checked — OK
[2026-08-12 10:02:15] VERIFY iPhone_Photos ...
12,047 files checked — OK
[2026-08-12 10:05:33] VERIFY 视频 ...
856 files checked — OK
[2026-08-12 10:05:48] All directories verified: 3/3 OK
Why not just use Time Machine?
Time Machine is great for system-level recovery, but it's single-tier: one backup drive, no compression, no dedup, no bit rot detection, and no tiered priority. If your backup drive fills up, Time Machine silently deletes old backups. This toolkit gives you three independent layers of protection, each with a different failure mode.
Why not just use Backblaze / cloud backup?
Three reasons: (1) cost — uploading terabytes of data costs money every month forever; (2) speed — restoring from cloud is hours or days, rsync mirror is instant; (3) privacy — your data never leaves your physical drives. This toolkit is designed for people who already own multiple external drives.
Do I need exactly 3 drives?
No. The minimum is 2 drives (work + backup). The 3-tier setup (work + archive + cold backup) is the recommended configuration. You can also use the scripts independently — for example, just shasum-verify.sh for bit rot detection on a single archive drive.
What happens if a drive fails mid-backup?
Each tier is independent. If the cold backup drive fails during T2, T1 is already backed up. rsync mirror doesn't use --delete, so source data is never at risk. restic snapshots are atomic — a failed backup leaves the repository in a consistent state.
How much disk space does restic save vs rsync?
Typically 40-60% less, due to compression (--compression max) and deduplication. If you back up overlapping directories (e.g., work files that also appear in the archive), restic deduplicates them automatically.
All scripts write JSON status to /tmp/:
cat /tmp/daily-work-mirror.status # mirror status
cat /tmp/restic-cold-1tb.status # cold backup status
cat /tmp/restic-prune.status # prune status
cat /tmp/restic-check.status # check status
cat /tmp/shasum-verify.status # shasum status
cat /tmp/backup-monthly-audit.status # monthly audit summarycat /tmp/daily-work-mirror.log # mirror log
cat /tmp/restic-cold-1tb.log # cold backup log
cat /tmp/restic-backup.log # prune/check log
cat /tmp/shasum-verify.log # shasum log
cat /tmp/backup-monthly-audit.log # audit logrestic-backup-toolkit/
├── config.sh # All configuration (edit this)
├── daily-work-mirror.sh # rsync work mirror (Tier 1)
├── restic-cold-1tb.sh # restic cold backup (Tier 2)
├── restic-prune.sh # Snapshot pruning
├── restic-check.sh # Repository integrity check
├── shasum-verify.sh # Archive SHA256 verification (Tier 3)
├── backup-monthly-audit.sh # Monthly audit of all tiers
├── rsync-media-mirror.sh # Optional standalone media mirror
├── install.sh # One-click install
├── uninstall.sh # Uninstall launchd jobs (keeps backup data)
├── com.user.daily-work-mirror.plist # launchd: mirror every 30min
├── com.user.restic-cold-1tb.plist # launchd: cold backup on plug-in
├── com.user.restic-prune.plist # launchd: prune daily
├── com.user.restic-check.plist # launchd: check monthly
├── com.user.shasum-verify.plist # launchd: shasum monthly
├── com.user.backup-monthly-audit.plist # launchd: audit monthly
├── CONTRIBUTING.md
├── LICENSE
└── .github/workflows/shellcheck.yml # CI: bash -n + ShellCheck
- macOS with Homebrew
restic(brew install restic) — Apple Silicon and Intel paths are detected automatically- Python 3 (pre-installed on recent macOS, or Xcode Command Line Tools)
- 2+ external drives (work + backup)
/bin/bashneeds Full Disk Access so launchd can read Keychain (System Settings → Privacy & Security → Full Disk Access)
Scripts pass the restic password with RESTIC_PASSWORD_COMMAND (Keychain). They do not export RESTIC_PASSWORD, which can leak via process environment listings.
| Feature | Time Machine | Carbon Copy Cloner | Backblaze | This toolkit |
|---|---|---|---|---|
| Free | Yes (Mac only) | $40 | $9/mo | Yes |
| Multi-tier | No | Partial | No | Yes (3 tiers) |
| Compression | No | No | Yes | Yes (restic max) |
| Dedup | No | No | Yes | Yes (restic) |
| Bit rot detection | No | No | No | Yes (SHA256) |
| Offline backup | No | Yes | No (cloud) | Yes |
| Readable mirror | Yes | Yes | No | Yes (rsync) |
| Zero cloud | No | Yes | No | Yes |
| Open source | No | No | No | Yes |
bash uninstall.shRemoves all launchd jobs and status files. Backup data and restic repositories are preserved. The Keychain password is left in place; delete it yourself if you want:
security delete-generic-password -s restic-backup-password -a resticmacOS 三盘备份脚本集:rsync 实时镜像 + restic 压缩冷备 + SHA256 完整性巡检。全程 launchd 自动化,零云依赖。
| 层级 | 工具 | 触发 | 说明 |
|---|---|---|---|
| 实时镜像 | rsync | 每 30 分钟 + 空闲 ≥30 分钟 | 工作内容 → 档案盘可读副本 |
| 离线冷备 | restic | 插盘自动,≥6 天节流 | 按重要性分层压缩灾备 |
| 完整性巡检 | shasum | 每月 | 检测 HDD 静默坏道 |
| 月度审计 | — | 每月 | 汇总检查三层状态,异常才通知 |
git clone https://github.com/andersyin/restic-backup-toolkit.git
cd restic-backup-toolkit
brew install restic
# 存密码到 Keychain(不要 export RESTIC_PASSWORD)
security add-generic-password -s restic-backup-password -a restic -w '你的密码'
# 改配置(必须替换 YourWorkDrive 等占位路径,否则 install.sh 会拒绝安装)
nano config.sh
# 一键安装(plist 从本目录复制到 ~/Library/LaunchAgents)
bash install.sh
# 首次运行
bash shasum-verify.sh gen
DAILY_WORK_MIRROR_FORCE=1 bash daily-work-mirror.sh
RESTIC_COLD_FORCE=1 bash restic-cold-1tb.sh所有路径和参数集中在 config.sh:
WORK_VOLUME/ARCHIVE_VOLUME/COLD_VOLUME— 三个磁盘卷路径COLD_TIER1_DIRS/COLD_TIER2_DIRS/COLD_TIER3_DIRS— 冷备分层目录WORK_MIRROR_DIRS— 工作镜像目录SHASUM_DIRS— SHA256 巡检目录STOP_PCT/MIN_DAYS— 容量阈值和周期节流
/bin/bash需要在「系统设置 → 隐私与安全性 → 完全磁盘访问权限」中开启,否则 launchd 上下文无法读 Keychain- restic 路径会自动检测 Apple Silicon (
/opt/homebrew/bin/restic) 与 Intel (/usr/local/bin/restic) - 首次使用
shasum-verify.sh需要先bash shasum-verify.sh gen生成清单 uninstall.sh只移除 launchd 任务和日志,不删除冷仓、镜像和 Keychain 密码
- iphone-auto-backup — Auto-backup iPhone photos & videos on USB plug-in, EXIF date archiving, content-hash dedup
- md-reader — Zero-dependency single-file HTML markdown reader, 77KB, no install
If this toolkit saved your data, consider giving it a ⭐