#!/bin/bash

# Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
# based on vserver-copy by Mark Lawrence <nomad@null.net>
#  
# 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, 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.

# Copy/Sync a virtual host from one machine to another
#
# History:
#
# 2003-04-04: Version 0.4 - Mark lawrence
# - Set "ONBOOT=no" in the destination .conf file when --startstop
#   is not used, in case the destination roothost reboots. We don't
#   want two copies of a vserver running at the same time.
#
# 2003-03-04: Version 0.3 - Mark lawrence
# - Changed all checks for [ "" != "$var" ] into [ -n|-z "$var" ]. "" doesn't
#   seem to work for bash on the Sparc architecture.
# - Changed $ssh variable into $shcmd.
#
# 2003-02-23: Version 0.2 - Mark Lawrence
# - Set ONBOOT to "no" in the original .conf file when the "-s" flag is 
#   used so that if/when you reboot the source roothost you don't have the
#   same vserver and IP address running on two machines.

: ${UTIL_VSERVER_VARS:=/usr/lib/util-vserver/util-vserver-vars}
test -e "$UTIL_VSERVER_VARS" || {
    echo $"Can not find util-vserver installation (the file '$UTIL_VSERVER_VARS' would be expected); aborting..." >&2
    exit 1
}
. "$UTIL_VSERVER_VARS"

VERSION="0.4"
umask 022
me=${0##*/}


### Helper functions ###

# Save stdin and stdout for later use
exec 3>&1
exec 4>&2

noninteractive () {
	exec &> /dev/null
}

interactive () {
	exec 1>&3
	exec 2>&4
}

info () {
	! $quiet && echo "I: $me: $1" >&3
}

warn () {
	! $quiet && echo "W: $me: $1" >&4
}

error () {
	! $quiet && echo "E: $me: $2" >&4
	exit $1
}


### Usage/Info functions ###

usage () {
    cat <<EOF 1>&2
Usage:	$me [-hVvqidrRs] vserver newname
	$me [-hVvqidrRs] vserver host:[newname]
EOF
}

full_usage () {
	usage
        cat <<EOF

$me uses rsync to make a copy of a vserver. If the destination
name contains a host specification the vserver will be synchronised to
the remote destination over ssh/rsh.

This can be used on a running vserver to make a warm backup. With the -s
flag a vserver can even be operationally moved to different hardware within
seconds.

The -i and -d flags can be used to minimally reconfigure the destination
vserver (rewrites /etc/vservers/newname.conf and $__DEFAULT_VSERVERDIR/newname/etc/hosts)

Options:
	-h, --help		this help
	-V, --version		copyright and version information
	-v, --verbose		show all output
	-q, --quiet		direct all output to /dev/null (no password
				prompt for logins on remote hosts!)
	-d, --domain [string]	new dns domain (must be used with -i)
	-i, --ip [addr]		new IP address (must be used with -d)
	-r, --vsroot		location of "/vserver/" directory
	-R, --rsh		use rsh (instead of default ssh) for
				network transport
	-s, --stopstart		stop the local vserver before copying and start
				it on the destination host afterwards

EOF
}

full_version () {
    cat <<EOF
$me version $VERSION
Copyright (c) 2002 Mark Lawrence   <nomad@null.net>

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.

EOF
}


### Default values and Command line options ###

stopstart=(false)
verbose=(false)
quiet=(false)
shcmd="ssh"
rsflag="-e"
rsh=(false)
colon=":"
domain=""
ip=""
vsroot=$__DEFAULT_VSERVERDIR

if [ $# -eq 0 ]; then  # Script invoked with no command-line args?
	usage
	exit 1
fi  

temp=$(getopt -o hVvqd:i:rRs --long help,version,verbose,quiet,domain:,ip:,vsroot,rsh,stopstart, -n $me -- "$@")

if [ $? -ne 0 ]; then
	echo "	(See -h for help)"
        exit 1
fi

# Note the quotes around `$temp': they are essential!
eval set -- "$temp"

while true; do
        case "$1" in
		-h|--help)	full_usage
				exit 1
				;;
		-V|--version)	full_version
				exit 1
				;;
		-v|--verbose)	verbose=(true)
				shift
				;;
		-q|--quiet)	quiet=(true)
				shift
				;;
		-d|--domain)	domain="$2"
				shift 2
				;;
		-i|--ip)	ip="$2"
				shift 2
				;;
		-r|--vsroot)	vsroot="$2"
				shift 2
				;;
		-R|--rsh)	rsh=(true)
				shift
				;;
		-s|--stopstart)	stopstart=(true)
				shift
				;;
                --)             shift
				break
				;;
		*)		echo "Internal error!"
				exit 1
				;;
	esac
done

