zBackups-TimeMachineMay 15, 2017

Self-service Restore of rsnapshot Backup Files

Introduction

This document describes a methodology of backing up Samba shares with rsnapshot and providing a special folder in the Samba shares (here named zBackups-TimeMachine) where users can securely recover their own files from the backup.

Time machine catalog.

I use a separate backup server to backup all my other servers. The backup server has passwordless access to all the other servers so I am most protective of people accessing the backup server.

I use rsnapshot to backup user files as it provides a space efficient method of storing a history of backups. However, because of the way rsnapshot stores its backups in folders named like hourly.0, hourly.1, ..., daily.0, daily.1, ..., and because it stores files in shares under their full path name, it makes it difficult for regular users to figure out where their backed up files are located and which version they are getting.

The script presented below maps the Samba share backups from the rsnapshot folders into a sorted list of links with names being the date of the backup. I then use Samba to share that folder back on to the network. However, I do not allow any regular users to access the Samba share for their backed up files. I instead mount the backup share on the user's server to a folder named zBackups-TimeMachine that is located in the user's share folder. That way every user who can access a share can also access the share's backup files.

Making Backup Shares

The shares on the backup server contain links to rsnapshot directories. The rsnapshot directories change (are rotated) each time rsnapshot runs. The following script should be run after every time rsnapshot runs. It will resynchronize the links in the backup shares to the proper rsnapshot directories. I place the script in /usr/local/sbin.

#!/bin/bash
SHELL=/bin/bash

# Synchronize samba share links with the rsnapshot backup files.
#
# This is necessary because share names are static whereas rsnapshot rotates its directory names.
# The top directories under hourly.0, hourly.1, ... correspond to the shares defined in /var/shares.
# The modify dates of the hourly.0, hourly.1, ... directories are used as file names in the backup shares.
# Format of the directory names in backup shares is: "nn-Weekday Month Day, Year HH:MMam"
# where nn ranges from 01 to the number of backups saved where 01 is the most recent backup. For example:
#       01-Tuesday September 24, 2013  8.00pm
#       02-Tuesday September 24, 2013  5.00pm
#       03-Tuesday September 24, 2013  2.00pm

# Note: This program should be run after every rsnapshot command. Do not use the cmd_postexec
# config setting in /etc/rsnapshot.conf as that is run only after rsync'ing the files and not
# after every rotation.

# Note: It is recommended to configure rsnapshot sync_first to rsync files as a separate step
# in the crontab file to reduce the time the links are out of sync with the backup files.

# Configure
source /etc/default/rsnapshot-samba

