Table des matières

rsync & ssh

Pour réaliser le backup d'un dossier vers un dossier d'une autre machine il faut :

  1. Disposer d'un accès à la machine distante via SSH (installation server SSH)
  1. Installer les clés SSH pour se connecter sans entrer de mot de passe
  2. Utiliser les scripts de backup.

Le script bash de backup principal (générique)

Ce script n'est rien d'autre qu'un wrapper de rsync/ssh. Il peut être appelé directement, mais il est plutôt conseillé de l'appeler depuis un script spécifique qui fixe tous les paramètres (cela facilite la mise au point). Voir exemple plus bas.

sshDirSynchro.sh
#!/bin/bash
# sshDirSynchro.sh
#
#   Backup directory(ies) from/to local directory to/from remote directory using rsync/ssh
#   It is just an Rsync wrapper.
#
# V0.1 - 2014-08-17
# V0.2 - 2014-08-18
#      Suppression du rapport de syncho intégré
#      Suppression de messages
# V0.3 - 2014-08-18
#      l'option "--delete" de rsync ne doit pas être positionnée par défaut (trop dangereux).
#      l'option "--delete" est ajoutée en parametre
#      l'option "--chmod" est ajoutée en parametre : elle emble nécessaire pour avoir des droits corrects
#      lors d'une copie depuis Windows (on fixe les droits chmod=u+rwx).
 
TIME_START_1970=$(date +%s)
 
#-------------------------------------------------------------
# Define usage
#-------------------------------------------------------------
 
usage() {
    cat <<-'_usage_statement_'
 
sshDirSynchro.sh: synchronizes a local directory and a distant directory using rsync/ssh.
 
Usage: sshDirSynchro.sh --backup|--restore -ld <localDirectory> -rh <remotHost> -ru <remoteUser> -rd <remoteDirectory> [ -h -p <sshPort> -bwl <bandWidthLimit> -ksd <killSleepDelay> -lf <logFile> --delete --chmod --inconv --check]
                ]
 
Mandatory parameters:    
  --backup             
        copy from local to remote
  --restore            
        copy from  remote to local
  -ld, --local-dir         
        Local directory from where the data are copied
  -rh, --remote-host       
        Remote host to where the data are copied 
  -ru, --remote-user       
        Authorized user for the remote host
  -rd, --remote-dir        
        Remote directory to where the data are copied 
 
Optionnal parameters:
  -h, --help
        Display usage information.    
  -p, --ssh-port
        Default port is 22.    
  -bwl, --bandwidth-limit
        Embeded "--bwlimit" for rsync
  -ksd, --kill-sleep-delay
        Permit to kill the script after a user defined delay. 
        Exemple: -ksd "500s" for 500 seconds or -ksd "5h" for 5 hour. 
        See man sleep for delay format.
  -lf, --log-file
        If no log file is set, the report is writen on stdout.
        If -lf is set a log file is created for the report.
  --delete                 
        rsync delete extraneous files from dest dirs
  --chmod                  
        ensure chmod=u+rwx on remote host
  --inconv
		convert chrset from Windows to Linux
  --check
		compare local and remote dir content
 
*** Caution:
    This script use several ssh commands in batch mode. To avoid password asking ssh keys must be installed.
    See man ssh-keygen
 
_usage_statement_
}
 
