CLI tool
westpoint-io/lazyrsync avatar
westpoint-io/lazyrsync

lazyrsync: a terminal UI that puts a dry-run diff in front of every rsync

🦀 A friendly terminal UI for rsync, written in Rust. Reusable profiles, an honest dry-run diff, and live progress, even over SSH.

845 stars22 forksRustMIT

At a glance

What is it?
lazyrsync wraps rsync in a Ratatui TUI with reusable profiles, a structured preview, and the same profiles runnable headless from cron. It is MIT licensed, written in Rust, and its last push was on 2026-08-10.
Who is it for?
Adopt lazyrsync if you already trust rsync but keep second-guessing your flags, or if you want a snapshot chain that no static crontab line can express. Skip it if rsync is not on your PATH at version 3.1 or later, or if you are on macOS 15.4+ and have not installed a real rsync over the openrsync in /usr/bin.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 41 days ago.
What is it written in?
Mainly Rust, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 17, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

The problem lazyrsync solves is flag anxiety, not rsync

rsync is not hard to install. It is hard to trust. The README puts it plainly: its flags are easy to get wrong and a single mistake can delete data. That is the whole premise here. lazyrsync does not replace rsync and does not reimplement the delta algorithm. It sits in front of the binary you already have and changes what you see before the binary runs.

The target user is someone who runs backups or syncs on a machine they reach over SSH, where a desktop sync client cannot go. The README makes that boundary explicit: all from the terminal, including over SSH where a desktop GUI cannot reach. If you already have a well-worn rsync one-liner in a shell script and you have never fat-fingered a --delete, this tool is overhead. If you have, the preview is the feature you are paying attention to.

How the TUI, the profile file and the rsync process fit together

The architecture is thin by design. A task is a source and a destination, nothing more. The README is emphatic that this mirrors the rsync command line exactly: no push/pull direction field, no separate remote slot. Either side may be a local path or a user@host:/path string, and the trailing slash rule is rsync's own, so a trailing slash on the Source copies its contents while omitting it copies the folder itself.

Profiles persist to profiles.toml, written through toml_edit so hand edits and TUI edits do not fight each other. The TUI itself is ratatui 0.29 with crossterm 0.28, which is why the interface works in a plain terminal session. Preview is the interesting part: pressing p runs rsync as a dry run and parses the itemized output into a +/~/- diff with statistics. The statistics block is the reason the preview needs --stats, and it is also why a headless dry run drops that flag, since only the TUI parser reads it.

Snapshots are the one place where lazyrsync computes something rsync cannot. The README explains that the numbered destination directory and the --link-dest chain are resolved at run time by scanning the destination: 1/ links against nothing, 2/ links against 1/, and so on. That is a genuine reason for the wrapper to exist rather than a convenience layer.

Installing lazyrsync and previewing your first transfer

The prerequisite is not optional. rsync 3.1 or later must be on your PATH, because the preview needs --itemize-changes, the progress bar needs --info=progress2 (added in 3.1.0), and snapshots need --link-dest. Homebrew and AUR packages pull rsync in; cargo install and cargo binstall do not. lazyrsync warns at startup when the rsync it finds is older than 3.1.

Pick one install path:

bash
cargo install lazyrsync      # crates.io
cargo binstall lazyrsync     # prebuilt release binary
brew install lazyrsync       # Homebrew
yay -S lazyrsync             # AUR (Arch)

Or build from a checkout with cargo install --path . . Then launch the interface:

bash
lazyrsync            # launch the TUI

The quickstart sequence is four keystrokes of setup. Press ] to reach the Profiles sub-tab and a to add a profile. Press ] again to return to Tasks and a to add a task, giving it an ID, an Action (Sync or Snapshot, toggled with the arrow keys), a Source and a Destination. Press p to preview. You should see the transfer resolve into a +/~/- diff with stats, and nothing is written to disk. Press r to run it and c to cancel mid-transfer.

For a first real use, point a task at a directory you can afford to lose and preview it before running. If you want the same profile available to a scheduler, the headless surface mirrors the TUI:

bash
lazyrsync list                        # profiles, task ids, resolved rsync commands
lazyrsync run backups                 # every task in the profile
lazyrsync run backups/photos-3f2a     # a single task, by id from `list`
lazyrsync run backups -n              # real dry run, changes nothing
lazyrsync run backups --yes           # required if any task uses --delete

The list subcommand is the one to run first, because it prints the resolved rsync command for each task, which is the fastest way to confirm the flags you think you set are the flags that will run.

The --delete gate and what happens when you forget --yes

Destructive flags are gated rather than hidden. In the TUI, flipping on --delete forces a confirmation before anything can be removed. In headless mode the equivalent gate is a required flag: a run that includes any task using --delete is refused unless --yes is present, and the README's exit code table gives that refusal its own code, 1, with the note that nothing ran. That is a better failure than a partial run.

Exit code 0 covers every task succeeding, or a profile with no tasks, which says so on stderr. The table is truncated in the README after code 2, so the full set of codes is not documented in what is available here; check the project's own docs page before you build retry logic on top of specific codes.

