#!/bin/sh

# ClonePanel - Manages duplicate accounts on two or more webservers,
# including snapshot backups, monitoring and failover dns.
# Copyright (C)2006 Chris Cheers, Internet Lynx.
# Contact chris[at]clonepanel[dot]com.
# Internet Lynx, PO Box 7117, Mannering Park, NSW 2259, Australia
#
# Credits:
# This script is based in no small part on Mike Rubel's excellent tutorial
# http://www.mikerubel.org/computers/rsync_snapshots/

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; either version 2
# of the License, or (at your option) any later version.

# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.

# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.

Version=0.33
# sync -h for built-in documentation

set -u
# Be strict about variable declaration

unset PATH
#avoid use of $PATH - limit script to system commands we choose

PROGRAM_DIR=$(/bin/echo $0 |/bin/sed -e "s/\/[a-zA-Z0-9_]*$//")
# Extract the working directory from command line
# NB - if your system doesn't have echo and sed in these locations
# then this line will need to be changed in all shell scripts

# Standard include files:
. $PROGRAM_DIR/includes

# Built-in documentation:
function showdocs {
	$CAT <<-EODOC
		Sync web or e-mail documents between hosts in clonepanel system.
		Both accounts must already be set up using setup_account
	
		Command line options: 
		-h		Print these instructions and exit
		-t WEB|MAIL	Type of documents to sync (web or e-mail)
		-u user		CPanel username of the account
		-c mysql40	(optional) Use this compatibility mode for mysql dump
		

EODOC
	exit $DOC_REQUEST
}

function setup_mirror_dirs {

	case "$stype" in
		WEB)
# define variables for web sync (we can't combine this with the earlier
# case because MIRROR_DIRS may be host dependent)
			MIRROR_DIRS[0]=$MIRROR_DIR_WEB
			MIRROR_SS[0]=$MIRROR_SS_WEB
			EXCLUDES=$USER_DIR/$EXCLUDESFILE_WEB
			NOBACKUPS=$USER_DIR/$NOBACKUPFILE_WEB
			if [ "`$LS $USER_DIR/$DATABASE_DIRNAME`" ]; then
				MIRROR_DIRS[1]=$DATABASE_STORENAME
				MIRROR_SS[1]=$DATABASE_STORENAME
			fi
			;;
		MAIL)
# define variables for mail sync			
			MIRROR_DIRS[0]=$MIRROR_DIR_MAIL
			MIRROR_SS[0]=$MIRROR_SS_MAIL
			EXCLUDES=$USER_DIR/$EXCLUDESFILE_MAIL
			NOBACKUPS=$USER_DIR/$NOBACKUPFILE_MAIL
			;;
	esac
	mirror_dir_count=${#MIRROR_DIRS[@]}
}
function check_remote_error {
# Called with arguments status ($?) and error_code, check for errors in remote script
	status=$1
	ecode=$2
#	if [ -f "$TEMP_SYNC_RES_FILE" ]; then
# Display result for user
#		$CAT $TEMP_SYNC_RES_FILE
# No longer required - now displayed real-time
#	fi
	checkerror $status $ecode
# This should stop the sync if an error's found, but...

# Unfortunately CPanel jailshell doesn't return status code correctly so
# we should by now have trapped the output to check for errors...
	if [ -f "$TEMP_SYNC_RES_FILE" ]; then
		if [ "`$GREP "$errorcode" $TEMP_SYNC_RES_FILE`" ]; then
# Search for the error colour code in the output file
			cecho -c $error "Remote error $ecode - exiting"
			exit $ecode
		fi
# Finally, remove temp file
#		$RM $TEMP_SYNC_RES_FILE
	else
# If we didn't trap it, that's an error anyway
		cecho -c $error "Error $ecode - No sync result file found"
		exit $ecode
	fi
}

# Initialise own input variables:
stype='WEB'
user=''
compat=''

# Initialise other variables:
status=0

# Start real script


# Check for help request and input variables in the command line options:
while getopts ":ht:u:c:" opt
do
	case $opt in
		h)	showdocs
			;;
		t)	stype=$OPTARG
			;;
		u)	user=$OPTARG
			;;
		c)	compat=$OPTARG
			;;
		*)	$ECHO "Unknown option. Use -h for instructions"; exit $E_UNKNOWN_OPT
			;;
	esac
done
shift $(($OPTIND - 1))

OPTIND=1
# reset getopts for next time

if [ ! $user -o ! $stype ]; then
	cecho -c $error "Username and sync type must be specified"
	exit $E_BLANK_INPUTS
