# lazyrsync: A Rust TUI for rsync with Profiles, Honest Dry-Run Preview, and SSH

> lazyrsync is a Rust terminal UI built with Ratatui that wraps rsync with reusable profiles, a structured diff preview before any transfer runs, and live progress with cancellation. It is aimed at engineers who rely on rsync for backups and sync tasks but find raw rsync flags risky to assemble correctly by hand.

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

- Repository: https://github.com/westpoint-io/lazyrsync
- Website: https://lazyrsync.westpoint.io/
- Stars: 901 · Forks: 24
- Language: Rust
- License: MIT
- Published: 2026-09-17 · Updated: 2026-09-17 · Language: en
- Canonical page: https://hysenlabs.com/projects/westpoint-io-lazyrsync

## Why lazyrsync Exists: rsync Safety Without Leaving the Terminal

rsync has been the standard tool for file sync and incremental backups for decades, but its flag syntax is wide enough that a single wrong option, especially --delete, can remove data with no confirmation. GUI backup applications exist, but they cannot reach a remote server over SSH where no desktop session runs. lazyrsync occupies the space between the two: it stays in the terminal, works over SSH, and adds the confirmation and preview steps that raw rsync omits.

The project is aimed at developers and system administrators who already know rsync and use it for backups, deployments, and data moves, but who want to save recurring transfer configurations, inspect what will change before anything is written, and have a clear way to run those same configurations from cron without rewriting them as shell scripts.

## Profiles, the +/~/- Diff, and the --delete Gate

The central concept in lazyrsync is the profile. A profile groups one or more tasks, and each task is a Source and Destination pair that mirrors the rsync command line. Either side of a task can be a local path or a remote user@host:/path address. Profiles are saved and rerun with a keystroke; the README describes saving a transfer once and rerunning it to distinguish lazyrsync from typing rsync flags from memory each time.

Pressing p in the TUI triggers a dry-run preview. lazyrsync calls rsync with --itemize-changes and displays the result as a structured diff: each file gets a + (new), ~ (changed), or - (deleted) marker alongside byte and file statistics. Nothing is written at this stage. The dry-run is not advisory; it is a required step before the actual run can be reviewed and committed.

The --delete flag, which causes rsync to remove files from the destination that no longer exist in the source, is toggleable as a checkbox in the TUI. When --delete is on, lazyrsync requires explicit confirmation before running. In headless mode, the --yes flag serves the same function; if a task uses --delete and --yes is absent from the command, lazyrsync exits with code 1 and prints 'nothing ran', so a cron job without --yes will refuse destructive deletes rather than silently running them.

## Installing lazyrsync on macOS, Arch Linux, or from Source

lazyrsync requires rsync 3.1 or later on the PATH. The README notes that on macOS 15.4 and later, /usr/bin/rsync is openrsync, which does not implement --itemize-changes or --info, and accepts --link-dest without reliably hardlinking. The fix is to install rsync from Homebrew and ensure it appears before /usr/bin on the PATH, or to set rsync_path in the configuration. lazyrsync prints a warning at startup when it finds an rsync older than 3.1.

Installation options:

```bash
cargo install lazyrsync
```

For a prebuilt release binary without compiling from source:

```bash
cargo binstall lazyrsync
```

On macOS via Homebrew:

```bash
brew install lazyrsync
```

On Arch Linux via AUR:

```bash
yay -S lazyrsync
```

The Homebrew and AUR packages pull rsync in as a dependency; cargo install and cargo binstall do not, so on a fresh system those paths require rsync to be installed separately. To build from source, clone the repository and run:

```bash
cargo install --path .
```

Launch the TUI by running:

```bash
lazyrsync
```

From there, press ] to switch to the Profiles sub-tab and a to add a profile, then switch back to the Tasks tab and press a to add a task with a Source and Destination. Press p to preview the transfer as a diff, then r to run it. Progress appears in the Runs panel, and c cancels mid-transfer.

## Headless Mode, Exit Codes, and Cron Scheduling

Every profile built in the TUI can be run without the TUI, which makes lazyrsync usable as a scheduled backup runner. The lazyrsync list command prints the profiles, their task IDs, and the resolved rsync commands for inspection:

```bash
lazyrsync list
lazyrsync run backups
lazyrsync run backups/photos-3f2a
lazyrsync run backups -n
lazyrsync run backups --yes
lazyrsync run backups -v
```

A real headless run passes rsync -q, so only one line per task appears and errors still print on stderr. A dry run with -n is never quiet because the itemized diff is the output. Flags compose freely.

The exit codes are documented precisely: 0 means every task succeeded, 1 means the run was refused because --delete was used without --yes, 2 means the profile or task ID was not found or the config failed to load, and 3 means a task could not be started or was killed by a signal. When none of those apply, the exit code is the first failing rsync task's own exit code. The README notes that rsync exit codes 1, 2, and 3 overlap the lazyrsync codes, so the printed message is the authoritative source in ambiguous cases.

