#! /bin/bash

# clone uses rsync to copy files between systems. Behavior is based on 
# the content of configuration files in the user's home directory.
# Operation of this script assumes the user has configured passwordless 
# access between systems (see doc on ssh-keygen).
#
# Written 2007 Apr 10 v00 By Than Putzig.
# Updated 2010 May 12     To generalize for any system.
# Updated 2012 Jan 30     To use --keep-dirlinks option.
# Updated 2012 Mar 05 v01 To process port specifications from ".clone_remote".
# Updated 2012 Jun 19 v02 For system-specific cloning e.g. ".clone_hostname".
# Updated 2012 Aug 14 v03 Add -u and *-u_local, *-u_remote for update cloning,
#		          two-way -u" (turns off "--delete").
# Updated 2012 Dec 13 v04 Add -r and *-r_local, *-r_remote for reverse cloning.
#                         -r turns off forward direction but will run with 
#			  --update for .clone-u* unless -d is specified as well.
# Updated 2012 Dec 31 v05 Add -d to force --delete even for .clone-u* files
#                         (foregoes reverse-direction).
# Updated 2013 Mar 20 v06 To not write log file with -h or invalid option.
#                         Added "details" to display prior to "usage" with -h 
#                         Include STDERR to log file messages.
#                         Use tee to display messages to log file & screen (-s).
# Updated 2015 Aug 12 v07 To ignore comments configuration files (grep -v\#).
# Updated 2015 Dec 31 v08 To use --copy-dirlinks option.

version='08'
lsys=`hostname`		# hostname of the local system
# At least one configuration file is required for this script to take action.
# The following paired configuration files for one-way rsync "--delete" and
# two-way "rsync --update" can be used with this script:
clocd=~/.clone_local	# List of files and directories to rsync with --delete
cremd=~/.clone_remote	# List of host:dir of target systems
clocr=~/.clone-r_local	# List of files and directories to rsync in reverse
cremr=~/.clone-r_remote	# List of host:dir of source systems
clocu=~/.clone-u_local	# List of files and directories to rsync with --update
cremu=~/.clone-u_remote	# List of host:dir of target/source systems

# Alternatively/additionally, one can use system-specific configuration files
# such as ".clone_host:port:dir" or ".clone-u_host:~" or ".clone_host" 
# or ".clone-r_host" with content same as that for .clone*_local.

# List current configuration files.
for file in ~/.clone* 
do cfiles=( ${cfiles[*]} \\t$file\\n )	
done

# Detailed description of the clone script to display with -h
details="$0 version $version\n
\n
This rsync wrapper script copies files between the local system and\n
one or more remote systems. Configuration files in the user's home\n
directory on the local system may be paired or system-specific.\n
\n
Paired configuration files:\n
\t	For one-way 'rsync --delete' w/ local source, remote target(s):\n
\t	$clocd \t	Local files and directories (source)\n
\t	$cremd \t	Remote systems (targets)\n
\n
\t	For two-way 'rsync --update' between local and remote(s):\n
\t	$clocu \t	Local files and directories\n
\t	$cremu \t	Remote systems\n
\n
\t	For one-way 'rsync --delete' w/ remote source(s), local target:\n
\t	$clocr \t	Local files and directories (target)\n
\t	$cremr \t	Remote systems (sources)\n
\n
System-specific configuration files:\n
\t	.clone_host \t	Local sources to 'rsync --delete' to 'host'\n
\t	.clone-u_host\t	Local sources/targets to 'rsync --update' with 'host'\n
\t	.clone-r_host\t	Local targets to 'rsync --delete' from 'host'\n
\n
\t	'host' may include a username, a directory, or a port and directory:\n
\t	\t	.clone_user@host\n
\t	\t	.clone_host:dir\n
\t	\t	.clone_host:port:dir\n
\n
Example entries for '.clone*_local' and '.clone*_host':\n
\t	file\n
\t	dir\n
\t	dir/file\n
\n
Example entries for '.clone*_remote':\n
\t	host\n
\t	user@host\n
\t	sub.dom.org:dir/subdir\n
\t	host:port:dir\n
\n
You have ${#cfiles[@]} configuration files on $lsys:\n
${cfiles[*]}
\n
---------------------------------------------------------------------\n
"
# Usage description to display with -h and when an invalid option is given
usage="Usage: $0 [-udrnvsh]\n
\n
options to override default behavior for all configuration files:\n
\t -u \t two-way --update\n
\t -d \t one-way --delete\n
\t -r \t one-way rsync TO local FROM remote\n
\t -n \t dryrun (no copying occurs)\n
\t -v \t verbose messages\n
\t -s \t display messages to screen as well as log file\n
\t -h \t display full usage and exit\n
\n
\t Use 'clone -ud' to invert all normal delete/update settings.\n
\t Use 'clone -rd' to rsync TO local FROM remote with --delete.\n
"

# set default command options (all off)
update=0
delete=0
reverse=0
screen=0
dryrun=0
verbose=0

# process command options
while getopts udrnvsh O
do
 case $O in
      u) update=1  ;;	# this option forces --update and turns off --delete
      d) delete=1  ;;	# this option forces --delete and turns off --update
      r) reverse=1 ;;	# this option will rsync TO local FROM remote 
      n) dryrun=1  ;;	# run rsync with -n "dryrun" option
      v) verbose=1 ;;   # display rsync commands
      s) screen=1 ;;	# displays log file contents at end
      h) echo -e $details $usage | less
         exit 0 ;;
     \?) echo invalid option $OPTARG
         echo -e $usage
        exit 1 ;;
 esac