fi

# User-specific Include files (only after defining $user):
config -u $user -t $stype

if [ "$compat" ]; then
	compat="-c $compat"
	# mysql dump compatibility mode
fi


cecho -c $ok "$($DATE) - Starting sync"

cecho -c $info "Phase A - Preparing directories"
case "$stype" in
	WEB)
# define variables for web sync			
		pullfrom=WEB_MASTER ;
		pushto=WEB_SLAVE ;
		# identify which server to pull / push (actual server name and ip obtailed from roles and hosts files)
		;;
	MAIL)
# define variables for mail sync			
		pullfrom=MAIL_SLAVE ;
		pushto=MAIL_MASTER ;
		# NB - for mail, we collect occasional messages received on the secondary MX and transfer
		# to the primary server (MAIL_MASTER) - hence reversal of push / pull
		;;
	*)
		cecho -c $error "Unknown type"
		exit 1 ;
		;;
esac

# First check that we have a host to pull data from - if not don't rotate snapshots

# hostname_pull=$($GREP $pullfrom $ROLES_FILE |$CUT -d= -f2) ;
eval hostname_pull=\$$pullfrom
#get nickname of host server to pull from (from roles file)

if [ ! $hostname_pull ]; then
	cecho -c $error "No $pullfrom host found - cannot continue"
	exit $E_NO_PULLFROM_HOST
fi

# Host-specific Include files (only after defining $hostname):
config -H $hostname_pull

ip_pull=$A
if [ ! "$ip_pull" ]; then
	cecho -c $error "$pullfrom host $hostname_pull has no ip - check host config file"
	exit $E_NO_PULLFROM_IP
fi

username_pull=$REMOTEHOST_USERNAME
# username on remote host (usually same as account name)

remote_home_pull=$REMOTEHOST_HOME
#remote directory (normally /home/username)
if [ ! $remote_home_pull ]; then
	cecho -c $error "$pullfrom host $hostname_pull has no remote dir - check remote_dir file"
	exit $E_NO_PULLFROM_DIR
fi

setup_mirror_dirs
# Create the list of directories to backup / mirror
# (may depend on pull host config)

cecho -c $info "Phase B - Rotating snapshots of $MIRROR_DIRS"

# step B1: delete the oldest hourly snapshot, if it exists:
if [ -d $SNAPSHOT_SUBDIR/hourly.$NO_SNAPSHOTS ] ; then
	$RM -rf $SNAPSHOT_SUBDIR/hourly.$NO_SNAPSHOTS ;
fi ;
	
# step B2: shift the middle snapshots(s) back by one, if they exist as far as 1->2

for (( i=$NO_SNAPSHOTS; i > 1; i-- ))
do
	j=$($EXPR $i - 1) ;
	if [ -d $SNAPSHOT_SUBDIR/hourly.$j ] ; then
		$MV $SNAPSHOT_SUBDIR/hourly.$j $SNAPSHOT_SUBDIR/hourly.$i
	fi
done
	
# step B3: make a hard-link-only (except for dirs) copy of the current mirror dir.
# NB - newer alternative method using --link-dest flag on rsync seems to
# involve a lot more traffic on the rsync link...
if [ -d $SNAPSHOT_SUBDIR/hourly.0 ] ; then
	$CP -al $SNAPSHOT_SUBDIR/hourly.0 $SNAPSHOT_SUBDIR/hourly.1 ;
else 
	$MKDIR $SNAPSHOT_SUBDIR/hourly.0
fi;

cecho -c $info "Phase C - rsync 'pull' from the remote $pullfrom host ($hostname_pull)"

remote_ssh_pull="$SSH -p $SSH_PORT -i $AUTH_DIR/$hostname_pull";
# how to authenticate with remote domain (restricted password-less key system)

# Step C1 - Pre-process
$remote_ssh_pull $username_pull@$ip_pull "pre_process -t $stype -d PULL $compat" >$TEMP_SYNC_RES_FILE
check_remote_error $? $E_PRE_PULL_FAILED
$CAT $TEMP_SYNC_RES_FILE

# Step C2 - Sync
$TOUCH $SNAPSHOT_SUBDIR/hourly.0

for (( i = 0 ; i < $mirror_dir_count ; i++ ))
do
	dir=${MIRROR_DIRS[$i]}
	ssdir=${MIRROR_SS[$i]}
	if [ ! -d $SNAPSHOT_SUBDIR/hourly.0/$ssdir ]; then
		$MKDIR $SNAPSHOT_SUBDIR/hourly.0/$ssdir
	fi

	if [ -f "$NOBACKUPS" ]; then
		NOBACKUPS="--exclude-from=$NOBACKUPS"
	else
		NOBACKUPS=''
	fi

	:  >$TEMP_SYNC_RES_FILE