#-------------------------------------------------------------
# Analyse les arguments de la ligne de commande.
#-------------------------------------------------------------
SSH_PORT=22
# echo -e "SYNC_OPTION: ${SYNC_OPTION} - $#"
while [[ $# > 0 ]]
do
# echo -e "$# - OPTION: $1"
   case $1 in
        --backup) 
			if [ "${SYNC_OPTION}" ]
			then
			   echo -e "Error: You must choose between --backup OR --restore!"  
			   exit 1
			fi
            SYNC_OPTION="backup"
            shift
            ;;
        --restore) 
			if [ "${SYNC_OPTION}" ]
			then
			   echo -e "Error: You must choose between --backup OR --restore!"  
			   exit 1
			fi
            SYNC_OPTION="restore"
            shift
            ;;
        -ld|--local-dir) 
            LOCAL_WORKING_DIR=$2;
            shift 2
            ;;
        -rh|--remote-host) 
            REMOTE_HOST=$2
            shift 2
            ;;
        -ru|--remote-user) 
            REMOTE_USER=$2
            shift 2
            ;;
        -rd|--remote-dir) 
            REMOTE_WORKING_DIR=$2
            shift 2
            ;;
        -p|--ssh-port) 
            SSH_PORT=$2
            shift 2
            ;;
        -bwl|--bandwidth-limit) 
            BAND_WIDTH_LIMIT=$2
            shift 2
            ;;
        -ksd|--kill-sleep-delay) 
            KILL_SLEEP_DELAY=$2
            shift 2
            ;;
        -lf|--log-file) 
            LOGFILE="`basename $0`.`date +%Y\-%m\-%d\-%Hh%Mmn%S`.log"
            shift
            ;;
        --delete) 
            DELETE_OPTION="--delete-before"
            shift
            ;;
        --chmod) 
            CHMOD_OPTION="--chmod=u=rwX"
            shift
            ;;
        --iconv) 
            ICONV_OPTION="--iconv=ISO-8859-1,utf-8 --protect-args"
            shift
            ;;
        --check) 
            CHECK=$1
            shift
            ;;
        -h|--help) 
            usage
            exit 0
            ;;
         *) # Unknown option
            echo -e "Error : Unknown option $1\n"
            usage
            exit -1
            ;;
   esac
done
 
[ "${SYNC_OPTION}" ] || echo -e "\nError: --backup or --restore must be specified!"  
[ "${LOCAL_WORKING_DIR}" ] || echo -e "\nError --local-dir must be defined!!"  
[ ${REMOTE_HOST} ] || echo -e "\nError REMOTE_HOST must be defined!!"    
[ ${REMOTE_USER} ] || echo -e "\nError REMOTE_USER must be defined!!"    
[ ${REMOTE_WORKING_DIR} ] || echo -e "\nError REMOTE_WORKING_DIR must be defined!!"    
 
if [ ! "${LOCAL_WORKING_DIR}" ] || [ ! "${REMOTE_HOST}" ] || [ ! "${REMOTE_USER}" ] || [ ! "${REMOTE_WORKING_DIR}" ]
then
   usage
   exit -1
fi
 
#-------------------------------------------------------------
# Create a custom logger
#------------------------------------------------------------
 
if [ ${LOGFILE} ]
then
   logprint() {
      #echo -e "$(date +%T): $*" >> $LOGFILE     
      echo -e "$*" >> $LOGFILE     
   }
else
   logprint() {
      #echo -e "$(date +%T): $*" 
      echo -e "$*" 
   }
fi
 
#-------------------------------------------------------------
# Test local diretory is ok
#-------------------------------------------------------------
 
# if [ ! -d ${LOCAL_WORKING_DIR} ] 
# then
   # logprint  "ERROR: ${LOCAL_WORKING_DIR} does not exist!";
   # exit -1 
# fi
 
#-------------------------------------------------------------
# Test remote host is ok
#-------------------------------------------------------------
 
if [ $LOGFILE ]
then
   echo -e "Resulats dans `pwd`/$LOGFILE"
fi
 
# if ( ! ssh $REMOTE_USER@$REMOTE_HOST -p $SSH_PORT "pwd" > /dev/null 2>&1 )
# then
   # logprint  "ERROR: $REMOTE_HOST unreachable!"
   # exit -1
# fi
 
# if ( ! ssh $REMOTE_USER@$REMOTE_HOST -p $SSH_PORT "cd $REMOTE_WORKING_DIR" > /dev/null 2>&1 )
# then
   # logprint  "ERROR: $REMOTE_HOST/$REMOTE_WORKING_DIR unreachable!" 
   # exit -1
# fi
 