# Begin
# Set share root directory's last update time to be the same as the backup root.
# Samba share names for rsnapshot backups are derived from the time the backup was done.
# This should set the share root's last changed time to be the same as the share's name.
for b in $SNAPSHOT_ROOT/*
do
        for s in $SHARE_DIRS
        do
                [ -d "$b/$s" ] && touch --reference="$b" "$b/$s"
        done
done

# Construct commands that will link backups to shares.
cd $SNAPSHOT_ROOT
ls -lt --time-style=+'%A %B %-e, %Y %l.%M%P' | \
perl -ne '
BEGIN {
        our $index = 0;
        our $snapshot_root = "'$SNAPSHOT_ROOT'";
        our $shares_root = "'$SHARES_ROOT'";
        our @share_dirs = split(/\n/,<<End_Of_Share_Dirs);
'"$SHARE_DIRS"'
End_Of_Share_Dirs

        our @interval_dirs;     # hourly.0, ..., daily.0, ...
        our @backup_dates;      # e.g. "201204020000 Monday April 2, 2012 12:00am"

        print qq[#\n];
        print qq[# '"$(date)"'\n];
        print qq[#\n];
}
{
        next if $. == 1;        # skip first line (the total line produced by ls)
        chomp;
        s/([^ ]+ +){5}(.*) ([^ ]+)$/$2/;                # keep only date and interval directory name
        push(@backup_dates,$_);
        push(@interval_dirs,$3);
}
END {
        for my $share_dir (@share_dirs) {
                (my $share = $share_dir) =~ s|/.*||;    # shares are named after first directory in path
                next unless $share;

                chdir "$shares_root/$share" or next;

                # Get list of current files
                my @curr_files = <*>;
                my %seen;

                print qq[cd "$shares_root/$share"\n];
                for (my $i=0; $i < @interval_dirs; $i++) {
                        my $backup_date = $backup_dates[$i];
                        my $interval_dir = $interval_dirs[$i];
                        my $backup_path = "$snapshot_root/$interval_dir/$share_dir";
                        my $backup_link = sprintf("%02d",$i+1) . "-" . $backup_date;
                        $seen{$backup_link} = 1;

                        print qq(if [ -d "$backup_path" ]; then\n);
                        print qq(       [ -L "$backup_link" ] && rm -f "$backup_link"\n);
                        print qq(       ln -s "$backup_path" "$backup_link"\n);
                        print qq(fi\n);
                }

                # Remove existing files that are not links to the current set of backup files.
                # Remove only links and not actual files that might possibly exist.
                for my $file (@curr_files) {
                        if (! $seen{$file}) {
                                print qq([ -L "$file" ] && rm -f "$file"\n);
                        }
                }
        }
}' | /bin/bash                                                          # without log file
##}' | tee -a /var/log/rsnapshot-samba-sync-links.log | /bin/bash       # with log file

Sample /etc/default/rsnapshot-samba Directives

The following is a sample configuration file for the above script.

# Settings allowing rsnapshot backups to be sync'ed with samba shares allowing access to backups through the shares.

SNAPSHOT_ROOT=/backup/rsnapshot # location of backup directories hourly.0, hourly.1, ...
SHARES_ROOT=/var/shares         # location of samba shares

# List the directories under hourly.0, ... to the shares' top directories, one per line.
# This is the path on the host server from / to the directory that's shared (samba's path setting).
# The top directory under the interval directories (hourly.0,...) is constructed from the server
# name and the share name, e.g. server name is "homes" and share name is "accounting" so
# the directory name is "homes-accounting".
# The following settings should match the backups listed in /etc/rsnapshot.conf
SHARE_DIRS='
homes-accounting/home/accounting/share
homes-cs/home/cs/share
homes-it/home/it/share
homes-marketing/home/marketing/share
homes-production/home/production/share
homes-purchasing/home/purchasing/share
homes-sales/home/sales/share
'

Sample /etc/rsnapshot.conf Directives

The above configuration corresponds to a rsnapshot configuration similar to the following:

snapshot_root /backup/rsnapshot/
interval      hourly  24
interval      monthly 13
# Don't forget to exclude your mounted backup directories from your backup
# otherwise your backup will go in circles till you run out of resources.
exclude               /home/*/share/Backups/***
link_dest     1
sync_first    1
# Backup samba shares
# Note: These shares should also be listed in /etc/default/rsnapshot-samba
#       Also create a directory in /var/shares for the target server-share
backup        root@homes:/home/accounting/share/             homes-accounting/
backup        root@homes:/home/cs/share/                     homes-cs/
backup        root@homes:/home/it/share/                     homes-it/
backup        root@homes:/home/marketing/share/              homes-marketing/
backup        root@homes:/home/production/share/             homes-production/
backup        root@homes:/home/purchasing/share/             homes-purchasing/
backup        root@homes:/home/sales/share/                  homes-sales/

Sample /etc/crontab Directives

The following crontab entry invokes rsnapshot backups and keeps backup shares sync'ed.

# Backup snapshots of file shares
0   *	* * *	root	/usr/bin/rsnapshot sync; test $? -eq 0 -o $? -eq 2 && /usr/bin/rsnapshot hourly && /usr/local/sbin/rsnapshot-samba-sync-links
50 23	* * *	root	/usr/bin/rsnapshot daily && /usr/local/sbin/rsnapshot-samba-sync-links
40 23	* * 6	root	/usr/bin/rsnapshot weekly && /usr/local/sbin/rsnapshot-samba-sync-links
30 23	1 * *	root	/usr/bin/rsnapshot monthly && /usr/local/sbin/rsnapshot-samba-sync-links

Setting up Samba on Backup Server

Samba shares need to be set up on the backup server so that they can be mounted to directories in the user's share. I mount the shares in the /var/shares directory. Each share is named after the server name and the share name. So for example, if I'm backing up the share named Accounting on the server named Homes, I link the backups to the share homes-accounting that is located in /var/shares/homes-accounting.

I set up one user to access each backup share on the backup server. I name the user the same as the share name. So the backup share setup homes-accounting is accessed by the user homes-accounting.

- Create special user for mounting backup share back onto remote server.
- The numeric group id of this user needs to match the group id number of the remote server share group.
- Create long random password (I use KeyPass (128bit) random password option.)

- Create a group in /etc/group if group number on backup server is not yet defined.

- Use created group number as group id for new user.
useradd -M -N -d /nonexistent -s /bin/false -g 1012 homes-exec; smbpasswd -a homes-exec

- Create share for homes-exec
vi /etc/samba/smb.conf
/etc/init.d/samba reload

Sample Samba Stanza on Backup Server

Following is a sample set of instructions to set up a Samba share on the backup server that can be used to mount backups back on the user's server.

[global]
netbios name = BACKUPSERVER
hosts allow = 127. 192.168.1.
dead time = 20
unix extensions = no

[homes-accounting]
        comment = Backup of Accounting Files
        path = /var/shares/homes-accounting
        writeable = no
        read only = yes
        printable = no
        follow symlinks = yes
        wide links = yes
        valid users = homes-accounting

Mounting Backup Shares

Note: I have successfully used the autofs package to dynamically mount the backup drives when accessed instead of using the following scripts.

On the user's server the backup shares are mounted on directories within their share folders. To automatically mount the backup shares on the users' server you can edit /etc/fstab and place your passwords in a protected file. I however ended up creating a script that I copy for each backup share that needs to be mounted. It allows for remounting the shares if there is a network disconnect.

I created the directory /etc/mount-shares.d on the users' server and made copies of the following script there. Then to automatically remount the shares when the server reboots I put the line

run-parts /etc/mount-shares.d

into the /etc/rc.local file. The scripts should be owned by root and only readable by root as the passwords to the backup shares are embedded in them.

Sample server-share Mount Script

These scripts execute on the users' server to mount the backup server shares on to directories within the users' share.

#!/bin/bash

#
# Config
#

SHARE=//backupbox/homes-accounting
MOUNT=/home/accounting/share/Backups

# Remote user id the mount program is to use to connect to the server share.
export USER=homes-accounting
export PASSWD=AVeryLongArbitraryPasswordThatsHardToGuess

# Mounted files appear to be owned by following user and group.
local_user=accounting
local_group=accounting

# Mount share as read-only or read/write
#mode=rw        # read/write
mode=ro         # read-only

#
# Begin
#

[ "$1" = '-f' ] && { FORCE=-f; shift; }

[ -d "$MOUNT" ] || mkdir --mode=0555 "$MOUNT"

if mount | grep "on $MOUNT type" >/dev/null 2>&1
then
	if [ $FORCE ]
	then
		umount "$MOUNT"
		sleep 3
	else
		exit    # already mounted
	fi
fi

mount -t cifs -o uid=$local_user,gid=$local_group,$mode "$SHARE" "$MOUNT"