xen/xmclone.sh

580 lines
21 KiB
Bash

#!/bin/sh
################################################################################
# xmclone by Bob Brandt #
# based on XenClone by Glen Davis #
# Clone a SLES10 domU #
################################################################################
################################################################################
# Defaults #
# #
VERSION=0.3
XEN_CONFIGS=/etc/xen/vm/
XEN_BASE=/xen/
SOURCE=
DESTINATION=
DUPLICATE=0
HOSTNAME=
IP=
MAC=
################################################################################
################################################################################
# Subroutines used in this script #
# #
# Display the usage information for this script #
usage()
{
echo -e "\nUsage: ${0##*/} [-h|?|--help] [-v|--version] [-c dir] [-b dir] [-d]"
echo -e " [-n hostname] [-i address] [-m address]"
echo -e " SourcedomU NewdomU\n"
echo -e "Clones a domU, and gives a new Host name, MAC address and possibly IP address."
echo -e "Once finished the new domU should boot without any additional configuration."
echo -e "Currently works with single NIC, and basic bridge setup. Tested with cloning"
echo -e "a SLES10 install created from the SLES10 YaST Xen module.\n"
echo -e " -h, -?, --help Display this help message."
echo -e " -v, --version Display the version of this program."
echo -e " -c XEN configuration directory which defaults to:"
echo -e " $XEN_CONFIGS"
echo -e " -b XEN image base directory which defaults to:"
echo -e " $XEN_BASE"
echo -e " -d Only Duplicate, do not modify attributes."
echo -e " -n Hostname to be used, if not specified the NewdomU name"
echo -e " will be used."
echo -e " -i IP address to be used, if not specified the IP address"
echo -e " will not be changed."
echo -e " -m MAC address to be used, if not specified a psuedo-random"
echo -e " address will be used based on the ip address with the"
echo -e " format: 00:16:AA:BB:CC:DD"
echo -e " Where AA,BB,CC,DD are the Hex octals of the IP address."
echo -e "From XENSource Networking WIKI (http://wiki.xensource.com/xenwiki/XenNetworking)"
echo -e "Virtualised network interfaces in domains are given Ethernet MAC addresses. When"
echo -e "choosing MAC addresses to use, ensure you choose a unicast address. That is, one"
echo -e "with the low bit of the first octet set to zero. For example, an address"
echo -e "starting aa: is OK but ab: is not."
echo -e 'It is best to keep to the range of addresses declared to be "locally assigned"'
echo -e "(rather than allocated globally to hardware vendors). These have the second"
echo -e "lowest bit set to one in the first octet. For example, aa: is OK, a8: isn't.\n"
echo -e "Exit status is 0 if OK, 1 if minor problems, 2 if serious trouble.\n"
}
# Display the version information for this script #
version()
{
echo -e "${0##*/} (XEN vm clone utility) $VERSION"
echo -e "This is free software. You may redistribute copies of it under the terms of"
echo -e "the GNU General Public License <http://www.gnu.org/licenses/gpl.html>."
echo -e "There is NO WARRANTY, to the extent permitted by law.\n"
echo -e "Written by Bob Brandt, based on work by Glen Davis."
}
# Find/Replace text within a file #
replace()
{
SearchText=$1
ReplaceText=$2
File=$3
if sed "s/$SearchText/$ReplaceText/g" "$File" > "$File.temporary" && sleep 1
then
if mv -f "$File.temporary" "$File" && sleep 1
then
return 0
fi
fi
return 1
}
################################################################################
################################################################################
# Make sure the user is root #
# #
if [ `id -u` -ne 0 ]; then
echo -e "You must be root to run this script!\n"
exit 1
fi
################################################################################
################################################################################
# Process the parameters #
# #
# Must look for double -- arguments before getopts
if [ "$1" = "--version" ]; then
version
exit 0;
fi
if [ "$1" = "--help" ]; then
usage
exit 0;
fi
while getopts ":hvc:b:dn:i:m:" opt; do
case $opt in
h | \? )
usage
exit 0;;
v )
version
exit 0;;
c ) XEN_CONFIGS=$OPTARG;;
b ) XEN_BASE=$OPTARG;;
d ) DUPLICATE=1;;
n ) HOSTNAME=$OPTARG;;
i ) IP=$OPTARG;;
m ) MAC=$OPTARG;;
esac
done
shift $(($OPTIND-1))
SOURCE=$1
DESTINATION=$2
################################################################################
################################################################################
# Verify the Source and Destination parameters #
# #
# The source and destination should be relative directory names without trailing /'s
# If the source does have a full path, use that path as the XEN_BASE
# Otherwise remove all but the last part of the path for both source and destination
SOURCEDIR=${SOURCE%/*}
SOURCEBASE=${SOURCE##*/}
if [ "$SOURCEDIR" != "$SOURCEBASE" ]; then
XEN_BASE=$SOURCEDIR"/"
SOURCE=$SOURCEBASE
fi
SOURCE=${SOURCE##*/}
DESTINATION=${DESTINATION##*/}
################################################################################
################################################################################
# Verify the XEN Config and Source parameters #
# #
# Directories should have a / after them #
if [ "$XEN_CONFIGS" != "" ]; then
XEN_CONFIGS="${XEN_CONFIGS%/}/"
fi
if [ "$XEN_BASE" != "" ]; then
XEN_BASE="${XEN_BASE%/}/"
fi
# Verify the validity of each agrument ask the user if there is a problem
while [ ! -d "$XEN_CONFIGS" ]; do
echo -e "\nThe $XEN_CONFIGS directory does not exist. Please enter a valid directory."
read -p "XEN Configuration Directory? " XEN_CONFIGS
done
while [ ! -d "$XEN_BASE" ]; do
echo -e "\nThe $XEN_BASE directory does not exist. Please enter a valid directory."
read -p "XEN Image Base Directory? " XEN_BASE
done
################################################################################
################################################################################
# Verify that actual image and configuration file exist #
# #
while [ ! -d "$XEN_BASE$SOURCE" ] || [ ! -f "$XEN_CONFIGS$SOURCE" ]; do
if [ ! -d "$XEN_BASE$SOURCE" ]; then
echo -e "The directory $XEN_BASE$SOURCE is invalid, please select another."
FILES=
tmpFILES=`ls $XEN_BASE`
for FILE in $tmpFILES; do
# If the Entry is a Directory
if [ -d "$XEN_BASE$FILE" ]; then
# And if that Directory is not empty
if [ "`ls $XEN_BASE$FILE`" != "" ]; then
FILES="$FILES $XEN_BASE$FILE"
fi
fi
done
if [ "$FILES" = "" ]; then
echo -e "There are no directories beneath $XEN_BASE"
exit 1
else
echo -e "Directories beneath $XEN_BASE"
select FILE in $FILES; do
if [ "$FILE" ]; then
SOURCE=${FILE##*/}
break
else
echo -e "Invalid Selection."
fi
done
fi
fi
if [ ! -f "$XEN_CONFIGS$SOURCE" ]; then
echo -e "\nThe $XEN_CONFIGS$SOURCE file does not exist. Please select a valid file."
FILES=
tmpFILES=`ls $XEN_CONFIGS`
for FILE in $tmpFILES; do
# If the Entry is a File
if [ -f "$XEN_CONFIGS$FILE" ]; then
FILES="$FILES $XEN_CONFIGS$FILE"
fi
done
if [ "$FILES" = "" ]; then
echo -e "There are no files beneath $XEN_CONFIGS"
exit 1
else
echo -e "Files beneath $XEN_CONFIGS"
select FILE in $FILES; do
if [ "$FILE" ]; then
SOURCE=${FILE##*/}
break
else
echo -e "Invalid Selection."
fi
done
fi
fi
done
################################################################################
################################################################################
# That the destination location does not already have a image or config file #
# #
while [ "$DESTINATION" == "" ]; do
echo -e "\nYou have not specified a Destination."
read -p "New Destination? " DESTINATION
done
while [ -d "$XEN_BASE$DESTINATION" ] || [ -f "$XEN_CONFIGS$DESTINATION" ]; do
if [ -d "$XEN_BASE$DESTINATION" ]; then
echo -e "The image location $XEN_BASE$DESTINATION already exists!"
read -p "Please select a new Destination? " DESTINATION
fi
if [ -f "$XEN_CONFIGS$DESTINATION" ]; then
echo -e "The configuration file $XEN_CONFIGS$DESTINATION already exists!"
read -p "Please select a new Destination? " DESTINATION
fi
done
################################################################################
################################################################################
# Verify the network parameters (if Duplicate Only was not selected) #
# #
if [ "$DUPLICATE" == "0" ]; then
if [ "$HOSTNAME" == "" ]; then
echo -e "\nYou have not entered a Host Name. If you wish to, enter one now."
read -p "New Host Name? (Default: $DESTINATION) " HOSTNAME
fi
if [ "$HOSTNAME" == "" ]; then
HOSTNAME=$DESTINATION
fi
if [ "$IP" == "" ]; then
echo -e "\nYou have not specified an IP Address. If you wish to change the IP address, enter one now."
read -p "New IP Address? " IP
fi
while [ "$IP" != "" ] && [ "${IP/*.*.*.*/ok}" != "ok" ]; do
echo -e "\nThe IP Address you specified is invalid. If you wish, enter a new one now."
read -p "New IP Address? " IP
if [ "$IP" == "" ]; then
break
fi
done
if [ "$MAC" == "" ]; then
newMAC=""
newMACtext="(format 01:23:45:67:89:AB)"
# If the IP Address is specified and the MAC isn't, generate one.
if [ "$IP" != "" ]; then
octal1=${IP%%.*}
IP=${IP#*.}
octal2=${IP%%.*}
IP=${IP#*.}
octal3=${IP%%.*}
octal4=${IP#*.}
IP="$octal1.$octal2.$octal3.$octal4"
octal1="00"`echo $octal1 16 o p | dc`
octal2="00"`echo $octal2 16 o p | dc`
octal3="00"`echo $octal3 16 o p | dc`
octal4="00"`echo $octal4 16 o p | dc`
newMAC="00:16:"${octal1:(-2)}":"${octal2:(-2)}":"${octal3:(-2)}":"${octal4:(-2)}
newMACtext="(default $newMAC)"
fi
echo -e "\nYou have not specified a MAC Address. If you wish to change the MAC address, enter one now."
read -p "New MAC Address? $newMACtext " MAC
if [ "$MAC" == "" ]; then
MAC=$newMAC
fi
fi
while [ "$MAC" != "" ] && [ "${MAC/[0123456789abcdefABCDEF][0123456789abcdefABCDEF]:[0123456789abcdefABCDEF][0123456789abcdefABCDEF]:[0123456789abcdefABCDEF][0123456789abcdefABCDEF]:[0123456789abcdefABCDEF][0123456789abcdefABCDEF]:[0123456789abcdefABCDEF][0123456789abcdefABCDEF]:[0123456789abcdefABCDEF][0123456789abcdefABCDEF]/ok}" != "ok" ]; do
echo -e "\nThe MAC Address you specified is invalid. If you wish, enter a new one now."
read -p "New MAC Address? (format 01:23:45:67:89:AB) " MAC
if [ "$MAC" == "" ]; then
break
fi
done
else
HOSTNAME=
IP=
MAC=
fi
################################################################################
################################################################################
# Make sure that the source VM is not running #
# #
xmid=`xm list | grep $SOURCE | cut -c34-36`
if [ "$xmid" != "" ]; then
echo -e "domU $SOURCE is currently running on Xen, please shutdown before cloning."
echo -e "The command \"xm shutdown $xmid -w\" will shutdown the domU"
exit 1
fi
################################################################################
################################################################################
# Copy the XEN Config file #
# #
SOURCECONFIG="$XEN_CONFIGS$SOURCE"
DESTCONFIG="$XEN_CONFIGS$DESTINATION"
echo -e "Copying Configuration files"
if ! cp -fv "$SOURCECONFIG" "$DESTCONFIG"
then
echo -e "The Config file $SOURCECONFIG was unable to be copied to $DESTCONFIG"
exit 1
fi
################################################################################
################################################################################
# Edit newly copied configuration file #
# #
echo -e "Editing config file ($DESTCONFIG), correcting the new domU Name."
if ! replace $SOURCE $DESTINATION $DESTCONFIG
then
echo -e "Unable to change the domU name in $DESTCONFIG from $SOURCE to $DESTINATION"
exit 1
fi
if [ "$DUPLICATE" == "0" ] && [ "$MAC" != "" ]; then
# Get the vif line in the config file
oldMAC=`grep "vif = " $DESTCONFIG`
# extract everything between the square brackets
oldMAC=${oldMAC#*[}
oldMAC=${oldMAC%*]}
# using the single quotes as delimiters, get the second field (this script can only deal with one adapter!)
oldMAC=`echo "$oldMAC" | cut -f2 -d\'`
# remove the mac= from the beginning
oldMAC=${oldMAC#mac=*}
if ! replace $oldMAC $MAC $DESTCONFIG
then
echo -e "Unable to change the MAC address in $DESTCONFIG from ($oldMAC) to ($MAC)"
exit 1
fi
fi
################################################################################
################################################################################
# Create and Copy image directory #
# #
SOURCEXEN="$XEN_BASE$SOURCE/"
DESTXEN="$XEN_BASE$DESTINATION/"
echo -e "Creating the new image directory $DESTXEN"
if ! mkdir -pv --mode=775 "$DESTXEN"
then
echo -e "Unable to create the directory $DESTXEN"
exit 1
fi
echo -e "Copying complete image. (This may take a few minutes!)"
if ! cp -fv --sparse=never $SOURCEXEN* $DESTXEN
then
echo -e "Unable to copy the images from $SOURCEXEN to $DESTXEN"
exit 1
fi
################################################################################
# The rest of the document only applies if we are change the values within the image
if [ "$DUPLICATE" == "0" ] && [ "$MAC" != "" ]; then
############################################################################
# Mount the newly copied image file #
# #
# Find a temporary directory name
tmpdir="/mnt/xentmp"
declare -i a=0
while [ -d "$tmpdir$a" ]; do
a=a+1
done
tmpdir="$tmpdir$a"
if ! mkdir -pv "$tmpdir"
then
echo -e "Unable to create $tmpdir Directory."
exit 1
fi
# Get the vif line in the config file
DISKIMAGE=`grep "disk = " $DESTCONFIG`
# extract everything between the square brackets
DISKIMAGE=${DISKIMAGE#*[}
DISKIMAGE=${DISKIMAGE%*]}
# extract the first entry that is a file
DISKIMAGE=${DISKIMAGE#*file:*}
# remove the end of the entry
DISKIMAGE=${DISKIMAGE%,w*}
# using the comma as delimiter, get the second field (this script assumes that the first file entry is the boot disk!)
DISKIMAGE=`echo "$DISKIMAGE" | cut -f2 -d\,`
# Get the vif line in the config file
PARTITION=`grep "bootentry = " $DESTCONFIG`
# using the single quotes as delimiters, extract the data
PARTITION=`echo "$PARTITION" | cut -f2 -d\'`
# using the comma as delimiter, extra the boot partition information
PARTITION=`echo "$PARTITION" | cut -f1 -d\,`
PARTITION=${PARTITION%:*}
PARTITION=${PARTITION#$DISKIMAGE*}
if ! lomount -diskimage "$DESTXEN$DISKIMAGE" -partition $PARTITION "$tmpdir"
then
echo -e "Unable to mount newly copied image $DESTXEN$DISKIMAGE at $tmpdir"
exit 1
fi
############################################################################
############################################################################
# Change the Network Configuration in the mounted image file #
# #
sleep 1
pushd "$tmpdir"
if [ "$MAC" != "" ]; then
echo -e "Changing the Network configuration in the newly copied image."
cd "$tmpdir/etc/sysconfig/network/"
sleep 1
# Make a backup of the old eth config files in ./othether directory
if mkdir -pv ./oldether/
then
cp -fv ifcfg-eth* ./oldether/
fi
# Find the ifcfg-ethMACADDRESS file in the newly copied image
ETH0=`ls | grep ifcfg-eth | cut -f1`
if [ "$ETH0" = "" ]; then
echo -e "Unable to find ethernet file in image file"
exit 1
fi
# Rename the file to ifcfg-eth0 (if it has not already been done.
# Then delete all the other config files
if [ "$ETH0" != "ifcfg-eth0" ]; then
if mv -f "$ETH0" ifcfg-eth0
then
rm -fv ifcfg-eth-id*
fi
fi
# Make sure that the ifcfg-eth0 file exists, since everything depends on it!
if [ ! -f "ifcfg-eth0" ]; then
echo -e "Unable to create /etc/sysconfig/network/ifcfg-eth0 file!"
exit 1
fi
# The 30-net_persistent_names.rules file controls which interface to use.
# Be removing the SUBSYSTEM line, we force the system to recreate it.
cd "$tmpdir/etc/udev/rules.d/"
cp -fv 30-net_persistent_names.rules 30-net_persistent_names.rules.old
grep -v SUBSYSTEM 30-net_persistent_names.rules > 30-net_persistent_names.rules.tmp
if ! mv -f 30-net_persistent_names.rules.tmp 30-net_persistent_names.rules
then
echo -e "Unable to modify the /etc/udev/rules.d/30-net_persistent_names.rules file."
exit 1
fi
fi
############################################################################
############################################################################
# Change the IP Address in the mounted image file #
# #
if [ "$IP" != "" ]; then
echo -e "Modify the IP Address of the new domU."
cd "$tmpdir/etc/sysconfig/network/"
sleep 1
# Make sure that the ifcfg-eth0 file exists, since everything depends on it!
if [ ! -f "ifcfg-eth0" ]; then
echo -e "Unable to find /etc/sysconfig/network/ifcfg-eth0 file!"
exit 1
fi
# Make a copy every line of the ifcfg-eth0 file except the IPADDR line.
grep -v IPADDR ifcfg-eth0 > ifcfg-eth0.xentmp
sleep 1
# Then add a new IPADDR line with the new IP address
echo 'IPADDR="'$IP'"' >> ifcfg-eth0.xentmp
sleep 1
if ! mv ifcfg-eth0.xentmp ifcfg-eth0
then
echo -e "unable to modify the IP address in /etc/sysconfig/network/ifcfg-eth0"
exit 1
fi
fi
############################################################################
############################################################################
# Change the HOSTNAME and hosts files in the mounted image file #
# #
if [ "$HOSTNAME" != "" ]; then
echo -e "Changing HOSTNAME file to $HOSTNAME."
cd "$tmpdir/etc"
# using the period as a delimter, select the first column for the hostname
oldHOSTNAME=`cat "$tmpdir/etc/HOSTNAME" | cut -f1 -d\.`
sleep 1
if ! replace $oldHOSTNAME $HOSTNAME HOSTNAME
then
echo -e "Unable to change the HOSTNAME from $oldHOSTNAME to $HOSTNAME"
exit 1
fi
FQDN=`cat HOSTNAME`
echo -e "Changing hosts file."
cp -fv hosts hosts.old
if grep -v $oldHOSTNAME hosts > hosts.xentmp
then
echo "$IP\t$FQDN\t$HOSTNAME" >> hosts.xentmp
fi
if [ ! -f hosts.xentmp ]; then
echo -e "Unable to change the hosts file"
exit 1
fi
if ! mv -f hosts.xentmp hosts
then
echo -e "Unable to change the hosts file"
exit 1
fi
fi
############################################################################
############################################################################
# Umount image file and remove temporary directory #
# #
popd > /dev/null
sleep 1
umount "$tmpdir"
sleep 1
rm -r "$tmpdir"
############################################################################
fi
echo -e "Clone is complete. domU $DESTCONFIG is ready to start!"
exit 0