#-------------------------------------------------------------
# Lancement du kill de ce processus en tâche de fond
# pour limiter la durée d'execution (au cas où cela déborderait)
#-------------------------------------------------------------
# Attention:
# la commande rsync s'execute un dans un process fils
# l'utilisation du ssh crée un nouveau processus fils du rsync.
# il faut donc faire un "pkill -P $$" pour tuer l'arborescence des processus.
 
if [ ${KILL_SLEEP_DELAY} ]
then
   logprint "Ce script stoppera dans $KILL_SLEEP_DELAY"
 
   sleep $KILL_SLEEP_DELAY \
      && logprint  \
      && logprint  "<<STOP SYNCHRO>> Le processus est tué car il dépasse le temps imparti" \
      && logprint  \
      && pkill -P $$ &
   # variante d'écriture : (sleep 6s; kill $$ ) &
   # logprint  "PID processus kill : $! - delay: $SLEEP_DELAY"
 
   # On stocke le PID de process lancé en backgound pour pouvoir le tuer
   KILL_ME_PID=$!
fi
 
#-------------------------------------------------------------
# Recupération sur erreur
#-------------------------------------------------------------
function traitement_erreur {
   kill $KILL_ME_PID > /dev/null 2>&1
   logprint  
   logprint  "---------------------"
   logprint  ">>> Erreur $? >>>>"
   logprint  "---------------------"
   logprint  
   # remote_report
   exit -1
}
trap traitement_erreur SIGHUP SIGINT SIGTERM SIGKILL
 
#-------------------------------------------------------------
# Set rsync options
#------------------------------------------------------------
# options rsync:
# -a, --archive               mode archivage; identique à -rlptgoD (pas -H)
# -v, --verbose               plus loquace
# -S, --sparse                traite les fichiers à trous efficacement
# -P                          équivalent à --partial --progress
#     --progress              montre l'avancement pendant le transfert
#     --partial               conserve les fichiers partiellement transférés 
#     --delete                delete extraneous files from dest dirs
#     --delete-before         receiver deletes before xfer, not during
# --no-p                      disables permissions copying
# --no-g                      disables group copying
# --chmod=ugo=rwX             ensures that all non-masked bits get enabled
# --delete-excluded           also delete excluded files from dest dirs
# --exclude=PATTERN           exclude files matching PATTERN
# -s, --protect-args          no space-splitting; wildcard chars only
# --iconv=ISO-8859-1,utf-8    de Windows vers Linux
 
# RSYNC_OPTIONS=" -avS --delete-before --bwlimit=$BAND_WIDTH_LIMIT -e \"ssh -p $SSH_PORT \" "
# le -e \"ssh -p $SSH_PORT \" pose problème dans RSYNC_OPTIONS
# le --chmod=u=rwX semble nécessaire pour le Raspi
 
RSYNC_OPTIONS="-avS --no-p --no-g --stats"
[ ${CHMOD_OPTION} ] && RSYNC_OPTIONS="${RSYNC_OPTIONS} ${CHMOD_OPTION}"
[ ${DELETE_OPTION} ] && RSYNC_OPTIONS="${RSYNC_OPTIONS} ${DELETE_OPTION}"
[ "${ICONV_OPTION}" ] && RSYNC_OPTIONS="${RSYNC_OPTIONS} ${ICONV_OPTION}"
[ ${BAND_WIDTH_LIMIT} ] && RSYNC_OPTIONS="${RSYNC_OPTIONS} --bwlimit=$BAND_WIDTH_LIMIT"
#LOGFILE="`basename $0`.`date +%Y\-%m\-%d\-%Hh%Mmn%S`.log"
 
#logprint "------------------------------------------------------"
logprint "Synchronisation"
if [[ ${SYNC_OPTION} = "backup" ]] 
then
	logprint "   de   `hostname` ${LOCAL_WORKING_DIR}"
	logprint "   vers $REMOTE_USER@$REMOTE_HOST:$SSH_PORT $REMOTE_WORKING_DIR"
fi
if [[ ${SYNC_OPTION} = "restore" ]] 
then
	logprint "   De    $REMOTE_USER@$REMOTE_HOST:$SSH_PORT $REMOTE_WORKING_DIR"
	logprint "   Vers  `hostname` ${LOCAL_WORKING_DIR}"