done

# The script creates a log file clone.log in this directory: 
cldr=~/logs
clog=$cldr/clone.log
# create log directory if it doesn't exist
for dir in $cldr
do if [ ! -e $dir ]
   then mkdir $cldr
        echo Created required log directory $dir 
   fi
done
# initialize log file with time-date stamp
now=`date`
echo running $0 v$version $now > $clog 2>&1

if [ $screen = 1 ]
then cat $clog
     redir="2>&1 | tee -a $clog"
else redir=">> $clog 2>&1"
fi

# set dopt to update flag if option -u is given, otherwise set to delete flag
if [ $update = 1 ]; then dopt="--update"; else dopt="--delete"; fi

# set uopt to delete flag if option -d is given, otherwise set to update flag
if [ $delete = 1 ]; then uopt="--delete"; else uopt="--update"; fi

# make array of system-specific configuration files with -u in name 
for file in ~/.clone-u_*
do if [[ -e "$file" ]] 
   then ext=`echo $file | awk -F_ '{ print $2 }'`
	if [[ "$ext" != "local" && "$ext" != "remote" ]]
	then csyfu=( ${csyfu[*]} $file )
	fi
   fi
done
nsyu=${#csyfu[*]}	# count number of sys-specific config files with -u

# make array of system-specific configuration files without -u or -r in name 
for file in ~/.clone_*
do if [[ -e "$file" ]]
   then ext=`echo $file | awk -F_ '{ print $2 }'`
	if [[ "$ext" != "local" && "$ext" != "remote" ]]
	then csyfd=( ${csyfd[*]} $file )
	fi
   fi
done
nsyd=${#csyfd[*]}	# count no. of sys-specific config files without -u,-r

# combine for array of system-specific configuration files (with -u first)
csyf=( ${csyfu[*]} ${csyfd[*]} )

# strip system names to make array of system-specific systems
for file in ${csyf[*]}
do 
   csy=( `echo $file | awk -F_ '{ print $2 }'` )
   csys=( ${csys[*]} $csy )
done

# build array of update or delete options
for (( n=0; n<$nsyu; n++ )); do duopts=( ${duopts[*]} $uopt ); done
for (( n=0; n<$nsyd; n++ )); do duopts=( ${duopts[*]} $dopt ); done

# append .clone-u_local, .clone-u_remote contents, opts if .clone-u_local exists
if [ -e $clocu ]
then for rsys in `cat $cremu | grep -v ^\#`
     do csys=( ${csys[*]} $rsys )
        csyf=( ${csyf[*]} $clocu )
        duopts=( ${duopts[*]} $uopt )
     done
fi
# append .clone_local, .clone_remote contents, opts if .clone_local exists
if [ -e $clocd ]
then for rsys in `cat $cremd | grep -v ^\#`
     do csys=( ${csys[*]} $rsys )
        csyf=( ${csyf[*]} $clocd )
        duopts=( ${duopts[*]} $dopt )
     done
fi
if [ $reverse = 1 ]
then nfor=0		# turn off forward direction for all rsync comands
else nfor=${#csys[*]}	# count number of forward-direction local-remote pairs
fi

# make array of system-specific configuration files with -r in name 
for file in ~/.clone-r_*
do if [[ -e "$file" ]] 
   then ext=`echo $file | awk -F_ '{ print $2 }'`
	if [[ "$ext" != "local" && "$ext" != "remote" ]]
	then csyfr=( ${csyfr[*]} $file )
	fi
   fi
done
nsyr=${#csyfr[*]}	   # count number of sys-specific config files with -r
csyf=( ${csyf[*]} ${csyfr[*]} ) # append to system-specific configuration files array
# strip system names to append to array of system-specific systems
for file in ${csyfr[*]}
do 
   csy=( `echo $file | awk -F_ '{ print $2 }'` )
   csys=( ${csys[*]} $csy )
done

# append to array of update or delete options
for (( n=0; n<$nsyr; n++ )); do duopts=( ${duopts[*]} $dopt ); done

# append .clone-r_local, .clone-r_remote contents, opts if .clone-r_local exists
if [ -e $clocr ]
then for rsys in `cat $cremr | grep -v ^\#`
     do csys=( ${csys[*]} $rsys )
        csyf=( ${csyf[*]} $clocr )
        duopts=( ${duopts[*]} $dopt )
     done
fi

# build invariant rsync options; dagger & gladius need r (usu. redund. with a)
oopts="-arvz --copy-dirlinks --keep-dirlinks"
# add dryrun flag if option -n is given
if [ $dryrun = 1 ]
then oopts="-n ${oopts}"
fi

nsys=${#csys[*]}	# count total number of local-remote pairs
if [ $verbose = 1 ]
then eval echo number of local-remote pairs is $nsys $redir
     eval echo remote systems: ${csys[*]} $redir
     eval echo file lists: ${csyf[*]} $redir
     eval echo duopts: ${duopts[*]} $redir
fi

for (( n=0; n<$nsys; n++ ))	# loop on the clones
do rsys="${csys[n]}"
   csyfnt="${csyf[n]}.tmp"	# temporary "from" file
   cat ${csyf[n]} | grep -v \#  > $csyfnt	# remove comment lines
   ropts="${oopts} ${duopts[n]} --files-from=$csyfnt" # final rsync opts
   earg="ssh"
   # perform rsync for files associated with each clone
   sport=""		# initialize string to report port number
   if [ $verbose = 1 ]
   then eval echo remote is $rsys $redir
   fi
   # Count colon-separated elements in clone-system specification
   nelem=`echo $rsys | awk -F: '{ print NF }'`
   if [ $nelem = 3 ] 	# Assume second of three elements is a port number
   then rhost=`echo $rsys | awk -F: '{ print $1 }'`
        rport=`echo $rsys | awk -F: '{ print $2 }'`
        rdest=`echo $rsys | awk -F: '{ print $3 }'`
        rsys="${rhost}:${rdest}"
        sport="on port $rport"
        earg="ssh -p $rport"
   fi
   if [ $nelem = 1 ] # Use home dir as destination if not given
   then rsys="${rsys}:~"
   fi
   eval echo ------------------------------------------------------ $redir
   if [ "$n" -lt "$nfor" ]
   then eval echo cloning $lsys files to $rsys $sport $redir
        if [ $verbose = 1 ]
        then eval echo "command: rsync $ropts -e \"$earg\" ~ $rsys $redir"
        fi
        eval rsync $ropts -e \"$earg\" ~ $rsys $redir
   fi
   if [ "${duopts[n]}" == "--update"  ] || [ "$n" -ge "$nfor" ]
   then eval echo cloning files to $lsys from $rsys $sport $redir
        eval rsync $ropts -e \"$earg\" $rsys ~ $redir
        if [ $verbose = 1 ]
        then eval echo "command: rsync $ropts -e \"$earg\" $rsys ~ $redir"
        fi
   fi
   rm $csyfnt
done
