aboutsummaryrefslogtreecommitdiff

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 install

After 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 list

Configuration

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

  1. rsync copies your directories to destination/latest/
  2. Oldest snapshot beyond retention count is deleted
  3. Existing snapshots rotate (hourly.0hourly.1hourly.2…)
  4. latest/ is hard-linked to hourly.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 flock wrapper 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 entries

Development

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 -v

Test 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 runner
  • tests/lib/test_helpers.sh - Assertion functions and test environment setup
  • tests/cases/test_validation.sh - Input validation tests
  • tests/cases/test_functions.sh - Unit tests for ismounted and derivepaths
  • tests/cases/test_includes.sh - Include file parsing tests
  • tests/cases/test_dryrun.sh - Dry-run mode tests
  • tests/cases/test_rsync_flags.sh - rsync invocation flag tests (command-capture shim)
  • tests/cases/test_backup.sh - Backup and rotation tests
  • tests/cases/test_cron.sh - Cron job management tests
  • tests/cases/test_locking.sh - Concurrency lock tests
  • tests/cases/test_remote.sh - Remote-mode (SSH) tests, gated on localhost SSH

License

BSD 3-Clause. See LICENSE file.