A failing task does not abort the batch. Every task gets its turn, then you get a summary and a non-zero exit code. The README frames this as leaving less data unprotected than aborting would. That is a defensible trade, but it means a single broken source produces a non-zero exit that looks identical in kind to any other failure unless you read the summary.

macOS 15.4 and later: the rsync you have is not the rsync this needs

This is the sharpest limitation in the README and the one most likely to bite. On macOS 15.4 and later, /usr/bin/rsync is openrsync. It has no --itemize-changes and no --info at all, and it accepts --link-dest without reliably hardlinking. Every headline feature of lazyrsync depends on one of those three. The preview cannot parse a diff that was never produced, the progress bar has no progress2 stream to read, and a snapshot chain built on an unreliable --link-dest is not a snapshot chain.

The fix is brew install rsync, then either put it ahead of /usr/bin on PATH or set rsync_path. The README points to a Choosing which rsync runs section for the config key, but does not spell out the value format in the portion available here. lazyrsync warns at startup when the rsync it finds is older than 3.1, which is a useful tripwire, though a warning at startup is easy to miss when the TUI redraws immediately.

Where lazyrsync is simply the wrong tool: if your transfer is a static rsync invocation with no --delete, no snapshot chain, and no remote side, cron calling rsync directly is fewer moving parts. The README concedes this, saying that for a plain Sync you could point cron at rsync directly. It argues the wrapper earns its place for Snapshots, where the command genuinely differs on every run.

lazyrsync against a plain rsync script or a desktop sync client

The obvious alternative is a shell script wrapping rsync. The difference is not convenience, it is where the safety lives. A script encodes flags once and runs them forever; the only way to see what a run will do is to remember to pass -n yourself, and the only way to catch a bad --delete is to read the diff carefully. lazyrsync makes the preview a keystroke and puts the destructive flag behind a confirmation, so the check is part of the workflow rather than a discipline you have to maintain. The cost is a binary in the path and a TOML file to keep in sync with reality.

The second alternative is a desktop sync client. Those give you a visual diff, which is the same appeal, but the README's framing is the honest distinction: a desktop GUI cannot reach a headless remote box over SSH. lazyrsync runs where your shell runs, and the same profile that you verified in the TUI is the one a scheduler executes later. That continuity between the interactive check and the unattended run is the actual product, more than the diff rendering itself.

Maintenance, licence and what the release history tells you

The last push to the repository was on 2026-08-10, and v0.3.0 was released the same day. The project is not archived. It is at 0.3.0, which means the maintainers have not declared the interface stable, and the README does not document a compatibility policy for profiles.toml across minor versions. If you hand-edit that file, keep a copy before upgrading.

The dependency set is small and conventional: ratatui, crossterm, clap, serde, toml, toml_edit, anyhow, anstyle, anstream and chrono. The release profile sets strip, lto, codegen-units = 1 and panic = "abort", which is a deliberate binary-size and speed choice rather than a default. Upgrade cost is therefore mostly the cost of tracking ratatui and crossterm, both of which move.

The licence is MIT, which is permissive and imposes no copyleft obligation on your own code. It also means you get no warranty and no patent grant, which is the standard trade for this licence family. That is a description of what MIT says, not advice about your situation.

One operational detail worth knowing before you schedule anything: tasks run in the order lazyrsync list shows them, which the README says is not the order they appear in profiles.toml, because the loader sorts by recency. If your batch has an implicit dependency between tasks, verify the order with list rather than assuming the file order holds.

Editorial conclusion

Adopt lazyrsync if you already trust rsync but keep second-guessing your flags, or if you want a snapshot chain that no static crontab line can express. Skip it if rsync is not on your PATH at version 3.1 or later, or if you are on macOS 15.4+ and have not installed a real rsync over the openrsync in /usr/bin. Before scheduling anything, run lazyrsync list and confirm the task order, because the loader sorts by recency rather than by the order tasks appear in profiles.toml.

Frequently asked questions

What is rsync used for?

lazyrsync's README describes rsync as the right tool for backups and syncs, which is the role it plays underneath this interface: copying a source to a destination, locally or over SSH, while transferring only what changed. lazyrsync does not replace it and calls the rsync binary directly.

What does rsync stand for?

The README does not expand the name, so the acronym is not documented in the available material. What it does document is the version requirement: rsync 3.1 or later must be on your PATH.

Is rsync faster than smb?

The README makes no comparison between rsync and SMB, so there is nothing here to support an answer. The only performance-adjacent claim it makes is that lazyrsync's progress bar depends on --info=progress2, which arrived in rsync 3.1.0.

Which is faster, cp or rsync?

The README does not benchmark rsync against cp. It does explain one structural difference that matters for repeated runs: rsync transfers only what has changed, and lazyrsync's preview surfaces exactly which files fall into the +, ~ and - categories before anything is written.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. westpoint-io/lazyrsync on GitHub
Community notes

Community notes