if [ $# -ne 2 ]; then
	usage
	exit 1
fi


### ###

# By default we are reasonably quiet (ouput only via info, warn & error)
if $verbose; then
	interactive
else
	noninteractive
fi

now=$(date)
info "called on $(hostname) at $now"


vserver=$1
vconf=/etc/vservers/$vserver.conf
vroot=$vsroot/$vserver

if $rsh; then
	shcmd="rsh"
fi

if (echo $2 | grep '^[a-z][a-z0-9]\+$'); then
	dhost=""
	newname=$2
	shcmd=""
	rsflag=""
	colon=""
	if $rsh; then
		warn "rsh is set but not used for a local copy"
	fi
elif (echo $2 | grep '^[a-z].*[a-z0-9]:$'); then
	dhost=${2/:/}
	newname=$vserver
elif (echo $2 | grep '^[a-z].*[a-z0-9]:[a-z].*[a-z0-9]$'); then
	dhost=${2/:*/}
	newname=${2/*:/}
else
	error 1 "Second argument must be of the form \"[host:]name\" or \"host:\""
fi

target=$vsroot/$newname
targetconf=/etc/vservers/$newname.conf


### Perform some sanity checks ###

if [ ! -d $vroot ]; then
	error 1 "Directory \"$vroot\" does not exist"
fi

if [ ! -e $vconf ]; then
	error 1 "Vserver file \"$vconf\" does not exist"
fi

if [ -z "$dhost" ] && [ "$vserver" == "$newname" ]; then
	error 1 "Source and destination names cannot be the same on the localhost"
fi

if [ -n "$dhost" ] && ! (host $dhost | grep 'has address'); then
	warn "$dhost does not resolve into an IP address"
fi

if [ \( -n "$ip" -a -z "$domain" \) -o \
     \( -z "$ip" -a -n "$domain" \) ]
then
	error 1 "Both IP address and domain must be specified together"
fi

if [ -n "$ip" ] && \
! (echo $ip | grep '^[0-9]\{1,3\}\(\.[0-9]\{1,3\}\)\{3\}$' ); then
	error 1 "\"$ip\" is not a valid IP address"
fi

# This works both locally and remote
if ($shcmd $dhost $__SBINDIR/vserver $newname running | grep 'is running'); then
	warn "destination vserver \"$newname\" is running" 
	error 1 "Cannot copy over a running vserver"
fi


### Do the copy ###

info "Attempting to copy $vserver to $dhost$colon$newname"

if $stopstart; then
	info "Stopping virtual server \"$vserver\" on localhost"
	$__SBINDIR/vserver $vserver stop
fi

info "Syncing directories"
# trailing slashes very important in the rsync!
if ! rsync -Havxz --numeric-ids $rsflag $shcmd $vroot/ $dhost$colon$target/; then
	error 1 "rsync failed"
fi

if [ -n "$ip" -a -n "$domain" ]; then
	# Insert the new IPROOT/S_HOSTNAME values into the config file
	info "Modifying $targetconf"
	tmpf=$(tempfile)
	if (sed -e "s/^S_HOSTNAME=.*/S_HOSTNAME=\"$newname\"/" \
		-e "s/^IPROOT=.*/IPROOT=\"$ip\"/" $vconf > $tmpf)
	then
		if ! rsync -v $rsflag $shcmd $tmpf $dhost$colon$targetconf; then
			error $? "vserver config file copy/change failed"
		fi

	else
		warn "Unable to reconfigure virtual server config file"
	fi

	# create a new /etc/hostname
	info "Creating hostname file"
	echo $newname > $tmpf
	if ! rsync -v $rsflag $shcmd $tmpf $dhost$colon$target/etc/hostname; then
		error 1 "vserver /etc/hostname copy failed"
	fi

	info "Creating /etc/hosts"
	cat << EOF > $tmpf
# /etc/hosts (automatically generated by $me)

127.0.0.1       localhost
$ip	$newname.$domain	$newname

# The following lines are desirable for IPv6 capable hosts

::1     ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts
EOF

	# copy /etc/hosts
	if ! rsync -v $rsflag $shcmd $tmpf $dhost$colon$target/etc/hosts; then
		error 1 "vserver /etc/hosts copy failed"
	fi
	rm -f $tmpf

else
	if ! $stopstart; then
		# Make sure that this vserver doesn't start on the 
		# destination host if it reboots
		tmpf=$(tempfile)
		sed -e 's/^ONBOOT=.*/ONBOOT=no/' $vconf > $tmpf
		vconf=$tmpf
	fi

	# copy newname.conf unchanged
	info "Copying $targetconf"
	if ! rsync -v $rsflag $shcmd $vconf $dhost$colon$targetconf; then
		error 1 "vserver config file copy/change failed"
	fi

	rm -f $tmpf
fi


if $stopstart; then
	info "Starting virtual server \"$vserver\" on $dhost"
	$shcmd $dhost $__SBINDIR/vserver $vserver start
	if ($shcmd $dhost $__SBINDIR/vserver $vserver running | \
	grep 'not running'); then
		error 1 "Virtual server \"$vserver\" failed to start on $dhost"
	fi

	# Make sure that we don't start the original on next boot
	tmpf=$(tempfile)
	sed -e 's/^ONBOOT=.*/ONBOOT=no/' $vconf > $tmpf
	mv $tmpf $vconf
fi

exit 0
