Full system rsync

This script will perform a full system backup with rsync, leaving out /dev and /tmp and what not.

There are several configuration parameters:

  • target : system to back up, must be set to output of uname -n. Used as a sanity check.
  • source_dir : source directory. Set to / for full system backup.
  • target_dir : target directory. Set to the mountpoint of an external hard drive or a remote server. Note: make sure that target_dir or a parent is included in exclude_dir otherwise rsync will recurse and your hard drive will get filled up completely.
  • exclude_dir : excluded directories. Set these to temporary directories and other things you don't want backed up.
  • include_dir : included directories. Subfolders of excluded directories to include.

If you want to use this script with a remote host where the target user is not root, change flags to

flags=(-avPse ssh --delete --ignore-errors --rsync-path="rsync --fake-super")

and use a remote path of the form user@host:path for target_dir

backup-system

#!/bin/bash
set -f
 
hostname=$(hostname -s)
target="my-hostname"
 
source_dir="/"
target_dir="/media/external-hard-drive/$target/"
exclude_dir=(*/.gvfs /media /run/media /sys /dev /proc /mnt /tmp pagefile.sys hiberfil.sys /home/*/.cache .mozilla/firefox/*/Cache .Trash-1000)
include_dir=(/mnt/linux-data/opt /mnt/data)
flags=(-avPAX --delete --ignore-errors)
 
if [ -n "$target" ] ; then
  if [ "$hostname" != "$target" ] ; then
    echo Error: must be run on $target
    exit 1
  fi
fi
 
include=()
 
# process include directories
for d in "${include_dir[@]}"
do
  found=0
  dir=""
  shortest_parent="/"
 
  echo include_dir $d
 
  # Need to deal with rsync idiosyncracies
  # by including excluded directories
  # but not their contents
  # Removes matches to the include directory
  # and finds the shortest parent path
  for d2 in "${exclude_dir[@]}"
  do
    echo exclude_dir $d2
 
    if [[ $d = $d2* ]]; then
      found=1
 
      if [[ "$shortest_parent" == "/" ]]; then
        shortest_parent=$d2
      else
        if [ ${#shortest_parent} -gt ${#d2} ]; then
          shortest_parent=$d2
        fi
      fi
    fi
    echo shortest_parent $shortest_parent
  done
 
  if [[ $found ]]; then
    path=$(dirname "$d")
    while [[ "$path" != "/" ]]; do
      exclude_dir+=("$path/*")
      include+=("--include=$path")
      if [[ "$path" == "$shortest_parent" ]]; then
        break
      fi
      path=$(dirname "$path")
    done
  fi
 
  include+=("--include=$d")
done
 
exclude=()
 
for d in "${exclude_dir[@]}"
do
  exclude+=("--exclude=$d")
done
 
sudo -E rsync "${flags[@]}" "${include[@]}" "${exclude[@]}" "$source_dir" "$target_dir"