A maintained implementation of this setup, including the current Python 3 wrapper and an optional post-transfer snapshot/prune hook, is in the Git repository
http://mennucc1.debian.net/simple_rsync_backup.gitThis page explains the design; for a real installation you should clone that repository and use the scripts from there.
serverhost# adduser --system rsyncdInstall the current scripts from the repository:
serverhost# apt install git rsync openssh-server python3 python3-daemon serverhost# apt install btrfs-progs # optional, for Btrfs-backed snapshots serverhost# git clone http://mennucc1.debian.net/simple_rsync_backup.git /usr/local/src/simple_rsync_backup serverhost# git -C /usr/local/src/simple_rsync_backup submodule init serverhost# git -C /usr/local/src/simple_rsync_backup submodule update serverhost# install -o root -g root -m 755 /usr/local/src/simple_rsync_backup/rsyncd_wrapper /usr/local/sbin/rsyncd_wrapper serverhost# install -d -o rsyncd -g rsyncd /home/rsyncd/log serverhost# install -d -o rsyncd -g rsyncd -m 700 /home/rsyncd/.ssh serverhost# install -o rsyncd -g rsyncd -m 600 /dev/null /home/rsyncd/.ssh/authorized_keys serverhost# su rsyncd -s /bin/sh -c 'touch ~/.hushlogin'If you use Btrfs, allow the rsyncd user to run only the required btrfs subvol commands, for example by adapting the provided examples_sudoers.txt and installing it as /etc/sudoers.d/rsyncd-btrfs. Always validate the result with visudo -cf /etc/sudoers.d/rsyncd-btrfs.
serverhost# usermod rsyncd -s /usr/local/sbin/rsyncd_wrapperis the key security step: the rsyncd account will not get an interactive shell, it will only be able to start the restricted wrapper. The post-transfer hook should be run from the cloned repository, so it can import the bundled plain_config.py helper.
# sample rsyncd.conf configuration file
# GLOBAL OPTIONS
log file=/home/rsyncd/log/rsyncd.log
pid file=/home/rsyncd/rsyncd.pid
# MODULE OPTIONS
[host1]
comment = backup space for one host
path = /srv/backups/host1/current
use chroot = no
# the default for read only is yes...
read only = no
ignore nonreadable = yes
transfer logging = no
refuse options = checksum
dont compress = *.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz
auth users = host1
secrets file = /home/rsyncd/rsyncd.secrets
post-xfer exec = /usr/local/src/simple_rsync_backup/post_backup
# for this , /srv/backups must be mounted with option user_xattr
fake super = yes
The matching rsync-daemon passwords are stored in
/home/rsyncd/rsyncd.secrets.
The example above uses fake super = yes. For that to work correctly,
the backup filesystem must support extended attributes; otherwise metadata such
as owner, mode, and modification time may be lost in the backup. On Linux this
usually means mounting the backup filesystem with user_xattr.
PRUNE_MIN_LEN/i=60 PRUNE_KEEP_RECENT/i=14 PRUNE_CADENCE/i=7
serverhost$ python3 /usr/local/src/simple_rsync_backup/control check --module-name host1 serverhost$ python3 /usr/local/src/simple_rsync_backup/control server-add-module --module-name host1 --server-host backup-server.example.net --write-json /tmp/host1.jsonThe second command creates /srv/backups/host1/current and /srv/backups/host1/past, appends a module stanza to /home/rsyncd/rsyncd.conf, adds mandatory rsync-daemon auth in /home/rsyncd/rsyncd.secrets, and writes a JSON handoff file. By default the rsync auth user is the same as the module name, and if no password is passed a random 10-character password is generated.
localhost$ python3 /usr/local/src/simple_rsync_backup/control client-add --from /tmp/host1.jsonBy default that writes ~/bin/backup_<module_name>_<hostname>, ~/.config/simple_rsync_backup/<module_name>_<hostname>.rsync_password, and ~/.config/simple_rsync_backup/<module_name>_<hostname>.exclude_from. The generated script also expects helper commands such as flock, nocache, and ionice; on Debian systems you may need to install the packages that provide them, for example apt install nocache util-linux. The generated script uses a fixed receiver-side partial directory .rsync-partial together with --delete-after, so interrupted transfers can resume across runs while stale partial files are cleaned up by rsync after a successful run. The JSON handoff file contains the rsync password, so treat it as secret material.
[host1]
comment = backup space for one host
path = /srv/backups/host1/current
use chroot = no
read only = no
ignore nonreadable = yes
transfer logging = no
refuse options = checksum
dont compress = *.gz *.tgz *.zip *.z *.rpm *.deb *.iso *.bz2 *.tbz
auth users = host1
secrets file = /home/rsyncd/rsyncd.secrets
post-xfer exec = /usr/local/src/simple_rsync_backup/post_backup
fake super = yes
Then add the matching rsync-daemon credential to
/home/rsyncd/rsyncd.secrets:
host1:some_random_passwordThat file should have mode 0600.
serverhost# install -d -o rsyncd -g rsyncd /srv/backups/host1/current /srv/backups/host1/past
localhost# ssh-keygen -f ~/.ssh/rsyncdCopy the key in the serverhost, e.g. copy/paste between terminals using
localhost# cat ~/.ssh/rsyncd.pub
serverhost# su rsyncd -s /bin/sh -c 'nano -w ~/.ssh/authorized_keys'Test it
localhost# rsync -e "ssh -l rsyncd -i /root/.ssh/rsyncd" serverhost::it should return the list of available modules. Store the generated rsync password in a local password file with mode 0600, for example in /root/.config/simple_rsync_backup/host1.rsync_password:
localhost# install -d -m 700 /root/.config/simple_rsync_backup localhost# install -m 600 /dev/null /root/.config/simple_rsync_backup/host1.rsync_password
localhost# rsync -aHAX --delete -e "ssh -l rsyncd -i /root/.ssh/rsyncd" --password-file /root/.config/simple_rsync_backup/host1.rsync_password /home/ host1@serverhost::host1/If it works fine, automate it with cron, systemd timers, or another scheduler. For example, if the backup runs as root, put that command in an executable script such as /root/bin/backup_host1_clienthost and link it into /etc/cron.daily/:
localhost# ln -s /root/bin/backup_host1_clienthost /etc/cron.daily/backup_host1