The README makes an important note about ordering: tasks run in the order lazyrsync list shows them, which is sorted by recency, not the order they appear in profiles.toml. The README advises checking list before scheduling anything that assumes a fixed task order.

For Snapshot tasks, headless mode is particularly useful because the --link-dest chain is computed fresh at run time by scanning the destination directory. A static cron command pointing at rsync directly cannot express that computation; lazyrsync run resolves it on each invocation.

## SSH Transfers and Numbered Snapshot Rotations

lazyrsync treats remote paths as a first-class part of a task rather than a separate remote mode. Either the Source or the Destination can be a user@host:/path address. A remote source downloads files to a local destination; a remote destination uploads files from a local source. The mechanism is rsync's own SSH transport, so no additional daemon is required on the remote host beyond an SSH server and rsync 3.1.

The Snapshot task type produces numbered, hardlinked backup directories using rsync's --link-dest flag. The first run creates a 1/ directory. Each subsequent run creates the next numbered directory and hardlinks unchanged files against the previous snapshot rather than copying them again. This means each snapshot appears as a full copy of the source but consumes only incremental space on disk. The README lists --link-dest as one of the rsync features that requires rsync 3.1; the openrsync on macOS 15.4 accepts the flag but does not reliably hardlink.

## Where lazyrsync Does Not Fit

lazyrsync wraps the system rsync binary directly. Any limitation of rsync is also a limitation of lazyrsync: no cloud storage targets (S3, Google Drive, Backblaze), no bidirectional sync, and no conflict resolution. It is a TUI and scheduler for rsync transfers, not a general-purpose sync engine.

Rclone is an alternative sync tool that supports cloud storage backends and also offers a web-based interface and an experimental TUI mode. Unlike lazyrsync, which uses rsync's existing binary protocol and SSH transport, rclone implements its own transfer engine and is designed primarily for cloud provider sync. Rclone does not wrap rsync and does not use the rsync wire protocol.

lazyrsync is also at an early version: the most recent release at the time of the last push was v0.3.0, published on 2026-08-10. Configuration is stored in profiles.toml, and the README does not document a migration path for breaking changes between versions, so users on a scheduled backup setup should read CHANGELOG.md before upgrading.

Teams running in environments where Rust and Cargo are not available, and where Homebrew or AUR is also unavailable, have no documented installation path other than providing a prebuilt binary manually, since cargo binstall resolves GitHub Release assets and requires the Rust toolchain to be present.

## Maintenance Status and MIT License

The repository is not archived. The last push was on 2026-08-10, and three releases (v0.1.1, v0.2.0, v0.3.0) landed between July and August 2026. The project is under active development. A CHANGELOG.md and a RELEASING.md are present in the repository, which suggests a structured release process.

lazyrsync is licensed under MIT, as declared both in the Cargo.toml license field and in the LICENSE file at the repository root. MIT is permissive: it allows use, modification, and redistribution with attribution and without requiring derivative works to carry the same license. There are no noted patent grants or contribution license agreements in the repository.

## Conclusion

lazyrsync suits engineers who already trust rsync but want a safety layer before destructive transfers run and a reliable headless mode for scheduled backups. It is the wrong choice for anyone needing cloud storage targets or bidirectional sync, since it wraps rsync directly. Check that rsync 3.1 or later is available on the target machine, and on macOS 15.4 or later confirm you are running the Homebrew rsync, not the system openrsync, before committing any profile to a backup schedule.

## FAQ

### Does lazyrsync install rsync automatically?

The Homebrew and AUR packages pull rsync in as a dependency. Installing via cargo install or cargo binstall does not. In those cases, rsync 3.1 or later must be installed separately and available on the PATH before running lazyrsync.

### Can lazyrsync run without the TUI, for example in a cron job?

Yes. Every profile built in the TUI can be run headless with lazyrsync run followed by the profile name or a specific task ID. The --yes flag is required when any task uses --delete; without it, the run is refused and nothing is transferred.

### Does lazyrsync support macOS?

Yes, it is available via Homebrew. On macOS 15.4 and later, the system rsync at /usr/bin/rsync is openrsync, which lacks --itemize-changes and does not reliably support --link-dest. The README recommends installing rsync from Homebrew and placing it ahead of /usr/bin on the PATH.

## Sources

- [License: MIT](https://github.com/westpoint-io/lazyrsync/blob/main/LICENSE)
- [Project website](https://lazyrsync.westpoint.io/)
- [README](https://github.com/westpoint-io/lazyrsync/blob/main/README.md)
- [Releases](https://github.com/westpoint-io/lazyrsync/releases)
- [westpoint-io/lazyrsync on GitHub](https://github.com/westpoint-io/lazyrsync)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/westpoint-io-lazyrsync