fi
logprint "   bwlimit=$BAND_WIDTH_LIMIT, delay max=$KILL_SLEEP_DELAY"
logprint "   rsync options: ${RSYNC_OPTIONS}"
 
 
#-------------------------------------------------------------
# Build final command
#-------------------------------------------------------------
 
RSYNC_COMMAND="rsync $RSYNC_OPTIONS -e \"ssh -p $SSH_PORT\""
[ "${SYNC_OPTION}" = "backup" ] && RSYNC_COMMAND="${RSYNC_COMMAND} \"${LOCAL_WORKING_DIR}\" $REMOTE_USER@$REMOTE_HOST:\"$REMOTE_WORKING_DIR\""
[ "${SYNC_OPTION}" = "restore" ] && RSYNC_COMMAND="${RSYNC_COMMAND} $REMOTE_USER@$REMOTE_HOST:\"$REMOTE_WORKING_DIR\" \"${LOCAL_WORKING_DIR}\" "
[ ${LOGFILE} ] && RSYNC_COMMAND="${RSYNC_COMMAND} >> $LOGFILE 2>&1"
 
logprint "\n$RSYNC_COMMAND"
 
# !!!! eval introduces potential vulnerabilities and unexpected behavior situations so must be used !!!
 
eval ${RSYNC_COMMAND}  || {
   logprint
   logprint "**************************************"
   logprint "\n\n   Erreur sur RSYNC: $?\n\n"
   logprint "**************************************"
   exit -2
}
 
[ "${CHECK}" ] && compareLocalAndRemoteDirs.sh -ld "${LOCAL_WORKING_DIR}" -rd "$REMOTE_WORKING_DIR" -rh $REMOTE_HOST -ru $REMOTE_USER -p $SSH_PORT
 
#-------------------------------------------------------------
# On tue le process de kill (qu'il existe ou pas)
# afin qu'il n'interrompe pas rapport d'execution
#-------------------------------------------------------------
if [ ${KILL_SLEEP_DELAY} ]
then
   # logprint  "*** processus de kill $! tué"
   kill $KILL_ME_PID > /dev/null 2>&1
fi
 
#-------------------------------------------------------------
# fin des opération
#-------------------------------------------------------------
TIME_END_1970=$(date +%s)
logprint
logprint "******** $(date +%T) - $(hostname): Fin de $(basename $0) OK! ($(convertDuration.sh $(expr  $TIME_END_1970 - $TIME_START_1970))) **********"

Le script contrôle du backup (générique)

Ce script compare des repertoires qui euvent être locaux et/ou distants. Voir exemple plus bas.

compareDirs.sh
#!/bin/bash
#
#   Compare the content of a local and a remote directories
#
# V0.1 - 2014-11-23
#    Missing: SSH port management
 
#-------------------------------------------------------------
# Define usage
#-------------------------------------------------------------
 
usage() {
    cat <<-'_usage_statement_'
 
compareDirs.sh compares the top level content of 2 local or remote directories using du.
 
Usage: compareDirs.sh [[user@]host1:]dir1 [[user@]host2:]dir2
 
Note: SSH port management is missing.
 
_usage_statement_
}
 
#-------------------------------------------------------------
# Analyse les arguments de la ligne de commande.
#-------------------------------------------------------------
 
echo -e "\n$(basename $0) $@ \n"
 
