#!/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 ." 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