Mirra is a macOS command-line tool powered by rsync that keeps a destination folder as an exact one-way replica of its source — including every file, permission, and metadata.
- Exact mirror — destination is always a perfect replica; files removed from source are removed from destination on the next sync
- Dry run before you sync — preview every change (
+copy,~metadata,-delete) without writing anything - Full metadata preservation — ACLs, extended attributes, hard links, and APFS/HFS+ creation times all preserved
- Symlink-accurate — symlinks are preserved as symlinks, maintaining exact filesystem structure
- Smart exclusions — macOS volume metadata (
.Spotlight-V100,.Trashes,.DS_Store, etc.) excluded automatically viaexclusions.txt - Verify mode — byte-for-byte checksum comparison against source without writing anything; reports mismatched, missing, and extra files
- Animated progress — braille spinner with accurate elapsed time; zero I/O overhead during the transfer
- Run log — every run writes a structured log (
mirra-<mode>-<timestamp>.log) to your private temp directory, opened automatically when there are changes; plain text, no ANSI codes - Consistent output — the same
+/~/-symbols and summary format across dry-run, sync, and verify
- macOS
- Modern rsync: Apple's built-in rsync is missing flags mirra depends on. Install a modern version via Homebrew:
brew install rsync. - Full Disk Access: Since macOS Mojave, Apple restricts file access even for root. Grant "Full Disk Access" to your Terminal (or iTerm2) via System Settings > Privacy & Security > Full Disk Access, or rsync will throw "Operation not permitted" errors on protected directories.
- Target File System: The destination must be formatted as APFS or Mac OS Extended (Journaled). ExFAT does not support macOS extended attributes or BSD file flags — a true 1:1 clone is not possible.
- Data Drives Only: Mirra cannot create a bootable macOS backup due to Apple's cryptographically sealed Signed System Volume (SSV).
Mirra uses sudo to preserve exact permissions, ownership, and ACLs. macOS configures sudo with a restricted PATH that may not include Homebrew, causing sudo to fall back to Apple's built-in rsync.
If mirra fails with rsync: illegal option -- N, verify which rsync sudo actually uses:
sudo which rsyncThis should output the Homebrew path (/opt/homebrew/bin/rsync on Apple Silicon, /usr/local/bin/rsync on Intel). If it outputs /usr/bin/rsync, add the Homebrew bin directory to /etc/paths, or update secure_path in /etc/sudoers via sudo visudo.
git clone https://github.com/ahmedomer/mirra.git
cd mirra
chmod +x mirra.sh./mirra.sh [--dry-run|--verify|--no-confirm|-y] [<source> <destination>]
./mirra.sh --version
./mirra.sh --helpRun without arguments for fully interactive mode.
Mirra prompts for source and destination paths (with readline editing and tab completion), then a mode selection:
Source path: /Volumes/Extreme SSD
Destination path: /Volumes/Backup
Mode:
[1] Dry run (default)
[2] Sync
[3] Verify
Select:
After selecting a mode, mirra shows a focused summary of what will happen before proceeding:
/Volumes/Extreme SSD → /Volumes/Backup
Preview only — no files will be modified.
sudo may prompt for your password to preserve file permissions and ACLs.
sudo rsync \
-aAXHN \
--delete \
--numeric-ids \
--exclude-from=/path/to/mirra/exclusions.txt \
--dry-run \
"--out-format=%i %n" \
"/Volumes/Extreme SSD/" \
"/Volumes/Backup"
Preview what would change without writing anything. The terminal shows the summary; the full per-file list goes to the log:
Started: Mon 19 May 2026 08:33:43
⠋ Dry run... 0s
+ 3 transfer ~ 1 metadata - 1 delete
Log: /var/folders/.../T/mirra-dry-run-20260519-083343.log
✓ Dry run completed in 2s.
| Symbol | Meaning |
|---|---|
+ |
File will be / was transferred (new or content-changed; includes new symlinks and special files) |
~ |
Metadata only will be / was updated on an existing item (permissions, timestamps, ACLs — no data transfer) |
- |
File will be / was deleted from destination (or is extra in destination during verify) |
Mirror source to destination (requires sudo for full metadata preservation). Asks for confirmation before proceeding:
Proceed? [y/N]: y
Started: Mon 19 May 2026 08:33:43
⠋ Syncing... 8s
+ 3 transferred ~ 1 metadata - 1 deleted
Log: /var/folders/.../T/mirra-sync-20260519-083343.log
✓ Sync completed in 9s.
The full per-file change list, any rsync warnings, and a summary are written to a structured log in your private temp directory ($TMPDIR). The log opens automatically when there are entries. Each run creates a new timestamped file — no previous log is overwritten.
Compare every file byte-for-byte against the source using checksums. Nothing is written. Reports files that differ, are missing, or exist only on destination:
Started: Mon 19 May 2026 08:33:43
⠋ Verifying... 42s
+ 1 transfer ~ 0 metadata - 1 delete
[Warning] Differences found in 43s — run Sync to resolve.
Log: /var/folders/.../T/mirra-verify-20260519-083343.log
✓ Verify completed in 43s.
When destination matches source exactly, no log is opened:
Destination matches source byte-for-byte.
✓ Verify completed in 43s.
Verify reads every byte on both sides, so it is slower than a dry run on large volumes.
Pass paths directly to skip the interactive prompts. Pass --dry-run or --verify to skip the mode menu:
./mirra.sh /Volumes/Source /Volumes/Destination
./mirra.sh --dry-run /Volumes/Source /Volumes/Destination
./mirra.sh --verify /Volumes/Source /Volumes/DestinationUse --no-confirm (or -y) to sync without a confirmation prompt — this forces sync mode and skips the mode selection menu, useful for scripted or automated use:
./mirra.sh --no-confirm /Volumes/Source /Volumes/Destination
./mirra.sh -y /Volumes/Source /Volumes/DestinationPaths can include a trailing slash — mirra normalises them automatically (source always gets a trailing slash, destination never does).
| Flag | Purpose |
|---|---|
-a |
Archive mode: recursive, preserves modification times, permissions, owner, group, and device files |
-A |
Preserve ACLs (Access Control Lists) |
-X |
Preserve extended attributes (xattrs) |
-H |
Preserve hard links |
-N |
Preserve file creation times (APFS/HFS+ birth times) |
--delete |
Remove files from destination that no longer exist in source |
--numeric-ids |
Preserve exact numeric UID/GID values without name mapping |
--out-format="%i %n" |
Structured itemize output used for parsing — present in all modes |
--dry-run |
Added in dry-run and verify modes; rsync scans but writes nothing |
--checksum |
Added in verify mode; forces byte-for-byte comparison instead of mtime+size |
rsync has a built-in --log-file option. mirra does not use it because it is unreliable in dry-run and verify modes.
--log-file only records completed operations. In dry-run and verify, file transfers are simulated and never actually complete, so rsync silently drops them from the log. Empirically: a run with four events (new file, metadata-only update, new symlink, deletion) produced all four lines via --out-format but only one line via --log-file. A dry-run log built on --log-file would be misleadingly sparse.
--out-format fires during file-list iteration — before any write decision — so it captures every affected item consistently across all three modes. The custom log pipeline (_process_output) then maps rsync's 11-character itemize codes to the readable + / ~ / - symbols; that conditional logic cannot be expressed in rsync's format strings.
exclusions.txt (in the same directory as the script) lists paths excluded from the sync. It uses rsync's --exclude-from format:
- Paths starting with
/are anchored to the source root (e.g./.Trashes) - Paths without a leading
/match at any depth (e.g..DS_Store)
The default exclusions.txt covers macOS volume metadata. Edit it freely to add your own exclusions. If the file is absent, mirra continues without exclusions and prints a notice.
The test suite validates rsync command construction in each mode. Tests require bats-core:
brew install bats-core
bats tests/command_generation.batsEight tests cover all three modes (dry-run, sync, verify) with and without exclusions.txt, plus --no-confirm and -y. Each test verifies flag composition, exact source and destination path values, trailing-slash rules, and argument ordering.
See CONTRIBUTING.md.
MIT — see LICENSE