About
rsyncshot creates space-efficient backups using rsync and hard links. Each snapshot looks like a full backup, but unchanged files share disk space across all snapshots.
Supports backing up to local drives (USB, NFS) or remote servers via SSH.
Inspired many years ago by Mike Rubel's rsync snapshots.
Quick Start
# Download and install
wget https://raw.githubusercontent.com/cjennings/rsyncshot/main/rsyncshot
sudo bash ./rsyncshot setup
# Or clone and install
git clone https://github.com/cjennings/rsyncshot
cd rsyncshot
make deps # Install dependencies (auto-detects package manager)
sudo make installAfter setup, edit /etc/rsyncshot/config to set your backup destination.
Commands
| Command | Description |
|---|---|
rsyncshot backup |
Run an immediate one-off backup |
rsyncshot <name> <count> |
Create snapshot with name and retention |
rsyncshot setup |
Install script and configure cron jobs |
rsyncshot status |
Show installation and environment status |
rsyncshot list |
Show existing snapshots with sizes |
rsyncshot dryrun <n> <c> |
Preview backup without making changes |
rsyncshot help |
Show help message |
Examples
# Immediate backup (creates manual.0)
sudo rsyncshot backup
# Keep 24 hourly snapshots
sudo rsyncshot hourly 24
# Keep 7 daily snapshots
sudo rsyncshot daily 7
# Preview what would be backed up
sudo rsyncshot dryrun manual 1
# Check if everything is configured correctly
sudo rsyncshot status
# See existing snapshots
sudo rsyncshot listConfiguration
All settings live in /etc/rsyncshot/config. Created automatically by rsyncshot setup.
Backup Modes
Remote Mode (SSH)
Back up to a remote server over SSH:
REMOTE_HOST="myserver"
REMOTE_PATH="/mnt/backups"Backups go to myserver:/mnt/backups/hostname/.
If your SSH key isn't in root's ~/.ssh/, specify the path:
SSH_IDENTITY_FILE="/home/youruser/.ssh/id_ed25519"Local Mode
Back up to a mounted drive (USB, NFS, etc.):
REMOTE_HOST=""
MOUNTDIR="/media/backup"Backups go to /media/backup/hostname/. If the drive isn't mounted, rsyncshot will try to mount it.
What Gets Backed Up
Edit /etc/rsyncshot/include.txt - one directory per line (supports paths with spaces):
/home
/etc
/usr/local/bin
# Comments start with #
What Gets Excluded
Edit /etc/rsyncshot/exclude.txt - one pattern per line:
# Caches and temp files
.cache
*.tmp
*.log
# Build artifacts
node_modules
__pycache__
*.pyc
Lock File
rsyncshot takes an exclusive lock before each backup, so concurrent runs can't overlap (a slow backup overrunning the next cron tick fails fast instead of stacking up). Cron entries need no flock wrapper.
The lock defaults to /run/lock/rsyncshot.lock (a root-only directory), falling back to /var/lock then /tmp. Override it in the config if needed:
LOCKFILE="/run/lock/rsyncshot.lock"Automatic Backups
Setup installs a default cron schedule:
| Type | Schedule | Retention |
|---|---|---|
| Hourly | Every hour, 1am-11pm | 22 |
| Daily | Noon, Monday-Saturday | 6 |
| Weekly | Noon, Sunday | 51 |
The installed cron lines carry no flock wrapper - rsyncshot locks itself (see Lock File above).
Edit with sudo crontab -e.
How It Works
- rsync copies your directories to
destination/latest/ - Oldest snapshot beyond retention count is deleted
- Existing snapshots rotate (
hourly.0→hourly.1→hourly.2…) latest/is hard-linked tohourly.0(or whatever type you specified)
Hard links mean unchanged files share disk space. A 100GB backup with 24 hourly snapshots might only use 110GB total if most files don't change.
Each source is stored under its full path (/usr/local/bin lands at latest/usr/local/bin), so sources never collide and restores are unambiguous. Ownership is preserved as numeric uid/gid, so a restore stays correct even when the destination has a different user/group database.
Safeguards
- Separate by hostname - one drive can back up multiple machines
- Internal lock - prevents overlapping runs; no
flockwrapper needed in cron - Validates sources - checks directories exist before starting
- Validates destination - checks mount or SSH connectivity
- Checks rsync exit code - won't rotate if backup failed
- Read-only snapshots - prevents accidental deletion
- Timestamped logging - all runs logged to
/var/log/rsyncshot.log
Requirements
- bash
- rsync
- cron
- grep
- flock
- ssh (for remote mode)
The script checks for rsync and ssh at startup and shows install instructions if missing.
Uninstalling
# If you cloned the repo
sudo make uninstall
# Or manually
sudo rm /usr/local/bin/rsyncshot
sudo rm -rf /etc/rsyncshot
sudo rm /var/log/rsyncshot.log # optional
sudo crontab -e # remove rsyncshot entriesDevelopment
Run make to see all available targets:
| Target | Description |
|---|---|
| help | Show all targets (default) |
| install | Install rsyncshot and configure cron |
| uninstall | Remove rsyncshot and config |
| deps | Install dependencies (auto-detects distro) |
| lint | Run shellcheck static analysis |
| check | Run lint + full test suite |
| test | Run full test suite |
| test-quick | Run quick tests (skip backup/cron) |
| test-verbose | Run tests with verbose output |
Testing
An automated test suite is included in the tests/ directory.
Running Tests
# Run all tests (requires sudo)
sudo ./tests/test_rsyncshot.sh
# Skip slow backup/cron tests
sudo ./tests/test_rsyncshot.sh --quick
# Verbose output
sudo ./tests/test_rsyncshot.sh -vTest Coverage
| Category | Tests | Description |
|---|---|---|
| Validation | 9 | Input validation, help, arg checks, empty-path and zero-count guards |
| Functions | 6 | ismounted exact-match logic, derivepaths mode/path derivation |
| Include | 5 | Path parsing, comments, empty lines, spaces in paths |
| Dry-run | 4 | Preview mode doesn't modify anything |
| Rsync flags | 1 | rsync invoked with –numeric-ids and -R (relative paths) |
| Backup | 8 | Creation, copying, rotation, retention, exclusions, basename collisions |
| Cron | 4 | Cron management, no duplicates, preserves jobs, no flock wrapper |
| Locking | 3 | Refuses concurrent runs, dryrun not blocked, sequential runs |
| Remote | gated | Backup + rotation over SSH-to-localhost (skipped if unavailable) |
| Total | 40 | (plus the gated remote suite when localhost SSH is available) |
Test Structure
tests/test_rsyncshot.sh- Main test runnertests/lib/test_helpers.sh- Assertion functions and test environment setuptests/cases/test_validation.sh- Input validation teststests/cases/test_functions.sh- Unit tests for ismounted and derivepathstests/cases/test_includes.sh- Include file parsing teststests/cases/test_dryrun.sh- Dry-run mode teststests/cases/test_rsync_flags.sh- rsync invocation flag tests (command-capture shim)tests/cases/test_backup.sh- Backup and rotation teststests/cases/test_cron.sh- Cron job management teststests/cases/test_locking.sh- Concurrency lock teststests/cases/test_remote.sh- Remote-mode (SSH) tests, gated on localhost SSH
License
BSD 3-Clause. See LICENSE file.