[[ $# != 2 ]] && {
   usage
   exit -1
}
 
function getDirFromParam {
   idx=`expr index "$1" ':' `
   if [ $idx == 0 ]
   then
      echo $1
   else
      echo ${1:$idx}
   fi
}
 
function getHostFromParam {
   idx=`expr index "$1" ':' `
   if [ $idx != 0 ]
   then
      ((idx--))
      echo ${1:0:$idx}
   fi
}
 
function checkHostAndDir {
   dir=$1
   host=$2
   if [ $host ]
   then
      # echo "$host - $dir --> REMOTE"
      if ( ! ssh $host "pwd" > /dev/null 2>&1 )
      then
         echo  "ERROR: $host unreachable!"
         return  1
      elif ( ! ssh $host "cd $dir" > /dev/null 2>&1 )
      then
         echo  "ERROR: $dir unreachable on $host!" 
         return  2
      fi
   else 
      # echo "$dir  --> Local"
      [ -d "${dir}" ] || {
         echo -e "Error $dir must be a directory!"  
         return  3
      }
   fi
   return  0
}
 
dir1=$(getDirFromParam $1)
host1=$(getHostFromParam $1)
dir2=$(getDirFromParam $2)
host2=$(getHostFromParam $2)
 
# echo -e "dir1 : $dir1 - host1 : $host1\ndir2 : $dir2 - host2 : $host2"
 
checkHostAndDir "$dir1" $host1
[ $? -eq 0 ] || exit -1
checkHostAndDir "$dir2" $host2
[ $? -eq 0 ] || exit -1
 
#-------------------------------------------------------------
# VARS
#-------------------------------------------------------------
 
tmp_report_1="/tmp/`basename $0`-local-`date +%F-%T`.log"
tmp_report_2="/tmp/`basename $0`-remote-`date +%F-%T`.log"
 
#-------------------------------------------------------------
# Recupération sur erreur
#-------------------------------------------------------------
function traitement_erreur {
   echo  
   echo  "---------------------"
   echo  ">>> Erreur $? >>>>"
   echo  "---------------------"
   echo  
   rm -f $tmp_report_1 $tmp_report_2
   exit 1
}
trap traitement_erreur SIGHUP SIGINT SIGTERM SIGKILL
 
#-------------------------------------------------------------
# fonction de bilan de synchro
# déclarée ici pour pouvoir être utilisée par le traitement d'erreur qui suit
#-------------------------------------------------------------
 
function local_report {
   function numFormat { 
      echo $1  | sed ':a;s/\B[0-9]\{3\}\>/.&/;ta' 
   }
	function stats_folder() {
	  # Attention : le nom du dossier peut contenir des blancs
	  # il faut donc le traiter entre ""
	  local folder="$1"
	  local items=`find "$folder" -iname '*' | wc -l`
	  local taille=0
	  echo  -e "   $folder" 
	  # echo  -e "   $(basename $folder)" 
	  if [ $items -gt 0 ]; then
		 taille=$(du -bc "$folder" | grep total | sed -e "s/[ \t]*total/ /g")
       taille_f=$(numFormat $taille)
       items_f=$(numFormat $items)
		 echo -e "      $items_f items, $taille_f octets" 
	  else 
		 echo vide
	  fi
	}
   local curdir=$(pwd)
   local dir="$(readlink -f "$1")"
	echo -e "LOCAL report:"
   echo -e "$dir"
   cd "$dir"
	for f in * ; do
		# Attention : le nom du dossier peut contenir des blancs
		# il faut donc le passer entre ""
      #echo "---f: $f"
		[ -d "$f" ] && stats_folder "$f"
	done
	echo -e "Total: " 
	stats_folder "$dir"
   # echo "--- pwd: $(pwd) "
   cd "$curdir"
	echo
}
 
function remote_report {
   local dir="$1"
   local host="$2"
   ssh $host "
      function numFormat { 
         echo \$1  | sed ':a;s/\\B[0-9]\\{3\}\\>/.&/;ta' 
      }
      function stats_folder() {
         # Attention : le nom du dossier peut contenir des blancs
         # il faut donc le traiter entre \"\"
         local folder=\"\$1\"
         local items=\`find \"\$folder\" -iname '*' | wc -l\`
	      local taille=0 
         echo  -e \"   \$folder\" 
         if test \$items -ne 0; then
		      taille=\$(du -bc \"\$folder\" | grep total | sed -e \"s/[ \t]*total/ /g\")
            taille_f=\$(numFormat \$taille)
            items_f=\$(numFormat \$items)
            echo -e \"      \$items_f items, \$taille_f octets\" 
         else 
            echo vide
         fi
      }
 
      dir=$(readlink -f "$dir")
      echo \"REMOTE report: \"
      echo $dir
      cd $dir
      for folder in * ; do
         # Attention : le nom du dossier peut contenir des blancs
         # il faut donc le passer entre \"\"
         [ -d \"\$folder\" ] && stats_folder \"\$folder\"
      done
      echo -e \"Total: \" 
      stats_folder \"$dir\"
      echo
   "
}
echo
 
# set -x
 
if [ $host1 ]
then
   remote_report "$dir1" $host1 >> $tmp_report_1
else 
   local_report "$dir1" >> $tmp_report_1
fi
 
if [ $host2 ]
then
   remote_report "$dir2" $host2 >> $tmp_report_2
else 
   local_report "$dir2" >> $tmp_report_2
fi
 
sdiff -atw 140 $tmp_report_1 $tmp_report_2
rm -f $tmp_report_1 $tmp_report_2

Un exemple de script de backup

#!/bin/bash

N73SM="n73sm"
RASPI="raspi"
BUREAU="HAL"

DIR_SCRIPTS_BUREAU="/home/Roge/scripts/"
DIR_SCRIPTS_N73SM="/media/hd2/scripts/"
DIR_SCRIPTS_RASPI="/media/hd1/roge/scripts/"

case `hostname` in
  $N73SM) 
      # Scripts : N73SM --> Raspi
      echo -e "Backup Scripts : N73SM --> Raspi"
      ./sshDirSynchro.sh --backup --check -rh $RASPI -ru roge \
         -rd $DIR_SCRIPTS_RASPI \
         -ld $DIR_SCRIPTS_N73SM   
      ;;
  $BUREAU) 
      # Scripts : Bureau --> N73SM
      echo -e "Backup Scripts : Bureau --> N73SM"
       ./sshDirSynchro.sh --backup --check  --delete -rh $N73SM -ru roge \
         -rd $DIR_SCRIPTS_N73SM \
         -ld $DIR_SCRIPTS_BUREAU   
 
      # execution sur N73SM pour alimenter le Raspi
      ssh roge@n73sm "cd /media/hd2/scripts; ./$(basename $0)"
      ;;
   *) # Unknown option
      echo "ERROR: `basename $0` is not authorized to run on `hostname` host!"
      exit -1
      ;;
esac

Un exemple de rapport de backup :

Le rapport suivant est produit par le script ci-dessus :

$ ./bkpScripts.sh
Backup Scripts : Bureau --> N73SM
09:18:47: ------------------------------------------------------
09:18:47: Synchronisation
09:18:47:    de   HAL /home/Roge/scripts/
09:18:47:    vers roge@n73sm:22 /media/hd2/scripts/
09:18:47:    bwlimit=, delay max=
09:18:47:    rsync options: -avS --no-p --no-g --delete-before
09:18:47: ------------------------------------------------------
09:18:47:
09:18:47: >> rsync -avS --no-p --no-g --delete-before -e "ssh -p 22" "/home/Roge/scripts/" roge@n73sm:"/media/hd2/scripts/"
building file list ... done
deleting sshDirectorySynchro.sh
./
archive/
archive/sshDirectorySynchro.sh

sent 1429 bytes  received 121 bytes  3100.00 bytes/sec
total size is 175567  speedup is 113.27
09:18:47: ******** Fin du traitment OK! **********
09:18:47:

--------------                                                          --------------
LOCAL report :                                                       |  REMOTE report :
--------------                                                          --------------
LOCAL_WORKING_DIR: /home/Roge/scripts/                               |  REMOTE_WORKING_DIR: /media/hd2/scripts/
  - archive: 21 fichiers, 133K                                       |    - archive: 21 fichiers, 120K
  - exemples: 6 fichiers, 24K                                             - exemples: 6 fichiers, 24K
  - restore: 18 fichiers, 96K                                             - restore: 18 fichiers, 96K
  - test: 1 fichiers, 4,0K                                                - test: 1 fichiers, 4,0K
--------------------------                                              --------------------------
Total:                                                                  Total:
  - .: 57 fichiers, 329K                                             |    - .: 57 fichiers, 312K

Autres solutions

Tester websync