# Clear the sync results file ready for append

	$RSYNC	--bwlimit=$RSYNC_BWLIMIT			\
		-avz --delete					\
		$NOBACKUPS					\
		-e "$remote_ssh_pull" 				\
		$username_pull@$ip_pull:$remote_home_pull/$dir/ $SNAPSHOT_SUBDIR/hourly.0/$ssdir |
# Rsync output is piped to a while loop for display on screen and save to file
# (surely there must be an easier way to do this??)
		while read line
		do
		        $ECHO $line
		        $ECHO $line >>$TEMP_SYNC_RES_FILE
		done

	check_remote_error $? $E_RSYNC_PULL_FAILED

# NB - exclude-from="$EXCLUDES" only on push. Excluded files should be
# backed up but not transferred (eg. config files - system specific)
# This means that special config files on slave are NOT backed up here,
# only backup -H SLAVEHOSTNAME will keep these

# For files you really don't want backed up (eg. large temporary files)
# use web_nobackup instead.

done

# Prepare for push

# hostname_push=$($GREP $pushto $ROLES_FILE |$CUT -d= -f2) ;
eval hostname_push=\$$pushto
#get host nickname (eg. MYHOST) of server to push to (from roles file)
if [ $hostname_push ]; then
# We will have a push host only if using a hot-spare server

	# Host-specific Include files (only after defining $hostname):
	config -H $hostname_push

	ip_push=$A
	if [ ! "$ip_push" ]; then
	        cecho -c $error "$pushto host $hostname_push has no ip - check host cconfig"
	        exit $E_NO_PUSHTO_IP
	fi

	username_push=$REMOTEHOST_USERNAME
	remote_home_push=$REMOTEHOST_HOME
	#remote directory (normally /home/username)
	if [ ! $remote_home_push ]; then
		cecho -c $error "$pushto host $hostname_push has no remote dir - check host config file"
		exit $E_NO_PUSHTO_DIR
	fi

	setup_mirror_dirs
	# Recreate the list of directories to  mirror
	# (may depend on push host config)

	cecho -c $info "Phase D - rsync 'push' to the remote $pushto host ($hostname_push)"

	remote_ssh_push="$SSH -p $SSH_PORT -i $AUTH_DIR/$hostname_push";

# Step D1 - Pre-process
	$remote_ssh_push $username_push@$ip_push "pre_process -t $stype -d PUSH $compat" >$TEMP_SYNC_RES_FILE
	check_remote_error $? $E_PRE_PUSH_FAILED
	$CAT $TEMP_SYNC_RES_FILE

# Step D2 - Sync
	for (( i = 0 ; i < $mirror_dir_count ; i++ ))
	do
		dir=${MIRROR_DIRS[$i]}
		ssdir=${MIRROR_SS[$i]}

	        :  >$TEMP_SYNC_RES_FILE
# Clear the sync results file ready for append

		$RSYNC	--bwlimit=$RSYNC_BWLIMIT		\
			-avz --delete				\
			--exclude-from="$EXCLUDES"		\
			-e "$remote_ssh_push"	 		\
			$SNAPSHOT_SUBDIR/hourly.0/$ssdir/ $username_push@$ip_push:$remote_home_push/$dir |
	                while read line
	                do
	                        $ECHO $line
	                        $ECHO $line >>$TEMP_SYNC_RES_FILE
	                done

		check_remote_error $? $E_RSYNC_PUSH_FAILED

	done


else
	cecho -c $warning "Warning - No $pushto host found - mirror backup only, no sync"
fi

cecho -c $info "Phase E - Post-process on both remote hosts"

# Step C3 - Post-process pull
$remote_ssh_pull $username_pull@$ip_pull "post_process -t $stype -d PULL $compat" >$TEMP_SYNC_RES_FILE
check_remote_error $? $E_POST_PULL_FAILED
$CAT $TEMP_SYNC_RES_FILE

# Step D3 - Post-process push
if [ $hostname_push ]; then
	$remote_ssh_push $username_push@$ip_push "post_process -t $stype -d PUSH $compat" >$TEMP_SYNC_RES_FILE
	check_remote_error $? $E_POST_PUSH_FAILED
	$CAT $TEMP_SYNC_RES_FILE
fi

cecho -c $ok "$($DATE) sync completed successfully"
