vm-create : create kvm virtual machines

#! /bin/bash
#: Title       : vm-create
#: Date Created: Sat Jan 29 01:54:07 PST 2011 
#: Last Edit   : Wed Feb  9 13:24:14 PST 2011
#: Author      : please_try_again
#: Version     : 1.0
#: Description : install OS from the Internet or local iso in a kvm vm
# ~~~~
prg=$(basename $0)
which virt-install &> /dev/null || Error "virt-install not installed"
 "x$(ps ax | grep libvirtd | awk '!/grep/ { print $1 }')" == "x" ] && Error "libvirtd is not running" 
VIRTINST_VER=$(virt-install --version | sed 's|^0||;s|\.||g')
LIVIRT_VER=$(libvirtd --version | sed 's|^0-9]*\([0-9]\)|\1|;s|\.||g;s|^0||')

# ~~~~
# DEFINE THE VARIABLES IN THIS SECTIONS TO MATCH YOUR SYSTEM

IMGPATH=/misc/vm 	# where to put the vm images. 
ISOPATH=/misc/iso 	# where to save iso images
IMPPATH=/import 	# where to mount storage pools
POOLNAME=install	# pollname for remote install
POOLPATH=/var/lib/libvirt/images/install

# your gateway (unless set in the environment variable GATEWAY) 
GW=192.168.101.1 		

# YOU MIGHT EDIT THE DEFAULTS BELOW

DEF_HYPERVISOR=qemu 	# qemu or xen
#DEF_HOST=				# default to localhost
DEF_CONNECT=${VIRSH_DEFAULT_CONNECT_URI:-${DEF_HYPERVISOR}://$DEF_HOST/system}
DEF_RAM=512
DEF_CPUS=1
DEF_ARCH=i386
DEF_KERN=generic26
DEF_VIDEO=vga	 		# cirrus, vga or vmvga
DEF_AUDIO=default		# AC97 if the hypervisor supports it, otherwise it will be ES1370.
DEF_VMSIZE=10			# default size of virtual machines (10GB)

DISPLAYOPTS=" --vnc --vnclisten=0.0.0.0"
 $VIRTINST_VER -gt 5003 ] && BOOTOPTS=" --boot=cdrom,hd,network"

# network defaults
DEF_BRIDGE=br0
DEF_NIC=eth0
DEF_NAT=virbr0
DEF_NETWORK=nat	        # nat or bridge
DEF_NETMASK=0xffffff00	# should be fine for a class C network
CREATEBRIDGE=1			# create bridge if needed
STARTVIRBR=1			# start virtual network (NAT) if needed

# You might change distros default versions

fedora_ver=14
opensuse_ver=11.3
opensuseM6_ver=11.4 ; opensuseM6_rel=1034
ubuntu_ver=maverick ; ubuntu_rel=10.10
debian_ver=lenny ; debian_rel=508
mandriva_ver=2010.2
archlinux_ver=2010.05
centos_ver=5.5
freebsd_ver="8.2" ; freebsd_rel="-RC3"
openbsd_ver="4.8"
netbsd_ver="5.1"
dflybsd_ver="2.8.2" ; dflybsd_rel=REL

# You might add/change ditros urls.

debian_url="ftp://ftp.osuosl.org/debian/dists/VER/main/installer-ARCH/"
fedora_url="ftp://ftp.osuosl.org/pub/fedora/linux/releases/VER/Fedora/ARCH/os/"
mandriva_url="ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/VER/i586/"
ubuntu_url="ftp://ftp.osuosl.org/pub/ubuntu/dists/VER/main/installer-ARCH/"
archlinux_iso="ftp://ftp.osuosl.org/pub/archlinux/iso/VER/archlinux-VER-netinstall-ARCH.iso"
centos_iso="http://centos.arcticnetwork.ca/VER/isos/ARCH/CentOS-VER-ARCH-netinstall.iso"
debian_iso="ftp://debian.osuosl.org/debian-cd/current/ARCH/iso-cd/debian-REL-ARCH-netinst.iso"
fedora_iso="ftp://ftp.osuosl.org/pub/fedora/linux/releases/VER/Fedora/ARCH/iso/Fedora-VER-ARCH-netinst.iso"
mandriva_iso="ftp://ftp.uwsg.indiana.edu/linux/mandrake/official/VER/ARCH/install/images/boot.iso"
opensuse_iso="http://download.opensuse.org/distribution/VER/iso/openSUSE-VER-NET-ARCH.iso"
ubuntu_iso="ftp://ftp.osuosl.org/pub/ubuntu/dists/VER/main/installer-ARCH/current/images/netboot/mini.iso"
freebsd_iso="ftp://ftp.freebsd.org/pub/FreeBSD/releases/ARCH/ISO-IMAGES/VER/FreeBSD-VERREL-ARCH-bootonly.iso"
openbsd_iso="ftp://ftp.openbsd.org/pub/OpenBSD/VER/ARCH/install${openbsd_ver/./}.iso"
netbsd_iso="ftp://ftp.netbsd.org/pub/NetBSD/iso/VER/ARCHcd-VER.iso"
dflybsd_iso="ftp://ftp.dragonflybsd.org/iso-images/dfly-ARCH-2.8.2_REL.iso.bz2"
opensuseM6_iso="http://download.opensuse.org/distribution/11.4-Milestone6/iso/openSUSE-NET-BuildREL-ARCH.iso"
#netbsd_iso="ftp://ftp.netbsd.org/pub/NetBSD/NetBSD-VER/ARCH/installation/cdrom/boot.iso"

# ~~~
# You should not modify the script blelow this point.

# recognized OS types to this day.
OSTYPES="debianetch debianlenny debiansqueeze \
fedora5 fedora6 fedora7 fedora8 fedora9 fedora10 fedora11 fedora12 fedora13 \
generic24 generic26 \
virtio26 \
mandriva2009 mandriva2010 \
mes5 mes5.1 \
rhel2.1 rhel3 rhel4 rhel5 rhel5.4 rhel6 \
sles10 sles11 \
ubuntuhardy ubuntuintrepid ubuntujaunty ubuntukarmic \
generic \
msdos \
netware4 netware5 netware6 \
opensolaris solaris10 solaris9 \
freebsd6 freebsd7 \
openbsd4 \
vista win2k win2k3 win2k8 win7 winxp winxp64"

 $VIRTINST_VER -lt 5003 ] && Error "virt-install 0.500.3 or newer required"
 $VIRTINST_VER -ge 5004 ] && OSTYPES="$OSTYPES fedora14 ubuntulucid freebsd8"
 $VIRTINST_VER -ge 5005 ] && OSTYPES="$OSTYPES ubuntumaverick"
 $LIVIRT_VER -ge 87 ] && Y="-- "

# valid xen and qemu MAC addresses
qemu_mac="52:54:00:"
xen_mac="00:16:3e:"

# ~~~
# use the popup library if found
which popup &>/dev/null && source $(which popup)

# use popup functions if available
function Error { declare -F | grep -q error || exec echo "Error: $*" ; error "$*" ;}
function Warn  { declare -F | grep -q warn  && warn  "$*" || echo "Warning: $*" ;}

# ~~~

function chkarg {  "${1:0:1}" == "-" ] && Error "invalid argument: $1" ;}
function chkopt {  "x$1" == "x" ] && Error "you should provide a vm name" ;}

function syntax {
if  "$1" == "-d" ] ; then
	# show defaults
	grep '^DEF_' $0 | sed '/\[/d;/^[	]/d;s/#.*//;/=\$.*/d;s/"//g' | awk -F "=" '{ sub(/^ /,"", $2) ; printf "%-20s	%s
", $1, $2}'
else
	cat << EOFSYNTAX
usage:
   $prg vm name

options:
	-c --connect <host>    : remote host where to create the vm
	-r --ram <ram in MB>   : amount of ram (default 512MB)
	-a --arch <i386|amd64> : architecture (i386 or amd64)
	-o --os <os variant>   : os variant (example: fedora14, mandriva2010)
	-u --url <os>          : os (fedora, debian, ubuntu, opensuse, mandriva) 
	-i --iso <img.iso>     : iso image 
	-p --path <directory>  : where to store vm images
	-s --size <size>       : size of virtual machine in GB
	-V --virtio            : same as -o virtio26
	-v --video             : video device: cirrus, vga* or vmvga (for kvm)
	-R --release           : OS version to install (other than default)
	-S --sound             : enable sound device (ac97)
	-l --sdl               : use sdl display rather than vnc
	-L --live              : boot live CD
	-b --bridge <bridge>   : use network bridge (otherwise use DEF_NETWORK if defined or 'nat')
	-e --eth <nic>         : network interface to use for bridge (otherwise use DEF_NIC if defined or eth0)
	-x --xen               : use Xen hypervisor rather than kvm	
	-t --try               : just print but not execute the command
	-h --help              : show this help
	-d --defaults          : show defaults

recognized OS variants    :
`echo $OSTYPES | fmt`

available guest systems   :
$(awk -F "=" '/^[a-zA-Z0-9]*_url=|^[a-zA-Z0-9]*_iso=/ { sub(/_.*/,"",$1); print $1 }' $0 | sort -u | tr "
" " ")
EOFSYNTAX
fi
exit
}

function createBridge {
	 "x$gw" == "x" ] && Error "no gateway defined"
	ping -q -c 1 -W 2 $gw > /dev/null || Error "gateway $gw is unreachable"
	echo  " - creating bridge $1"
	brctl addbr $1 
	ifconfig $2 0.0.0.0 promisc 
	brctl addif $1 $2
	ifconfig $1 inet $3 netmask $DEF_NETMASK
	brctl stp $br on
	route del default 2>/dev/null
	route add default gw $gw 2>/dev/null
}

function importPool {
	virsh -c $hyp pool-dumpxml $1 2>/dev/null | grep -q $2 && {
		virsh -c $hyp pool-info $1 | grep -q running || virsh -c $hyp pool-start $1
		virsh -c $hyp pool-refresh $1
		return
	}
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-destroy $1 2>/dev/null
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-undefine $1 2>/dev/null
	$SSH mkdir -m 600 $POOLPATH 2>/dev/null
	virsh -c $hyp pool-create-as $1 netfs $2 $ISOPATH -- -- $Y$POOLPATH
}

function getarch {
	case $arch in
	amd64)
		case $os in
			fedora|mandriva|archlinux)  arch=x86_64 ;;
		esac
	;;
	i386)
		case $os in
			mandriva|opensuse*)  arch=i586 ;;
			archlinux)  arch=i686 ;;
		esac
	;;
	esac
}

# find out if we need to download a netinstall iso image
function geturl {
	getarch
	 "$rflag" ] && url=${1}_iso || url=${1}_url ; url=${!url}
	 "$url" ] || { url=${1}_iso ; url=${!url} ; iflag=1 ; }
	url=$(echo $url | sed "s|VER|$VER|g;s|ARCH|$arch|g;s|REL|$REL|g")	
	 "$url" ] || Error "No url defined for $1"
	if  "$iflag" ] ; then
		 -d $ISOPATH ] && cd $ISOPATH || Error "$ISOPATH doesn't exist"
		isoimg=$(basename $url .bz2)
		 $tflag -eq 0 ] && (  -f ./$isoimg ] || wget -v $url )
		 -f ./$isoimg.bz2 ] && bunzip2 -v $isoimg.bz2
		if  "$rflag" ] ; then
			 $tflag -eq 0 ] && importPool $POOLNAME $HOSTNAME
			url=" --disk vol=$POOLNAME/$isoimg,device=cdrom"
		else
			url=" --cdrom=$ISOPATH/$isoimg"
		fi
		cd - >/dev/null
	else
		url=" --location=$url"
	fi
}

function chkMAC {
	Mac=$(echo ${1:0:9} | tr ":upper:]" ":lower:]")
	 "$Mac" == "$MAC" ] || Error "invalid $HV MAC address: $1"
	 "$(echo ${1:9} | sed 's|^:]*||g')" == "::" ] || Error "invalid $HV MAC address: $1"
	a=$(echo ${1:9} | sed 's|:||g;s|^0-9a-fA-F]*||g')
	 ${#a} -eq 6 ] || Error "invalid $HV MAC address: $1"
}

# ~~~

uflag=0 ; vflag=0 ; bflag=0 ; sflag=0 ; dflag=0 ; tflag=0 ; lflag=0 ; aflag=0 
url=""

# evaluate arguments
args=`getopt -q -u -o c:r:a:o:u:i:p:s:b:e:m:R:v:xSVlLhdt -l connect:,ram:,arch:,os:,url:,iso:,path:,size:,bridge:,eth:,mac:,release:,video:,xen,virtio,sdl,sound,live,help,defaults,try -- "$@"`

set -- $args
for i; do
	case "$i" in
		-c|--connect)  shift; chkarg $1 ; host="$1" ; HYP="+ssh://$host/system" ; shift ;;
		-r|--ram)      shift; chkarg $1 ; ram="$1" ; shift ;;
		-a|--arch)     shift; chkarg $1 ; arch="$1" ; shift ;;
		-o|--os)       shift; chkarg $1 ; ostype="$1" ; shift ;;
		-i|--iso)      shift; chkarg $1 ; iso="$1" ; uflag=$(($uflag | 1)) ; shift ;;
		-u|--url)      shift; chkarg $1 ; os="$1" ; uflag=$(($uflag | 2)) ; shift ;;
		-p|--path)     shift; chkarg $1 ; imgpath="$1" ; shift ;;
		-s|--size)     shift; chkarg $1 ; vmsize="$1" ; shift ;;
		-b|--bridge)   shift; chkarg $1 ; br="$1"  ; bflag=1  ; shift ;;
		-e|--eth)  	   shift; chkarg $1 ; nic="$1" ; shift ;;
		-m|--mac)  	   shift; chkarg $1 ; mac="$1" ; shift ;;
		-R|--release)  shift; chkarg $1 ; VER="$1" ; shift ;;
		-v|--video)    shift; chkarg $1 ; video="$1" ; shift ;;
		-x|--xen)      XEN=xen ; shift ;;
		-V|--virtio)   vflag=1 ; shift ;;
		-S|--sound)    aflag=1 ; shift ;;
		-l|--sdl)  	   sflag=1 ; shift ;;
		-L|--live) 	   lflag=1 ; shift ;;
		-t|--try)      tflag=1 ; shift ;;
		-d|--defaults) syntax -d ;;
		-h|--help)     syntax ;;
		--) shift ;    chkopt $1 ; vm=$1 ; shift ;;
	esac
done

HV=${XEN:-${DEF_HYPERVISOR}}
 "$XEN" ] && hyp=${HV}///system
 "$HYP" ] && hyp=${HV}${HYP}
hyp=${hyp:-$DEF_CONNECT}
host=${host:-$HOSTNAME}
ram=${ram:-$DEF_RAM}
arch=${arch/i[456]/i3} ; arch=${arch/x86_/amd}
arch=${arch:-$DEF_ARCH}
ver=${os}_ver ; ver=${!ver}
VER=${VER:-$ver}
REL=${os}_rel ; REL=${!REL}
MAC=${HV}_mac ; MAC=${!MAC}
 "$host" == "$HOSTNAME" ] || { rflag=1 ; iflag=1 ; SSH="ssh root@$host" ; }

if  $uflag -ge 3 ] ; then
	Error "options --url and --iso are mutually exclusive."
elif  $uflag -ge 2 ] ; then
	geturl $os
elif  $uflag -ge 1 ] ; then
	 "$rflag" ] && Error "remote install from a local iso is unsupported."
	 -f "$iso" ] && iso=" --cdrom=$iso" || Error "iso image $os not found."
elif  $uflag -ge 0 ] ; then
	Error "No OS or iso image specified."
fi

# ~~~
# OS and version
 "$os" ] && OSDESC=" ($os $VER)"
desc="$vm$OSDESC - vm guest on host $host"

# determine os variant from os name if option --os not given  
 "$ostype" ] || ostype=$os$VER
 "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$os$VER
 "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$(echo $os${OSTYPES##*$os} | sed 's| .*||')
 "${OSTYPES%$ostype*}" == "$OSTYPES" -a $vflag -gt 0 ] && ostype=virtio26  
 "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$DEF_KERN  
 "ostype" ] || ostype=generic  

arch=${arch/i3/i6} ; arch=${arch/amd/x86_}
# ~~~
# get size of vm 
vmsize=${vmsize:-$DEF_VMSIZE}

# get destination path,
IMGPATH=${IMGPATH:-/var/lib/libvirt/images}
imgpath=${imgpath:-$IMGPATH}

if  "$rflag" ] ; then
	DISKOPTS=" --disk pool=default,size=$vmsize"
else
	# check if there is enough space to store image locally. 
	hyp=$DEF_CONNECT
	 -d $imgpath ] || Error "Destination directory $imgpath doesn't exist."
	 $(($vmsize + 1)) -gt $(df -Phl $imgpath | awk '/G/ { sub(/G/,"", $4) ; print $4 }') ] && Error "Not enough space available on $imgpath" 
	imgname=${imgpath}/${vm}.img
	if  $lflag -ge 1 ] ; then
		DISKOPTS=" --nodisk"
		 $VIRTINST_VER -gt 5003 ] && BOOTOPTS=" --boot=cdrom"
	else
		DISKOPTS=" --disk path=${imgname},size=$vmsize"
	fi
fi

# ~~~
# networking 
gw=${GATEWAY:-$GW}

# which networking method to use
if  $bflag -eq 1 -o "$DEF_NETWORK" == "bridge" ] ; then
	br=${br:-$DEF_BRIDGE}
	nic=${nic:-$DEF_NIC}
	# create bridge on localhost if needed
	if  "$rflag" ] ; then
		if  $tflag -eq 0 ] ; then
			$SSH brctl show | grep -q "^$br 	]" || Error "bridge $br not present on host $host" 
		fi
	else
		if  "x$CREATEBRIDGE" == "x1" -a $tflag -eq 0 ] ; then
			net=$(ifconfig $nic | awk '/inet addr/ { sub(/addr:/,"",$2) ; print $2 }')
			brctl show | grep -q "^$br 	]" && echo "bridge $br already set" || createBridge $br $nic $net 
		fi
	fi
	NETOPTS=" --network bridge=$br"
else
	# start virtual network (NAT) if needed
	if  "x$STARTVIRBR" == "x1" ] ; then
		virsh -c $hyp net-list | grep -q 'active' || virsh -c $hyp net-start default
		NETOPTS=" --network network=default" 
	fi
fi

case $os in
	openbsd) NETOPTS="$NETOPTS,model=e1000" ;;
	netbsd)  NETOPTS="$NETOPTS,model=ne2k_pci" ; ACPI=" --noacpi" ; ostype=openbsd4 ;;
	dflybsd) NETOPTS="$NETOPTS,model=e1000" ; ostype=freebsd7 ;;
	opensuse) ostype=sles11 ;;
esac

 "$ostype" ] && ostype=" --os-variant=$ostype"

# get MAC address from option -m or /etc/ethers.
 "$mac" ] || mac=`$SSH grep $vm /etc/ethers 2&gt;/dev/null | awk '{print $1}'`
 "$mac" ] && chkMAC $mac
 "$mac" ] && NETOPTS="$NETOPTS,mac=$mac"

# ~~~
# display and graphic adapter
 $sflag -eq 1 ] && DISPLAYOPTS=" --sdl"
 "$video" ] || video=$DEF_VIDEO
 $aflag -ge 1 ] && audio=" --soundhw=$DEF_AUDIO"

# ~~~
if  $tflag -ge 1 ] ; then
	printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${iso}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm

" | sed 's|--|
--|g'
else
	virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${iso}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm
fi

What is vm-create
vm-create is a script which enables the installation of operating systems (directly from the Internet or from iso images saved locally) in kvm or xen virtual machines - although it’s totally untested with xen since I’m using kvm. Installation can be done on a local or remote hypervisor.

Requirements
The script uses virt-install and requires the packages virt-manager, virt-viewer and kvm (which provides qemu-kvm) to be installed. It has been originally written for virt-manager version 0.8.5.49-1 from openSUSE:Virtualization repo: [noparse]http://download.opensuse.org/repositories/Virtualization/openSUSE_11.3[/noparse], which provides virt-install 0.500.4, although I tried to make it compatible with version 0.500.3 shipped with Ubuntu and version 0.500.5 included in virt-manager 0.8.6-3 as on ArchLinux. Under openSUSE, virt-install is included in virt-manager, while in other distros such as Ubuntu, it is a separate package. To connect to the hypervisor from a machine running ArchLinux and perform remote installations from there you need to install netcat-openbsd on the openSUSE server and create the following symlink:

# ln -s nc /usr/bin/nc.openbsd

Installing OS in local vms from the Internet

There are many options but you should be able to create a vm and install an OS using a pretty short command with the default options. So the following command will install a 32bit version of the latest stable debian (lenny) directly from a debian mirror in a 10 GB virtual machine stored in /misc/vm/desiree.img on your local computer - desiree beeing the name of the guest.

# vm-create -u debian desiree

Mainstream Linux distros, as well as FreeBSD, NetBSD and openBSD urls, are already included in the script. You can add others by defining new variables in the form os_url or os_iso (and post the urls in that thread afterwards). The installation of Windows guests is not covered in this tutorial (and will never be).

The following commands will install the specified OS in virtual machines (whose names are of course just examples) with default options:

# vm-create -u archlinux archimede
# vm-create -u fedora felicita
# vm-create -u centos cecilia
# vm-create -u mandriva marcel
# vm-create -u opensuse othello
# vm-create -u opensuseM6 oscar
# vm-create -u ubuntu ubu
# vm-create -u freebsd fredo
# vm-create -u netbsd nestor
# vm-create -u openbsd oprah
# vm-create -u dflybsd daffy

Installing OS guest on a remote host

The installation of a virtual machine on a remote host is accomplished by connecting to the remote hypervisor through ssh. You need a ssh root account on that computer. It is advisable to use ssh keys to avoid being prompted to enter your password for each command - the installation might even fail if you aren’t given the opportunity to input your password.

While installing a guest OS on a remote host, a mini or net iso will be downloaded first to a directory on the local machine, and a pool containing that directory wil be created on the host. Thus in some cases where a direct network install is possible (when an os_url variable is defined in the script), remote install (which uses the os_iso variable) might be different from local install.

The following example installs openSUSE 11.4 Millestone 6 in a guest called *oscar *on a remote host:

# vm-create -c -u opensuseM6 oscar

Installing from iso image

If guest and host are on the same machine, you can install from a locally stored iso image with the option -i (–iso) followed by the path of the iso file. Of course this could also be an nfs mounted filesystem. It is not possible in virt-install to install a guest on host B from an iso image located on host A. It would be do-able in the script but wouldn’t it be simpler to upload the iso on the remote host and install from there?

The following command boots openSUSE 11.4 Millestone 6 live CD from an iso image in the example path into a local vm guest called oprah:

# vm-create -i /data/Linux/openSUSE/11.4/iso/openSUSE-GNOME-LiveCD-Build1034-i686.iso oprah

Booting live CD only

To boot a live CD into a vm without actually creating a virtual disk, add option -L to the example above, giving:

# vm-create -i /data/Linux/openSUSE/11.4/iso/openSUSE-GNOME-LiveCD-Build1034-i686.iso -L oprah

This will create a diskless virtual machine which doesn’t use any disk space on the host.

Display: VNC or SDL

The default display is VNC, which allows remote usage of the virtual machine. If the virtual machine is to be seen on the local computer only, you can install SDL rather than VNC display with the option -l (or --sdl) , as shown in the following example:

# vm-create -u opensuseM6 --sdl oscar

Network: NAT or bridge

The default is to use NAT for network connection. The virtual network (default) will be started if needed on the local or even on a remote hypervisor. You can install directly using bridge with the option -b (or --bridge) followed by the bridge name (usually br0). If the bridge doesn’t exist, the script will attempt to create one on the network interface specified in DEF_NIC (default eth0). You can specify another network interface if needed with the option -e (or --eth).
The following example creates a vm called archimede with a 8GB disk and network bridge br1 on eth1, and installs archlinux:

# vm-create -s 8 -u archlinux -b br1 -e eth1 archimede

A bridge can only be created on the local hypervisor. If if you want to install a virtual machine on a remote host using bridge, you have to create the bridge first on the remote machine -for example by calling the vm-bridge](http://forums.opensuse.org/english/other-forums/development/programming-scripting/453961-vm-bridge-convert-virtual-machines-nat-bridge-bridge-nat.html#post2288556) script under the name nat2bridge or by creating the bridge permanently. If the bridge doesn’t exist, the script will issue an error message and exit.

MAC address

You can specify the MAC address you want to assign to the virtual network interface with the optin -m (–mac). It can be useful for various reasons, such as reinstalling a guest which obtains a fixed IP from a DHCP server. If an entry for this guest already exists in /etc/ethers on the hosts, the MAC address defined in this file will be used. MAC addresses should be in Xen (starting with 00:16:3e) or Qemu (starting with 52:54:00) valid ranges. Otherwise vm-create will display an error message an abort.

OS variant

The os-variant is determined by the OS name and version or sles11 for opensuse, otherwise the value of DEF_KERN, or, if not defined, generic. For Linux, you can enforce the use of the virtio driver with the option -v (–virtio). This only makes sense for distros whose variant is not already supported in virt-install, such as archlinux. If you wish to install archlinux with the virtio driver, you should rebuild the ramdisk with (some of) the virtio modules (virtio itself is built in the i686 kernel). Read this page for more info about how to install archlinux in kvm with the virtio driver: https://wiki.archlinux.org/index.php/KVM#Preparing_an_.28arch.29linux_guest.

Printing the command before executing

You can see how the complete command looks without running it with the -t (or --try) option. For example, without further options, the following command

# vm-create -t -u fedora felicita

produces the following output:

virt-install 
--connect=qemu:///system 
--ram=512 
--arch=i686 
--vcpus=1 
--video=vga 
--description="felicita (fedora 14) - vm guest on host bareil" 
--os-variant=fedora14 
--location=ftp://ftp.osuosl.org/pub/fedora/linux/releases/14/Fedora/i386/os/ 
--boot=cdrom,hd,network 
--disk path=/misc/vm/felicita.img,size=10 
--network network=default
--vnc 
--vnclisten=0.0.0.0 
--name=felicita

It’s a good idea to print the command with the option -t before executing it.

As an another example, the command below proposes to install openSUSE 11.4 M6 with default settings + sound support from an iso image stored locally in the current directory into a vitual machine called oprah:

# vm-create -t -S -i /data/Linux/openSUSE/11.4/iso/openSUSE-GNOME-LiveCD-Build1034-i686.iso oprah

The option -t shows the command to be executed:

virt-install 
--connect=qemu:///system 
--ram=512 
--arch=i686 
--vcpus=1 
--video=vga 
--soundhw=default 
--description="oprah - vm guest on host bareil" 
--os-variant=generic26 
--cdrom=/data/Linux/openSUSE/11.4/iso/openSUSE-GNOME-LiveCD-Build1034-i686.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/oprah.img,size=10 
--network network=default
--vnc 
--vnclisten=0.0.0.0 
--name=oprah

Since libvirt doesn’t recognize openSUSE 11.4 as a valid os variant at this time, vm-create selected “generic26” kernel instead. However we know that openSUSE’s kernel supports the virtio driver, an I/O an network driver (libvirt: Wiki: Virtio) optimized for kvm guests. So we can add the option -V (–virtio) to our command and take a look at it first:

**# vm-create -t -S -V -i /data/Linux/openSUSE/11.4/iso/openSUSE-GNOME-LiveCD-Build1034-i686.iso oprah **

virt-install 
--connect=qemu:///system 
--ram=512 
--arch=i686 
--vcpus=1 
--video=vga 
--soundhw=default 
--description="oprah - vm guest on host bareil" 
--os-variant=virtio26 
--cdrom=/data/Linux/openSUSE/11.4/iso/openSUSE-GNOME-LiveCD-Build1034-i686.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/oprah.img,size=10 
--network network=default 
--vnc 
--vnclisten=0.0.0.0 
--name=oprah

When we’re happy with that command, we can execute it after removing the option -t.

Checking the default settings

To see the current defaults, use the option -d (–defaults)

vm-create -d will print:

DEF_HYPERVISOR          qemu    
DEF_RAM                 512
DEF_CPUS                1
DEF_ARCH                i386
DEF_KERN                generic26
DEF_VIDEO               vga                     
DEF_AUDIO               default         
DEF_VMSIZE              10                      
DEF_BRIDGE              br0
DEF_NIC                 eth0
DEF_NAT                 virbr0
DEF_NETWORK             nat             
DEF_NETMASK             0xffffff00      

Viewing available options

To see the available options, use the option -h (–help)

I strongly encourage you to read the virt-install and virsh manpages before using this script.

bugs

to create a network pool livirtd 0.8.7 expects the following syntax:

pool-create-as <name> netfs <source host> <source path> -- --** --** <target>

while libvirtd 0.8.0 and 0.8.3 requires:

pool-create-as <name> netfs <source host> <source path> -- -- <target>

I don’t know what the third optional argument (–) is supposed to be. This is not documented and doesn’t show up with --print-xml. I suspect a bug in libvirt 0.8.7 (?). vm-create handles the situation by checking libvirt version. However you might encounter a syntax error if using versions other than 0.8.0, 0.8.3 and 0.8.7, because other versions may have changed this behavior.

remote install tested so far with kvm

Remote install was conducted on openSUSE server running libvirtd 0.8.7 (from Virtualization repo) from different client machines:

  • openSUSE (virt-install 0.500.4, libvirtd 0.8.7)
  • Ubuntu (virt-install 0.500.3, libvirtd 0.8.3)
  • Fedora (virt-install 0.500.4, libvirtd 0.8.3)
  • ArchLinux (virt-install 0.500.5, libvirt 0.8.7)
  • Mandriva (virt-install 0.500.3, libvirt 0.8.0)
                                                             boot      install     reboot
_____________________________________________________________________________________________
Debian    netinstall (debian-508-i386-netinst.iso)            : OK       OK         OK
Fedora    netinstall (Fedora-14-i386-netinst.iso)             : OK       OK         Failed 
Centos    netinstall (Centos-5.5-i386-netinstall.iso)         : OK       OK         OK
Archlinux netinstall (archlinux-2010.05-netinstall-i686.iso)  : OK       OK         OK**1**
openSUSE  netinstall (openSUSE-NET-Build1034-i586.iso)        : OK       OK         OK
Mandriva  netinstall (2010.2 boot.iso)                        : OK       OK         OK
Ubuntu    netinstall (mini.iso)                               : OK       OK         OK/Failed**2**
FreeBSD   netinstall (FreeBSD-8.2-RC3-i386-bootonly.iso)      : OK       OK         OK
NetBSD    cd install (i386cd-5.1.iso)                         : OK       Failed**3**    Failed 
OpenBSD   netinstall (cd48.iso)                               : OK       OK         OK**5**   
DragonFly cd install (dfly-i386-2.8.2_REL.iso)                : OK       OK         OK
_____________________________________________________________________________________________

local install tested so far with kvm

                                                              boot      install     reboot
_____________________________________________________________________________________________
openSUSE 11.3:     (openSUSE-11.3-DVD-i586.iso)               : OK       OK         OK
openSUSE 11.4 M6:  (openSUSE-GNOME-LiveCD-Build1034-i686.iso) : OK       OK         OK
Fedora 14:         (Fedora-14-i686-Live-Desktop.iso)          : OK       OK         OK
Ubuntu 10.10       (ubuntu-10.10-desktop-i386.iso)            : OK       OK         Failed**2**    
NetBSD 5.1         (i386cd-5.1.iso)                           : OK       OK**4**        OK
OpenBSD 4.8        (install48.iso)                            : OK       OK         OK**5**

_____________________________________________________________________________________________

Install “OK” & reboot “Failed” means that the setup appears to complete successfully, however the system failed to boot afterwards.

  1. If using the virtio driver, you should reinstall Grub as described here https://wiki.archlinux.org/index.php/KVM#Preparing_an_.28arch.29linux_guest or it won’t reboot.
  2. Fail to boot on Fedora 14 host with an “out of range pointer” message (with Grub2 in MBR or in partition bootsector)
  3. Failed to extract all the binary sets
  4. Finally OK but took very long to extract big archives. Remote install would probably have worked if I had waited long enough.
  5. Disable mpbios at first reboot:
boot> bsd -c
...
UKC> disable mpbios
352  mpbios disabled
UKC> quit
...

and later permanently:

# config -ef /bsd: 
UKC> disable mpbios
352  mpbios disabled
UKC> quit
Saving modified kernel

vms created on system with different **virt-install **versions might need some changes before you can import them. For example to use vms created on openSUSE or Fedora in Ubuntu or Mandriva, you have to replace

<type arch='i686' machine='pc-0.**13**'>hvm</type>

with

<type arch='i686' machine='pc-0.**12**'>hvm</type>

in the xml definition files.

Oh ! Oh ! I am eager to test it out.

Thanks please_try_again and I really hope it’ll work well for me.

I’ll test it on xen and give you feedback later. :slight_smile:

To make it even simpler (well … in some way), you can create the following function in your environment:

function newvm {
        unset C N guest host size ram bridge eth arch os
        if  "x$1" == "x-n" ] ; then 
                N=" -t"
                shift
        fi
        guest=$1 ; shift
        host=$1 ; shift
        size=${VM_SIZE:-10}
        ram=${VM_RAM:-512}
        bridge=${VM_BRIDGE:-br0}
        eth=${VM_ETH:-eth0}
        arch=${VM_ARCH:-i386}
        OS=$(lsb_release -s -i | tr ":upper:]" ":lower:]" | sed 's| *linux||;s|suse|open&|;s|n/a|archlinux|')
        os=${VM_OS:-$OS}
         "$os" == "archlinux" ] && virtio=" -V"
         "$host" == "" ] || C=" -c $host"
        su -p -c "vm-create$N$C -a $arch -r $ram -s $size -b $bridge -e $eth$virtio -u $os $guest"
}

The function calls the script with some default values that can be changed in the environment variables VM_SIZE, VM_RAM, VM_BRIDGE, VM_ETH, VM_ARCH and VM_OS. And such a function can be executed by users since it calls the script with “su”, while the script has to be executed by root (only root can create bridges and connect to qemu:///system or xen:///system - at least under openSUSE AFAIK.)

Thus with this function in your environment (write it in a ~/.functions file that you’ll source from your ~/.profile or ~/.bash_profile, just like aliases), if for example you set the following environment variables:

export VM_SIZE=20
export VM_RAM=1024
export VM_OS=opensuse

you’ll just have to run:
newvm spock

To create a vm (called spock) on a 20GB virtual disk with 1GB RAM and start an opensuse 11.3 net install in it.

  • Use the -n option before the machine name to display the command rather than execute it :

newvm -n spock

virt-install 
--connect=qemu:///system 
--ram=1024 
--arch=i586 
--vcpus=1 
--video=vga 
--description="spock (opensuse 11.3) - vm guest on host uhura" 
--os-variant=sles11 
--cdrom=/misc/iso/openSUSE-11.3-NET-i586.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/spock.img,size=20 
--network bridge=br0 
--vnc 
--vnclisten=0.0.0.0 
--name=spock

  • add a hostname after the vm name to install it on a remote host rather than localhost:

newvm spock risa :wink:

The function uses bridge as default (unlike the script and virt-install).

Such a function only makes sense if you create a lot of virtual machines.

  • Maybe I should use virtio26 rather than sles11 as default for openSUSE. I don’t know.

Similarly, to install from an openSUSE client to a Fedora server, you have to symlink nc to netcat on the Fedora machine. So netcat seems to be the issue while connecting two different Linux distros through VNC. The program needs to have the same name on the server and on the client trying to connect.

vm-create version 2.0

(Please see release notes in the next post)

#! /bin/bash
#: Title       : vm-create
#: Date Created: Sat Jan 29 01:54:07 PST 2011 
#: Last Edit   : Wed Feb 23 06:08:08 PST 2011
#: Author      : please_try_again
#: Version     : 2.0
#: Description : install OS from the Internet or local iso in a kvm vm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prg=$(basename $0)

# use the popup library if found
which popup &>/dev/null && source $(which popup)

# use popup functions if available
function Error { declare -F | grep -q error || exec echo "Error: $*" ; error "$*" ;}
function Warn  { declare -F | grep -q warn  && warn  "$*" || echo "Warning: $*" ;}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

which virt-install &> /dev/null || Error "virt-install not installed"
[ -r /etc/vm-create.cfg ] && source /etc/vm-create.cfg || Error "/etc/vm-create.cfg not found"
[ -r ~/.vm-create.cfg ] && source ~/.vm-create.cfg 

[ "x$(ps ax | grep libvirtd | awk '!/grep/ { print $1 }')" == "x" ] && Error "libvirtd is not running" 
VIRTINST_VER=$(virt-install --version | sed 's|^0||;s|\.||g')
LIVIRT_VER=$(libvirtd --version | sed 's|[^0-9]*\([0-9]\)|\1|;s|\.||g;s|^0||')

# recognized OS types to this day.
OSTYPES="debianetch debianlenny debiansqueeze \
fedora5 fedora6 fedora7 fedora8 fedora9 fedora10 fedora11 fedora12 fedora13 \
generic24 generic26 \
virtio26 \
mandriva2009 mandriva2010 \
mes5 mes5.1 \
rhel2.1 rhel3 rhel4 rhel5 rhel5.4 rhel6 \
sles10 sles11 \
ubuntuhardy ubuntuintrepid ubuntujaunty ubuntukarmic \
generic \
msdos \
netware4 netware5 netware6 \
opensolaris solaris10 solaris9 \
freebsd6 freebsd7 \
openbsd4 \
vista win2k win2k3 win2k8 win7 winxp winxp64"

[ $VIRTINST_VER -lt 5003 ] && Error "virt-install 0.500.3 or newer required"
[ $VIRTINST_VER -ge 5004 ] && { OSTYPES="$OSTYPES fedora14 ubuntulucid freebsd8" ; BOOTOPTS=" --boot=cdrom,hd,network";}
[ $VIRTINST_VER -ge 5005 ] && OSTYPES="$OSTYPES ubuntumaverick"
[ $LIVIRT_VER -ge 87 ] && Y="-- "

DEF_CONNECT=${VIRSH_DEFAULT_CONNECT_URI:-${DEF_HYPERVISOR}://$DEF_HOST/system}
DISPLAYOPTS=" --vnc --vnclisten=0.0.0.0"

# valid xen and qemu MAC addresses
qemu_mac="52:54:00:"
xen_mac="00:16:3e:"

# valid video adapters
video_models=":vga:vmvga:cirrus:xen:"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

function chknum {
	N=$(echo $1 | tr -d "[:alpha:]")
	[ "x$N" == "x$1" ] || Error "invalid numeric argument: $1" 
}

function chkMAC {
	Mac=$(echo ${1:0:9} | tr "[:upper:]" "[:lower:]")
	MAC=${HV}_mac ; MAC=${!MAC}
	[ "$Mac" == "$MAC" ] || Error "invalid $HV MAC address: $1"
	[ "$(echo ${1:9} | sed 's|[^:]*||g')" == "::" ] || Error "invalid $HV MAC address: $1"
	a=$(echo ${1:9} | sed 's|:||g;s|[^0-9a-fA-F]*||g')
	[ ${#1} -eq 17 -a ${#a} -eq 6 ] || Error "invalid $HV MAC address: $1"
}

function chkarg { [ "${1:0:1}" == "-" ] && Error "invalid argument: $1" ;}
function chkopt { [ "x$1" == "x" ] && Error "you should provide a vm name" ;}

function syntax {
	cat << EOFSYNTAX
usage:
   $prg vm name

options:
	-c --connect <host>    : remote host where to create the vm
	-r --ram <ram in MB>   : amount of ram (default 512MB)
	-a --arch <i386|amd64> : architecture (i386 or amd64)
	-o --os <os variant>   : os variant (example: fedora14, mandriva2010)
	-u --url <os>          : os (fedora, debian, ubuntu, opensuse, mandriva) 
	-i --iso <img.iso>     : iso image 
	-p --path <directory>  : where to store vm images
	-s --size <size>       : size of virtual machine in GB
	-V --virtio            : same as -o virtio26
	-v --video             : video device: cirrus, vga* or vmvga (for kvm)
	-R --release           : OS version to install (other than default)
	-S --sound             : enable sound device (ac97)
	-l --sdl               : use sdl display rather than vnc
	-L --live              : boot live CD
	-b --bridge <bridge>   : use network bridge (otherwise use DEF_NETWORK if defined or 'nat')
	-e --eth <nic>         : network interface to use for bridge (otherwise use DEF_NIC if defined or eth0)
	-x --xen               : use Xen hypervisor rather than kvm	
	-t --try               : just print but not execute the command
	-h --help              : show this help
	-d --disks             : number of virtual hard disks

recognized OS variants    :
`echo $OSTYPES | fmt`

available guest systems   :
$(awk -F "=" '/^[a-zA-Z0-9]*_url=|^[a-zA-Z0-9]*_iso=/ { sub(/_.*/,"",$1); print $1 }' /etc/vm-create.cfg | sort -u | tr "
" " ")
EOFSYNTAX
exit
}

function createBridge {
	[ "x$gw" == "x" ] && Error "no gateway defined"
	ping -q -c 1 -W 2 $gw > /dev/null || Error "gateway $gw is unreachable"
	echo  " - creating bridge $1"
	brctl addbr $1 
	ifconfig $2 0.0.0.0 promisc 
	brctl addif $1 $2
	ifconfig $1 inet $3 netmask $DEF_NETMASK
	brctl stp $br on
	route del default 2>/dev/null
	route add default gw $gw 2>/dev/null
}

function importPool {
	virsh -c $hyp pool-dumpxml $1 2>/dev/null | grep -q $2 && {
		virsh -c $hyp pool-info $1 | grep -q running || virsh -c $hyp pool-start $1
		virsh -c $hyp pool-refresh $1
		return
	}
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-destroy $1 2>/dev/null
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-undefine $1 2>/dev/null
	$SSH mkdir -m 600 $POOLPATH 2>/dev/null
	virsh -c $hyp pool-create-as $1 netfs $2 $ISOPATH -- -- $Y$POOLPATH
}

function getarch {
	case $1 in
	amd64)
		case $os in
			fedora|mandriva|archlinux|opensuse) echo x86_64 ;;
			*) echo $1 ;;
		esac
	;;
	i386)
		case $os in
			mandriva|opensuse*) echo i586 ;;
			archlinux) echo i686 ;;
			gentoo) echo x86 ;;
			*) echo $1 ;;
		esac
	;;
	esac
}

function getiso {
	[ -d $ISOPATH ] && cd $ISOPATH || Error "$ISOPATH doesn't exist"
	IMG=$1 ; shift ; URL=$1
	ISO=$(basename $IMG .bz2) ; ISO=$(basename $ISO .tgz)
	if [ $tflag -eq 0 ] ; then
		if [ "$URL" ] ; then
			[ -f ./$ISO  ] || [ -f ./$IMG ] || wget -v $URL/$IMG
			[ -f ./$ISO ] || bunzip2 -v $IMG
		else
			[ -f $IMG ] || Error "File not found: $IMG"
			file $IMG | grep -q bootable || Error "$IMG is not a bootable iso image"
		fi
	fi 
	DIR=$(dirname $IMG)
	[ "$DIR" == "." ] && DIR=$(pwd)

	if [ "$rflag" ] ; then
		[ "$DIR" == "$ISOPATH" ] || Error "iso img $ISO not in shared pool $ISOPATH"
		[ $tflag -eq 0 ] && importPool $POOLNAME $HOSTNAME
		url=" --disk vol=$POOLNAME/$ISO,device=cdrom"
	else
		url=" --cdrom=$DIR/$ISO"
	fi
	cd - >/dev/null
}

# find out if we need to download a netinstall iso image
function geturl {
	ARCH=$(getarch $arch)
	URL=${1//ARCH/$ARCH} ; shift
	URL=${URL//VER/$1}
	if [ "$iflag" ] ; then
		img=$(links -dump $URL | awk '/dfly|netinst|mini|NET|boot|cd[-0-9]/&&/iso$|iso.bz2$/&&/'$ARCH'/ { sub(/.*[/\] ]/,"", $0) ; print $0 }' | sort -u | tail -1)
		getiso $(basename $img) $URL
	else
		url=" --location=$URL"
	fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

uflag=0 ; vflag=0 ; bflag=0 ; sflag=0 ; dflag=0 ; tflag=0 ; lflag=0 ; aflag=0 
url=""

HV=$DEF_HYPERVISOR
hds=1
# evaluate arguments
args=`getopt -q -u -o c:r:a:m:o:u:i:p:s:b:e:R:v:d:xSVlLht -l connect:,ram:,arch:,mac:,os:,url:,iso:,path:,size:,bridge:,eth:,release:,video:,disks:,xen,virtio,sdl,sound,live,help,try -- "$@"`

set -- $args

for i; do
	case "$i" in
		-c|--connect)  shift; chkarg $1 ; rhost="$1" ; shift ;;
		-r|--ram)      shift; chkarg $1 ; ram="$1" ; shift ;;
		-a|--arch)     shift; chkarg $1 ; arch="$1" ; shift ;;
		-m|--mac)  	   shift; chkarg $1 ; mac="$1" ; shift ;;
		-o|--os)       shift; chkarg $1 ; ostype="$1" ; shift ;;
		-i|--iso)      shift; chkarg $1 ; iso="$1" ; uflag=$(($uflag | 1)) ; shift ;;
		-u|--url)      shift; chkarg $1 ; os="$1" ; uflag=$(($uflag | 2)) ; shift ;;
		-p|--path)     shift; chkarg $1 ; imgpath="$1" ; shift ;;
		-s|--size)     shift; chkarg $1 ; vmsize="$1" ; shift ;;
		-b|--bridge)   shift; chkarg $1 ; br="$1"  ; bflag=1  ; shift ;;
		-e|--eth)  	   shift; chkarg $1 ; nic="$1" ; shift ;;
		-R|--release)  shift; chkarg $1 ; ver="$1" ; shift ;;
		-v|--video)    shift; chkarg $1 ; video="$1" ; shift ;;
		-d|--disks)    shift; chkarg $1 ; hds="$1" ; shift ;;
		-x|--xen)      XEN=xen ; shift ;;
		-V|--virtio)   vflag=1 ; shift ;;
		-S|--sound)    aflag=1 ; shift ;;
		-l|--sdl)  	   sflag=1 ; shift ;;
		-L|--live) 	   lflag=1 ; shift ;;
		-t|--try)      tflag=1 ; shift ;;
		-h|--help)     syntax ;;
		--) shift ;    chkopt $1 ; vm=$1 ; shift ;;
	esac
done

if [ $uflag -ge 3 ] ; then
	Error "options --url and --iso are mutually exclusive."
elif [ $uflag -le 0 ] ; then
	Error "No OS or iso image specified."
fi

# check remote host
[ "$rhost" ] && hyp=$HV+ssh://$rhost/system || { [ "$HV" == "xen" ] && hyp=$HV:///system || hyp=${DEF_CONNECT:-$HV:///system} ; }
DEF_RHOST=$(echo ${DEF_CONNECT##*://} | sed 's|/system||')
host=${rhost:-$DEF_RHOST} ; host=${host:-$HOSTNAME}
[ "$host" == "$HOSTNAME" ] || {
	ping -q -c 1 -W 2 $host &> /dev/null || Error "host $host is unreachable" 
	rflag=1 ; iflag=1 ; SSH="ssh root@$host" 
}

# check virtual machine
virsh -c $hyp dominfo $vm &> /dev/null && Error "Virtual machine $vm already exists."

# check ram
ram=${ram:-$DEF_RAM}
chknum $ram ; [ $ram -ge $MIN_RAM ] || Error "not enough RAM allocated: $ram MB"

# check architecture
arch=${arch:-$DEF_ARCH}
arch=$(echo $arch | sed 's|i[456]|i3|;s|x86_|amd|')
[ "$arch" == "i386" -o "$arch" == "amd64" ] || Error "unsupported architecture: $arch"

# check OS version
if [ $uflag -ge 2 ] ; then
	url=${os}_url ; url=${!url}
	[ "$url" ] || { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	[ "$rflag" ] && { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	VER=${os}_ver ; VER=${!VER} ; ver=${ver:-$VER}
	which links &>/dev/null || Error "links not found. Please install the links package first."
	[ "$url" ] && geturl $url $ver  || Error "invalid OS: $os"
# check iso image
elif [ $uflag -ge 1 ] ; then
	getiso $iso
fi

# check size of vm 
vmsize=${vmsize:-$DEF_VMSIZE}
chknum $vmsize && VMSIZE=$vmsize

# check video
[ "$video" ] || video=$DEF_VIDEO
[ "${video_models%%:$video:*}" == "$video_models" ] && Error "Invalid video device: $video"

# check destination path,
IMGPATH=${IMGPATH:-/var/lib/libvirt/images}
imgpath=${imgpath:-$IMGPATH}
[ -d $imgpath ] || Error "Destination directory $imgpath doesn't exist."

# check mac address
[ "$mac" ] || mac=`$SSH grep $vm /etc/ethers 2&gt;/dev/null | awk '{print $1}'`
[ "$mac" ] && chkMAC $mac

# OS and version

[ "$ver" ] && osver=" $ver"
[ "$os" ] && OSDESC=" ($os$osver)"
desc="$vm$OSDESC - vm guest on host $host"

# check os variant
Ver=${ver%%.*}
[ "$ostype" ] || ostype=$os$Ver
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$os$VER
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$(echo $os${OSTYPES##*$os} | sed 's| .*||')
[ "${OSTYPES%$ostype*}" == "$OSTYPES" -a $vflag -gt 0 ] && ostype=virtio26  
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$DEF_KERN  
[ "ostype" ] || ostype=generic  

# check number of disk
[ "$hds" == "1" ] || chknum $hds
[ $hds -gt 1 ] && VMSIZE=$(($vmsize * $hds))

i=0 
while [ $i -lt $hds ] ; do
	hd[$i]=${vm}_hd$i.img
	let i++
done

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if [ "$rflag" ] ; then
	virsh -c $hyp pool-info default | grep -q running || virsh -c $hyp pool-start default
	poolAvailableSize=$(virsh -c $hyp pool-info default | awk '/Available:/&&/GB/ { s=int($2 * 1024) ;  print s }')
	[ $(( ($VMSIZE + 1) * 1024)) -gt $poolAvailableSize ] && Error "Not enough space available on default pool" 
	if [ $hds -gt 1 ] ; then
		if [ $tflag -eq 0 ] ; then
			for hd in ${hd[li]} ; do
[/li]				echo " - creating raw volume $hd, please wait..."
				virsh -c $hyp vol-create-as default $hd ${vmsize}G
			done	
		fi
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk '{ printf " --disk vol=default/%s", $1}')
	else
		DISKOPTS=" --disk pool=default,size=$vmsize"
	fi
else
	# check if there is enough space to store image locally. 
	hyp=$DEF_CONNECT
	[ $(( ($VMSIZE + 1) * 1024)) -gt $(df -Pl -B $((1024*1024)) $imgpath | awk 'END { print $4 }') ] && Error "Not enough space available on $imgpath" 
	if [ $lflag -ge 1 ] ; then
		DISKOPTS=" --nodisk"
		[ $VIRTINST_VER -gt 5003 ] && BOOTOPTS=" --boot=cdrom"
	else
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk 'BEGIN {S='$vmsize' ; P="'"$imgpath"'"} ; {printf " --disk path=%s/%s,size=%s", P, $1, S}')
	fi
fi

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# networking 
gw=${GATEWAY:-$GW}

# which networking method to use
if [ $bflag -eq 1 -o "$DEF_NETWORK" == "bridge" ] ; then
	br=${br:-$DEF_BRIDGE}
	nic=${nic:-$DEF_NIC}
	ifconfig $nic &> /dev/null || Error "invalid network device: $nic"
	# create bridge on localhost if needed
	if [ "$rflag" ] ; then
		if [ $tflag -eq 0 ] ; then
			$SSH brctl show | grep -q "^$br[ 	]" || Error "bridge $br not present on host $host" 
		fi
	else
		if [ "x$CREATEBRIDGE" == "x1" -a $tflag -eq 0 ] ; then
			net=$(ifconfig $nic | awk '/inet addr/ { sub(/addr:/,"",$2) ; print $2 }')
			brctl show | grep -q "^$br[ 	]" && echo "bridge $br already set" || createBridge $br $nic $net 
		fi
	fi
	NETOPTS=" --network bridge=$br"
else
	# start virtual network (NAT) if needed
	if [ "x$STARTVIRBR" == "x1" ] ; then
		virsh -c $hyp net-list | grep -q 'active' || virsh -c $hyp net-start default
		NETOPTS=" --network network=default" 
	fi
fi
[ "$mac" ] && NETOPTS="$NETOPTS,mac=$mac"

case $os in
	openbsd) NETOPTS="$NETOPTS,model=e1000" ;;
	netbsd)  NETOPTS="$NETOPTS,model=ne2k_pci" ; ACPI=" --noacpi" ; ostype=openbsd4 ;;
	dflybsd) NETOPTS="$NETOPTS,model=e1000" ; ostype=freebsd7 ;;
	opensuse) ostype=virtio26 ;;
esac

[ "$ostype" ] && ostype=" --os-variant=$ostype"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# display 
[ $sflag -eq 1 ] && DISPLAYOPTS=" --sdl"
[ $aflag -ge 1 ] && audio=" --soundhw=$DEF_AUDIO"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if [ $tflag -ge 1 ] ; then
	printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm

" | sed 's|--|
--|g'
else
	virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm
fi

vm-create 2.0 includes several major changes:

  • The script now uses a systemwide config file /etc/vm-create.cfg
    (required) and optional user config file ~/.vm-create.cfg to save user preferences and urls for network installations. The urls are also better organized, as the iso filenames are not written in the script anymore but read whenever possible from the distro ftp or http mirrors. The filename is read from the text browser “links”. Therefore the links package has to be installed. This make the installation of newer versions easier.
    To install another version as the default version, you can specify the release with the -R option. The following command installs openSUSE 11.4 RC1 in a virtual machine called ‘oprah’:
# vm-create -R 11.4-RC1 -u opensuse oprah

Withouth the -R option followed by the release, it would install openSUSE 11.3. Same goes for Ubuntu ‘natty’ alpha releases installed with the command:

# vm-create -R natty -u ubuntu   

Since those alpha and beta releases are not defined in /etc/vm-create.cfg but parsed from FTP listing outputs, chances are that future versions will be installed in the same way withouth the need of changing the script or its config file. Let’s hope so!

  • As a consequence the option -d
    (–default) which displayed the preferences previously defined in the script has been removed.
  • the letter ‘d’ is now used for another option -d
    (–disks) serving a new feature: the possibility of assigning several virtual disks to a virtual machine. The number of disks to use is given as argument of the option. This works for local and remote installs although in a different way. For local installation, several --disk path options are passed to virt-install, as in the example below:
# vm-create -t -s 8 -d 2 -b br0 -R 11.4-RC1 -u opensuse oprah

virt-install 
--connect=qemu:///system 
--ram=512 
--arch=i386 
--vcpus=1 
--video=vga 
--description="oprah (opensuse 11.4-RC1) - vm guest on host jadzia" 
--os-variant=virtio26 
--cdrom=/misc/iso/openSUSE-NET-Build1056-i586.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/oprah_hd0.img,size=8 
--disk path=/misc/vm/oprah_hd1.img,size=8 
--network bridge=br0 
--vnc 
--vnclisten=0.0.0.0 
--name=oprah

When installing remotely, the disk are first created in the default pool through a virsh connection to the remote hosts and passed to virt-install as --disk vol options. vm-create is not limited in the number of virtual disks you can add (although libvirt, kvm or xen might … I don’t know). In both local and remote installs, vm-create will add the sizes of all disks to create (more precisely multiply the desired size by the number of disks) and first check if there is enough space available on the local filesystem or remote pool. As you can only specify the size (in the -s or --size option) and the number of disks to crate (with -d or --disks), all the disks must have the same size. Using virtual disks of different size while creating a virtual machine is rarely needed if ever.

# vm-create -c bareil -t -s 8 -d 2 -b br0 -R 11.4-RC1 -u opensuse oprah

virt-install 
--connect=qemu+ssh://bareil/system 
--ram=512 
--arch=i386 
--vcpus=1 
--video=vga 
--description="oprah2 (opensuse 11.4-RC1) - vm guest on host bareil" 
--os-variant=virtio26 
--disk vol=install/openSUSE-NET-Build1056-i586.iso,device=cdrom 
--boot=cdrom,hd,network 
--disk vol=default/oprah2_hd0.img 
--disk vol=default/oprah2_hd1.img 
--network bridge=br0 
--vnc 
--vnclisten=0.0.0.0 
--name=oprah

Notice in red the differences in the syntax between local and remote installs.

  • Other changes in this version include a better error handling which tries to better catch syntax errors and invalid options entered by the user.

I used this version to install a raid1/LVM on 2 virtual hard disks for testing purpose and posted a quick howto here: http://forums.opensuse.org/english/get-technical-help-here/how-faq-forums/unreviewed-how-faq/454559-opensuse-11-4-raid1-lvm.html#post2294166

vm-create systemwide configuration file: /etc/vm-create.cfg

# system wide /etc/vm-create.cfg.
# variables  in this file can be ovrewritten in ~/.vm-create.cfg


# DEFINE THE VARIABLES IN THIS SECTIONS TO MATCH YOUR SYSTEM

IMGPATH=/misc/vm 	# where to put the vm images. 
ISOPATH=/misc/iso 	# where to save iso images
POOLNAME=install	# pollname for remote install
POOLPATH=/var/lib/libvirt/images/install

# your gateway (unless set in the environment variable GATEWAY) 
GW=192.168.101.1 		

# YOU MIGHT EDIT THE DEFAULTS BELOW

DEF_HYPERVISOR=qemu 	# qemu or xen
#DEF_HOST=				# default to localhost
DEF_RAM=512
DEF_CPUS=1
DEF_ARCH=i386
DEF_KERN=generic26
DEF_VIDEO=vga	 		# cirrus, vga or vmvga
DEF_AUDIO=default		# AC97 if the hypervisor supports it, otherwise it will be ES1370.
DEF_VMSIZE=10			# default size of virtual machines (10GB)


# network defaults
DEF_BRIDGE=br0
DEF_NIC=eth0
DEF_NAT=virbr0
DEF_NETWORK=nat	        # nat or bridge
DEF_NETMASK=0xffffff00	# should be fine for a class C network
CREATEBRIDGE=1			# create bridge if needed
STARTVIRBR=1			# start virtual network (NAT) if needed
MIN_RAM=64              # minimal amount of RAM for a vm

# You might change distros default versions
opensuse_ver=11.3
centos_ver=5
fedora_ver=14
ubuntu_ver=maverick
mandriva_ver=2010.2
debian_ver=squeeze
freebsd_ver=8.2
openbsd_ver=4.8
netbsd_ver=5.1
dflybsd_ver=2.8.2

# You might add/change ditros urls.
MIRROR="ftp://ftp.osuosl.org"

# mirrors
debian_mirror=$MIRROR
debiancd_mirror=$MIRROR
archlinux_mirror="$MIRROR/pub/archlinux"
centos_mirror="$MIRROR/pub/centos"
fedora_mirror="$MIRROR/pub/fedora"
gentoo_mirror="$MIRROR/pub/gentoo"
ubuntu_mirror="$MIRROR/pub/ubuntu"
#opensuse_mirror="$MIRROR/pub/opensuse"

# official sites 
freebsd_mirror="ftp://ftp.freebsd.org/pub/FreeBSD"
openbsd_mirror="ftp://ftp.openbsd.org/pub/OpenBSD"
netbsd_mirror="ftp://ftp.netbsd.org/pub/NetBSD"
dflybsd_mirror="ftp://ftp.dragonflybsd.org"
opensuse_mirror="http://download.opensuse.org"
mandriva_mirror="ftp://ftp.uwsg.indiana.edu"
#debian_mirror="ftp://ftp.debian.org"
#debiancd_mirror="htpp://cdimage.debian.org"
#archlinux_mirror="ftp://ftp.archlinux.org"
#fedora_mirror="http://download.fedoraproject.org/pub/fedora"
#gentoo_iso="http://distfiles.gentoo.org"
#ubuntu_mirror="http://ca.archive.ubuntu,com/ubuntu"

debian_url="$debian_mirror/debian/dists/VER/main/installer-ARCH/"
fedora_url="$fedora_mirror/linux/releases/VER/Fedora/ARCH/os/"
mandriva_url="$mandriva_mirror/linux/mandrake/official/VER/ARCH/"
ubuntu_url="$ubuntu_mirror/dists/VER/main/installer-ARCH/"

archlinux_iso="$archlinux_mirror/iso/latest"
centos_iso="$centos_mirror/VER/isos/ARCH"
debian_iso="$debiancd_mirror/debian-cd/current/ARCH/iso-cd"
fedora_iso="$fedora_mirror/linux/releases/VER/Fedora/ARCH/iso"
gentoo_iso="$gentoo_mirror/releases/ARCH/current-iso"
mandriva_iso="$mandriva_mirror/linux/mandrake/official/VER/ARCH/install/images"
opensuse_iso="$opensuse_mirror/distribution/VER/iso"
ubuntu_iso="$ubuntu_mirror/dists/VER/main/installer-ARCH/current/images/netboot"
freebsd_iso="$freebsd_mirror/releases/ARCH/ISO-IMAGES/VER"
openbsd_iso="$openbsd_mirror/VER/ARCH"
netbsd_iso="$netbsd_mirror/iso/VER"
dflybsd_iso="$dflybsd_mirror/iso-images"

vm-create version 2.1
There was a bug in the creation of 64bit virtual machines because some distros use amd64 and others x86_64 in the name of their install iso images. “amd64” was passed to virt-install instead of “x86_74”. This bug is fixed in this version. As before, you can give either amd64 or x86_64 as argument to the -a option in vm-create to create 64bit virtual machines.

#! /bin/bash
#: Title       : vm-create
#: Date Created: Sat Jan 29 01:54:07 PST 2011 
#: Last Edit   : Tue Mar  1 20:01:38 PST 2011
#: Author      : please_try_again
#: Version     : 2.1
#: Description : install OS from the Internet or local iso in a kvm vm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prg=$(basename $0)

# use the popup library if found
which popup &>/dev/null && source $(which popup)

# use popup functions if available
function Error { declare -F | grep -q error || exec echo "Error: $*" ; error "$*" ;}
function Warn  { declare -F | grep -q warn  && warn  "$*" || echo "Warning: $*" ;}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

which virt-install &> /dev/null || Error "virt-install not installed"
[ -r /etc/vm-create.cfg ] && source /etc/vm-create.cfg || Error "/etc/vm-create.cfg not found"
[ -r ~/.vm-create.cfg ] && source ~/.vm-create.cfg 

[ "x$(ps ax | grep libvirtd | awk '!/grep/ { print $1 }')" == "x" ] && Error "libvirtd is not running" 
VIRTINST_VER=$(virt-install --version | sed 's|^0||;s|\.||g')
LIVIRT_VER=$(libvirtd --version | sed 's|[^0-9]*\([0-9]\)|\1|;s|\.||g;s|^0||')

# recognized OS types to this day.
OSTYPES="debianetch debianlenny debiansqueeze \
fedora5 fedora6 fedora7 fedora8 fedora9 fedora10 fedora11 fedora12 fedora13 \
generic24 generic26 \
virtio26 \
mandriva2009 mandriva2010 \
mes5 mes5.1 \
rhel2.1 rhel3 rhel4 rhel5 rhel5.4 rhel6 \
sles10 sles11 \
ubuntuhardy ubuntuintrepid ubuntujaunty ubuntukarmic \
generic \
msdos \
netware4 netware5 netware6 \
opensolaris solaris10 solaris9 \
freebsd6 freebsd7 \
openbsd4 \
vista win2k win2k3 win2k8 win7 winxp winxp64"

[ $VIRTINST_VER -lt 5003 ] && Error "virt-install 0.500.3 or newer required"
[ $VIRTINST_VER -ge 5004 ] && { OSTYPES="$OSTYPES fedora14 ubuntulucid freebsd8" ; BOOTOPTS=" --boot=cdrom,hd,network";}
[ $VIRTINST_VER -ge 5005 ] && OSTYPES="$OSTYPES ubuntumaverick"
[ $LIVIRT_VER -ge 87 ] && Y="-- "

DEF_CONNECT=${VIRSH_DEFAULT_CONNECT_URI:-${DEF_HYPERVISOR}://$DEF_HOST/system}
DISPLAYOPTS=" --vnc --vnclisten=0.0.0.0"

# valid xen and qemu MAC addresses
qemu_mac="52:54:00:"
xen_mac="00:16:3e:"

# valid video adapters
video_models=":vga:vmvga:cirrus:xen:"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

function chknum {
	N=$(echo $1 | tr -d "[:alpha:]")
	[ "x$N" == "x$1" ] || Error "invalid numeric argument: $1" 
}

function chkMAC {
	Mac=$(echo ${1:0:9} | tr "[:upper:]" "[:lower:]")
	MAC=${HV}_mac ; MAC=${!MAC}
	[ "$Mac" == "$MAC" ] || Error "invalid $HV MAC address: $1"
	[ "$(echo ${1:9} | sed 's|[^:]*||g')" == "::" ] || Error "invalid $HV MAC address: $1"
	a=$(echo ${1:9} | sed 's|:||g;s|[^0-9a-fA-F]*||g')
	[ ${#1} -eq 17 -a ${#a} -eq 6 ] || Error "invalid $HV MAC address: $1"
}

function chkarg { [ "${1:0:1}" == "-" ] && Error "invalid argument: $1" ;}
function chkopt { [ "x$1" == "x" ] && Error "you should provide a vm name" ;}

function syntax {
	cat << EOFSYNTAX
usage:
   $prg vm name

options:
	-c --connect <host>    : remote host where to create the vm
	-r --ram <ram in MB>   : amount of ram (default 512MB)
	-a --arch <i386|amd64> : architecture (i386 or amd64)
	-o --os <os variant>   : os variant (example: fedora14, mandriva2010)
	-u --url <os>          : os (fedora, debian, ubuntu, opensuse, mandriva) 
	-i --iso <img.iso>     : iso image 
	-p --path <directory>  : where to store vm images
	-s --size <size>       : size of virtual machine in GB
	-V --virtio            : same as -o virtio26
	-v --video             : video device: cirrus, vga* or vmvga (for kvm)
	-R --release           : OS version to install (other than default)
	-S --sound             : enable sound device (ac97)
	-l --sdl               : use sdl display rather than vnc
	-L --live              : boot live CD
	-b --bridge <bridge>   : use network bridge (otherwise use DEF_NETWORK if defined or 'nat')
	-e --eth <nic>         : network interface to use for bridge (otherwise use DEF_NIC if defined or eth0)
	-x --xen               : use Xen hypervisor rather than kvm	
	-t --try               : just print but not execute the command
	-h --help              : show this help
	-d --disks             : number of virtual hard disks

recognized OS variants    :
`echo $OSTYPES | fmt`

available guest systems   :
$(awk -F "=" '/^[a-zA-Z0-9]*_url=|^[a-zA-Z0-9]*_iso=/ { sub(/_.*/,"",$1); print $1 }' /etc/vm-create.cfg | sort -u | tr "
" " ")
EOFSYNTAX
exit
}

function createBridge {
	[ "x$gw" == "x" ] && Error "no gateway defined"
	ping -q -c 1 -W 2 $gw > /dev/null || Error "gateway $gw is unreachable"
	echo  " - creating bridge $1"
	brctl addbr $1 
	ifconfig $2 0.0.0.0 promisc 
	brctl addif $1 $2
	ifconfig $1 inet $3 netmask $DEF_NETMASK
	brctl stp $br on
	route del default 2>/dev/null
	route add default gw $gw 2>/dev/null
}

function importPool {
	virsh -c $hyp pool-dumpxml $1 2>/dev/null | grep -q $2 && {
		virsh -c $hyp pool-info $1 | grep -q running || virsh -c $hyp pool-start $1
		virsh -c $hyp pool-refresh $1
		return
	}
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-destroy $1 2>/dev/null
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-undefine $1 2>/dev/null
	$SSH mkdir -m 600 $POOLPATH 2>/dev/null
	virsh -c $hyp pool-create-as $1 netfs $2 $ISOPATH -- -- $Y$POOLPATH
}

function getarch {
	case $1 in
	x86_64)
		case $os in
			centos|debian|dflybsd|freebsd|gentoo|netbsd|openbsd|ubuntu) echo amd64 ;;
			*) echo $1 ;;
		esac
	;;
	i386)
		case $os in
			mandriva|opensuse*) echo i586 ;;
			archlinux) echo i686 ;;
			gentoo) echo x86 ;;
			*) echo $1 ;;
		esac
	;;
	esac
}

function getiso {
	[ -d $ISOPATH ] && cd $ISOPATH || Error "$ISOPATH doesn't exist"
	IMG=$1 ; shift ; URL=$1
	ISO=$(basename $IMG .bz2) ; ISO=$(basename $ISO .tgz)
	if [ $tflag -eq 0 ] ; then
		if [ "$URL" ] ; then
			[ -f ./$ISO  ] || [ -f ./$IMG ] || wget -v $URL/$IMG
			[ -f ./$ISO ] || bunzip2 -v $IMG
		else
			[ -f $IMG ] || Error "File not found: $IMG"
			file $IMG | grep -q bootable || Error "$IMG is not a bootable iso image"
		fi
	fi 
	DIR=$(dirname $IMG)
	[ "$DIR" == "." ] && DIR=$(pwd)

	if [ "$rflag" ] ; then
		[ "$DIR" == "$ISOPATH" ] || Error "iso img $ISO not in shared pool $ISOPATH"
		[ $tflag -eq 0 ] && importPool $POOLNAME $HOSTNAME
		url=" --disk vol=$POOLNAME/$ISO,device=cdrom"
	else
		url=" --cdrom=$DIR/$ISO"
	fi
	cd - >/dev/null
}

# find out if we need to download a netinstall iso image
function geturl {
	ARCH=$(getarch $arch)
	URL=${1//ARCH/$ARCH} ; shift
	URL=${URL//VER/$1}
	if [ "$iflag" ] ; then
		img=$(links -dump $URL | awk '/dfly|netinst|mini|NET|boot|cd[-0-9]/&&/iso$|iso.bz2$/&&/'$ARCH'/ { sub(/.*[/\] ]/,"", $0) ; print $0 }' | sort -u | tail -1)
		getiso $(basename $img) $URL
	else
		url=" --location=$URL"
	fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

uflag=0 ; vflag=0 ; bflag=0 ; sflag=0 ; dflag=0 ; tflag=0 ; lflag=0 ; aflag=0 
url=""

HV=$DEF_HYPERVISOR
hds=1
# evaluate arguments
args=`getopt -q -u -o c:r:a:m:o:u:i:p:s:b:e:R:v:d:xSVlLht -l connect:,ram:,arch:,mac:,os:,url:,iso:,path:,size:,bridge:,eth:,release:,video:,disks:,xen,virtio,sdl,sound,live,help,try -- "$@"`

set -- $args

for i; do
	case "$i" in
		-c|--connect)  shift; chkarg $1 ; rhost="$1" ; shift ;;
		-r|--ram)      shift; chkarg $1 ; ram="$1" ; shift ;;
		-a|--arch)     shift; chkarg $1 ; arch="$1" ; shift ;;
		-m|--mac)  	   shift; chkarg $1 ; mac="$1" ; shift ;;
		-o|--os)       shift; chkarg $1 ; ostype="$1" ; shift ;;
		-i|--iso)      shift; chkarg $1 ; iso="$1" ; uflag=$(($uflag | 1)) ; shift ;;
		-u|--url)      shift; chkarg $1 ; os="$1" ; uflag=$(($uflag | 2)) ; shift ;;
		-p|--path)     shift; chkarg $1 ; imgpath="$1" ; shift ;;
		-s|--size)     shift; chkarg $1 ; vmsize="$1" ; shift ;;
		-b|--bridge)   shift; chkarg $1 ; br="$1"  ; bflag=1  ; shift ;;
		-e|--eth)  	   shift; chkarg $1 ; nic="$1" ; shift ;;
		-R|--release)  shift; chkarg $1 ; ver="$1" ; shift ;;
		-v|--video)    shift; chkarg $1 ; video="$1" ; shift ;;
		-d|--disks)    shift; chkarg $1 ; hds="$1" ; shift ;;
		-x|--xen)      XEN=xen ; shift ;;
		-V|--virtio)   vflag=1 ; shift ;;
		-S|--sound)    aflag=1 ; shift ;;
		-l|--sdl)  	   sflag=1 ; shift ;;
		-L|--live) 	   lflag=1 ; shift ;;
		-t|--try)      tflag=1 ; shift ;;
		-h|--help)     syntax ;;
		--) shift ;    chkopt $1 ; vm=$1 ; shift ;;
	esac
done

if [ $uflag -ge 3 ] ; then
	Error "options --url and --iso are mutually exclusive."
elif [ $uflag -le 0 ] ; then
	Error "No OS or iso image specified."
fi

# check remote host
[ "$rhost" ] && hyp=$HV+ssh://$rhost/system || { [ "$HV" == "xen" ] && hyp=$HV:///system || hyp=${DEF_CONNECT:-$HV:///system} ; }
DEF_RHOST=$(echo ${DEF_CONNECT##*://} | sed 's|/system||')
host=${rhost:-$DEF_RHOST} ; host=${host:-$HOSTNAME}
[ "$host" == "$HOSTNAME" ] || {
	ping -q -c 1 -W 2 $host &> /dev/null || Error "host $host is unreachable" 
	rflag=1 ; iflag=1 ; SSH="ssh root@$host" 
}

# check virtual machine
virsh -c $hyp dominfo $vm &> /dev/null && Error "Virtual machine $vm already exists."

# check ram
ram=${ram:-$DEF_RAM}
chknum $ram ; [ $ram -ge $MIN_RAM ] || Error "not enough RAM allocated: $ram MB"

# check architecture
arch=${arch:-$DEF_ARCH}
arch=$(echo $arch | sed 's|i[456]|i3|;s|amd|x86_|')
[ "$arch" == "i386" -o "$arch" == "x86_64" ] || Error "unsupported architecture: $arch"

# check OS version
if [ $uflag -ge 2 ] ; then
	url=${os}_url ; url=${!url}
	[ "$url" ] || { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	[ "$rflag" ] && { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	VER=${os}_ver ; VER=${!VER} ; ver=${ver:-$VER}
	which links &>/dev/null || Error "links not found. Please install the links package first."
	[ "$url" ] && geturl $url $ver  || Error "invalid OS: $os"
# check iso image
elif [ $uflag -ge 1 ] ; then
	getiso $iso
fi

# check size of vm 
vmsize=${vmsize:-$DEF_VMSIZE}
chknum $vmsize && VMSIZE=$vmsize

# check video
[ "$video" ] || video=$DEF_VIDEO
[ "${video_models%%:$video:*}" == "$video_models" ] && Error "Invalid video device: $video"

# check destination path,
IMGPATH=${IMGPATH:-/var/lib/libvirt/images}
imgpath=${imgpath:-$IMGPATH}
[ -d $imgpath ] || Error "Destination directory $imgpath doesn't exist."

# check mac address
[ "$mac" ] || mac=`$SSH grep $vm /etc/ethers 2&gt;/dev/null | awk '{print $1}'`
[ "$mac" ] && chkMAC $mac

# OS and version

[ "$ver" ] && osver=" $ver"
[ "$os" ] && OSDESC=" ($os$osver)"
desc="$vm$OSDESC - vm guest on host $host"

# check os variant
Ver=${ver%%.*}
[ "$ostype" ] || ostype=$os$Ver
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$os$VER
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$(echo $os${OSTYPES##*$os} | sed 's| .*||')
[ "${OSTYPES%$ostype*}" == "$OSTYPES" -a $vflag -gt 0 ] && ostype=virtio26  
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$DEF_KERN  
[ "ostype" ] || ostype=generic  

# check number of disk
[ "$hds" == "1" ] || chknum $hds
[ $hds -gt 1 ] && VMSIZE=$(($vmsize * $hds))

i=0 
while [ $i -lt $hds ] ; do
	hd[$i]=${vm}_hd$i.img
	let i++
done

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if [ "$rflag" ] ; then
	virsh -c $hyp pool-info default | grep -q running || virsh -c $hyp pool-start default
	poolAvailableSize=$(virsh -c $hyp pool-info default | awk '/Available:/&&/GB/ { s=int($2 * 1024) ;  print s }')
	[ $(( ($VMSIZE + 1) * 1024)) -gt $poolAvailableSize ] && Error "Not enough space available on default pool" 
	if [ $hds -gt 1 ] ; then
		if [ $tflag -eq 0 ] ; then
			for hd in ${hd[li]} ; do
[/li]				echo " - creating raw volume $hd, please wait..."
				virsh -c $hyp vol-create-as default $hd ${vmsize}G
			done	
		fi
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk '{ printf " --disk vol=default/%s", $1}')
	else
		DISKOPTS=" --disk pool=default,size=$vmsize"
	fi
else
	# check if there is enough space to store image locally. 
	hyp=$DEF_CONNECT
	[ $(( ($VMSIZE + 1) * 1024)) -gt $(df -Pl -B $((1024*1024)) $imgpath | awk 'END { print $4 }') ] && Error "Not enough space available on $imgpath" 
	if [ $lflag -ge 1 ] ; then
		DISKOPTS=" --nodisk"
		[ $VIRTINST_VER -gt 5003 ] && BOOTOPTS=" --boot=cdrom"
	else
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk 'BEGIN {S='$vmsize' ; P="'"$imgpath"'"} ; {printf " --disk path=%s/%s,size=%s", P, $1, S}')
	fi
fi

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# networking 
gw=${GATEWAY:-$GW}

# which networking method to use
if [ $bflag -eq 1 -o "$DEF_NETWORK" == "bridge" ] ; then
	br=${br:-$DEF_BRIDGE}
	nic=${nic:-$DEF_NIC}
	ifconfig $nic &> /dev/null || Error "invalid network device: $nic"
	# create bridge on localhost if needed
	if [ "$rflag" ] ; then
		if [ $tflag -eq 0 ] ; then
			$SSH brctl show | grep -q "^$br[ 	]" || Error "bridge $br not present on host $host" 
		fi
	else
		if [ "x$CREATEBRIDGE" == "x1" -a $tflag -eq 0 ] ; then
			net=$(ifconfig $nic | awk '/inet addr/ { sub(/addr:/,"",$2) ; print $2 }')
			brctl show | grep -q "^$br[ 	]" && echo "bridge $br already set" || createBridge $br $nic $net 
		fi
	fi
	NETOPTS=" --network bridge=$br"
else
	# start virtual network (NAT) if needed
	if [ "x$STARTVIRBR" == "x1" ] ; then
		virsh -c $hyp net-list | grep -q 'active' || virsh -c $hyp net-start default
		NETOPTS=" --network network=default" 
	fi
fi
[ "$mac" ] && NETOPTS="$NETOPTS,mac=$mac"

case $os in
	openbsd) NETOPTS="$NETOPTS,model=e1000" ;;
	netbsd)  NETOPTS="$NETOPTS,model=ne2k_pci" ; ACPI=" --noacpi" ; ostype=openbsd4 ;;
	dflybsd) NETOPTS="$NETOPTS,model=e1000" ; ostype=freebsd7 ;;
	opensuse) ostype=virtio26 ;;
esac

[ "$ostype" ] && ostype=" --os-variant=$ostype"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# display 
[ $sflag -eq 1 ] && DISPLAYOPTS=" --sdl"
[ $aflag -ge 1 ] && audio=" --soundhw=$DEF_AUDIO"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if [ $tflag -ge 1 ] ; then
	printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm

" | sed 's|--|
--|g'
else
	virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm
fi

Very usefull and interesting for me. Thank you @please_try_again!!!;):slight_smile:

Thanks. Got another problem with some URLs, including openSUSE. It seems that something changed on the download page, since I already installed openSUSE 11.3 and RC1. Anyway, I rewrote the geturl function an now it’s working for openSUSE (11.3 and 11.4), ubuntu (maverick and natty) as well as the other distros that the script handles. I also added the URL for the iso image in the output of **vm-create -t **… (which doesn’t do anything but printing the whole command). If you do a remote install (which means if you install a vm guest from the Internet onto a remote machine), you should see the iso URL displayed. If you don’t see it, the geturl function could not parse the download page (means something has changed again). And in that case, it won’t work. While installing from the Internet onto the local machine, the URL won’t appear in some cases not using the geturl and getiso functions, because the os_url (which refers to a directory and installs from that directory) has precedence over the os_iso (which first downloads a mini iso image from that directory to a directory on the local machine, exports that directory as a pool to the remote host and boots the iso image from there.

Installing from an Internet directory only works on a locale machine and only for the OS which have an os_url variable define in /etc/vm-create.cfg. These are debian, fedora, mandriva and ubuntu. It was supposed to work with openSUSE but it does not … and further it could not since it wouldn’t let you choose the architecture (32 or 64 bit) but select the architecture depending on the host … and having 32bit guests on 64bit hosts is quite common, in particular when you cannot afford 2 GB of RAM for each guest.

Be careful with distros which use a unique name for the net install iso image of different releases, like Ubuntu. If the iso image (in this case mini.iso) already exists in the volume pool, the script won’t download it again and you might end up installing the wrong version. It doesn’t affect openSUSE which use more specific filenames.

Here’s version 2.2 with rewritten geturl function:

#! /bin/bash
#: Title       : vm-create
#: Date Created: Sat Jan 29 01:54:07 PST 2011 
#: Last Edit   : Wed Mar  2 08:04:48 PST 2011
#: Author      : please_try_again
#: Version     : 2.2
#: Description : install OS from the Internet or local iso in a kvm vm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prg=$(basename $0)

# use the popup library if found
which popup &>/dev/null && source $(which popup)

# use popup functions if available
function Error { declare -F | grep -q error || exec echo "Error: $*" ; error "$*" ;}
function Warn  { declare -F | grep -q warn  && warn  "$*" || echo "Warning: $*" ;}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

which virt-install &> /dev/null || Error "virt-install not installed"
[ -r /etc/vm-create.cfg ] && source /etc/vm-create.cfg || Error "/etc/vm-create.cfg not found"
[ -r ~/.vm-create.cfg ] && source ~/.vm-create.cfg 

[ "x$(ps ax | grep libvirtd | awk '!/grep/ { print $1 }')" == "x" ] && Error "libvirtd is not running" 
VIRTINST_VER=$(virt-install --version | sed 's|^0||;s|\.||g')
LIVIRT_VER=$(libvirtd --version | sed 's|[^0-9]*\([0-9]\)|\1|;s|\.||g;s|^0||')

# recognized OS types to this day.
OSTYPES="debianetch debianlenny debiansqueeze \
fedora5 fedora6 fedora7 fedora8 fedora9 fedora10 fedora11 fedora12 fedora13 \
generic24 generic26 \
virtio26 \
mandriva2009 mandriva2010 \
mes5 mes5.1 \
rhel2.1 rhel3 rhel4 rhel5 rhel5.4 rhel6 \
sles10 sles11 \
ubuntuhardy ubuntuintrepid ubuntujaunty ubuntukarmic \
generic \
msdos \
netware4 netware5 netware6 \
opensolaris solaris10 solaris9 \
freebsd6 freebsd7 \
openbsd4 \
vista win2k win2k3 win2k8 win7 winxp winxp64"

[ $VIRTINST_VER -lt 5003 ] && Error "virt-install 0.500.3 or newer required"
[ $VIRTINST_VER -ge 5004 ] && { OSTYPES="$OSTYPES fedora14 ubuntulucid freebsd8" ; BOOTOPTS=" --boot=cdrom,hd,network";}
[ $VIRTINST_VER -ge 5005 ] && OSTYPES="$OSTYPES ubuntumaverick"
[ $LIVIRT_VER -ge 87 ] && Y="-- "

DEF_CONNECT=${VIRSH_DEFAULT_CONNECT_URI:-${DEF_HYPERVISOR}://$DEF_HOST/system}
DISPLAYOPTS=" --vnc --vnclisten=0.0.0.0"

# valid xen and qemu MAC addresses
qemu_mac="52:54:00:"
xen_mac="00:16:3e:"

# valid video adapters
video_models=":vga:vmvga:cirrus:xen:"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

function chknum {
	N=$(echo $1 | tr -d "[:alpha:]")
	[ "x$N" == "x$1" ] || Error "invalid numeric argument: $1" 
}

function chkMAC {
	Mac=$(echo ${1:0:9} | tr "[:upper:]" "[:lower:]")
	MAC=${HV}_mac ; MAC=${!MAC}
	[ "$Mac" == "$MAC" ] || Error "invalid $HV MAC address: $1"
	[ "$(echo ${1:9} | sed 's|[^:]*||g')" == "::" ] || Error "invalid $HV MAC address: $1"
	a=$(echo ${1:9} | sed 's|:||g;s|[^0-9a-fA-F]*||g')
	[ ${#1} -eq 17 -a ${#a} -eq 6 ] || Error "invalid $HV MAC address: $1"
}

function chkarg { [ "${1:0:1}" == "-" ] && Error "invalid argument: $1" ;}
function chkopt { [ "x$1" == "x" ] && Error "you should provide a vm name" ;}

function syntax {
	cat << EOFSYNTAX
usage:
   $prg vm name

options:
	-c --connect <host>    : remote host where to create the vm
	-r --ram <ram in MB>   : amount of ram (default 512MB)
	-a --arch <i386|amd64> : architecture (i386 or amd64)
	-o --os <os variant>   : os variant (example: fedora14, mandriva2010)
	-u --url <os>          : os (fedora, debian, ubuntu, opensuse, mandriva) 
	-i --iso <img.iso>     : iso image 
	-p --path <directory>  : where to store vm images
	-s --size <size>       : size of virtual machine in GB
	-V --virtio            : same as -o virtio26
	-v --video             : video device: cirrus, vga* or vmvga (for kvm)
	-R --release           : OS version to install (other than default)
	-S --sound             : enable sound device (ac97)
	-l --sdl               : use sdl display rather than vnc
	-L --live              : boot live CD
	-b --bridge <bridge>   : use network bridge (otherwise use DEF_NETWORK if defined or 'nat')
	-e --eth <nic>         : network interface to use for bridge (otherwise use DEF_NIC if defined or eth0)
	-x --xen               : use Xen hypervisor rather than kvm	
	-t --try               : just print but not execute the command
	-h --help              : show this help
	-d --disks             : number of virtual hard disks

recognized OS variants    :
`echo $OSTYPES | fmt`

available guest systems   :
$(awk -F "=" '/^[a-zA-Z0-9]*_url=|^[a-zA-Z0-9]*_iso=/ { sub(/_.*/,"",$1); print $1 }' /etc/vm-create.cfg | sort -u | tr "
" " ")
EOFSYNTAX
exit
}

function createBridge {
	[ "x$gw" == "x" ] && Error "no gateway defined"
	ping -q -c 1 -W 2 $gw > /dev/null || Error "gateway $gw is unreachable"
	echo  " - creating bridge $1"
	brctl addbr $1 
	ifconfig $2 0.0.0.0 promisc 
	brctl addif $1 $2
	ifconfig $1 inet $3 netmask $DEF_NETMASK
	brctl stp $br on
	route del default 2>/dev/null
	route add default gw $gw 2>/dev/null
}

function importPool {
	virsh -c $hyp pool-dumpxml $1 2>/dev/null | grep -q $2 && {
		virsh -c $hyp pool-info $1 | grep -q running || virsh -c $hyp pool-start $1
		virsh -c $hyp pool-refresh $1
		return
	}
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-destroy $1 2>/dev/null
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-undefine $1 2>/dev/null
	$SSH mkdir -m 600 $POOLPATH 2>/dev/null
	virsh -c $hyp pool-create-as $1 netfs $2 $ISOPATH -- -- $Y$POOLPATH
}

function getarch {
	case $1 in
	x86_64)
		case $os in
			centos|debian|dflybsd|freebsd|gentoo|netbsd|openbsd|ubuntu) echo amd64 ;;
			*) echo $1 ;;
		esac
	;;
	i386)
		case $os in
			mandriva|opensuse*) echo i586 ;;
			archlinux) echo i686 ;;
			gentoo) echo x86 ;;
			*) echo $1 ;;
		esac
	;;
	esac
}

function getiso {
	[ -d $ISOPATH ] && cd $ISOPATH || Error "$ISOPATH doesn't exist"
	IMG=$1 ; shift ; URL=$1
	ISO=$(basename $IMG .bz2) ; ISO=$(basename $ISO .tgz)
	if [ $tflag -eq 0 ] ; then
		if [ "$URL" ] ; then
			[ -f ./$ISO  ] || [ -f ./$IMG ] || wget -v $URL/$IMG
			[ -f ./$ISO ] || bunzip2 -v $IMG
		else
			[ -f $IMG ] || Error "File not found: $IMG"
			file $IMG | grep -q bootable || Error "$IMG is not a bootable iso image"
		fi
	fi 
	DIR=$(dirname $IMG)
	[ "$DIR" == "." ] && DIR=$(pwd)

	if [ "$rflag" ] ; then
		[ "$DIR" == "$ISOPATH" ] || Error "iso img $ISO not in shared pool $ISOPATH"
		[ $tflag -eq 0 ] && importPool $POOLNAME $HOSTNAME
		url=" --disk vol=$POOLNAME/$ISO,device=cdrom"
	else
		url=" --cdrom=$DIR/$ISO"
	fi
	cd - >/dev/null
}

# find out if we need to download a netinstall iso image
function geturl {
	ARCH=$(getarch $arch)
	URL=${1//ARCH/$ARCH} ; shift
	URL=${URL//VER/$1}
	if [ "$iflag" ] ; then
		imgs=($(links -dump $URL | awk '/dfly|netinst|mini|NET|boot|cd[-0-9]/&&/iso|iso.bz2/ { print $0}' | sed 's|.* \([^ ]*.iso[.bz2]*\).*|\1|;s|\.$||' | sort -u))
		[ ${#imgs[li]} -eq 1 ] && img=${imgs[0]} || img=$(echo ${imgs[*]} | tr " " "[/li]" | grep $ARCH | tail -1)
		getiso $(basename $img) $URL
		DISTURL="$URL/$img"
	else
		url=" --location=$URL"
	fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

uflag=0 ; vflag=0 ; bflag=0 ; sflag=0 ; dflag=0 ; tflag=0 ; lflag=0 ; aflag=0 
url=""

HV=$DEF_HYPERVISOR
hds=1
# evaluate arguments
args=`getopt -q -u -o c:r:a:m:o:u:i:p:s:b:e:R:v:d:xSVlLht -l connect:,ram:,arch:,mac:,os:,url:,iso:,path:,size:,bridge:,eth:,release:,video:,disks:,xen,virtio,sdl,sound,live,help,try -- "$@"`

set -- $args

for i; do
	case "$i" in
		-c|--connect)  shift; chkarg $1 ; rhost="$1" ; shift ;;
		-r|--ram)      shift; chkarg $1 ; ram="$1" ; shift ;;
		-a|--arch)     shift; chkarg $1 ; arch="$1" ; shift ;;
		-m|--mac)  	   shift; chkarg $1 ; mac="$1" ; shift ;;
		-o|--os)       shift; chkarg $1 ; ostype="$1" ; shift ;;
		-i|--iso)      shift; chkarg $1 ; iso="$1" ; uflag=$(($uflag | 1)) ; shift ;;
		-u|--url)      shift; chkarg $1 ; os="$1" ; uflag=$(($uflag | 2)) ; shift ;;
		-p|--path)     shift; chkarg $1 ; imgpath="$1" ; shift ;;
		-s|--size)     shift; chkarg $1 ; vmsize="$1" ; shift ;;
		-b|--bridge)   shift; chkarg $1 ; br="$1"  ; bflag=1  ; shift ;;
		-e|--eth)  	   shift; chkarg $1 ; nic="$1" ; shift ;;
		-R|--release)  shift; chkarg $1 ; ver="$1" ; shift ;;
		-v|--video)    shift; chkarg $1 ; video="$1" ; shift ;;
		-d|--disks)    shift; chkarg $1 ; hds="$1" ; shift ;;
		-x|--xen)      XEN=xen ; shift ;;
		-V|--virtio)   vflag=1 ; shift ;;
		-S|--sound)    aflag=1 ; shift ;;
		-l|--sdl)  	   sflag=1 ; shift ;;
		-L|--live) 	   lflag=1 ; shift ;;
		-t|--try)      tflag=1 ; shift ;;
		-h|--help)     syntax ;;
		--) shift ;    chkopt $1 ; vm=$1 ; shift ;;
	esac
done

if [ $uflag -ge 3 ] ; then
	Error "options --url and --iso are mutually exclusive."
elif [ $uflag -le 0 ] ; then
	Error "No OS or iso image specified."
fi

# check remote host
[ "$rhost" ] && hyp=$HV+ssh://$rhost/system || { [ "$HV" == "xen" ] && hyp=$HV:///system || hyp=${DEF_CONNECT:-$HV:///system} ; }
DEF_RHOST=$(echo ${DEF_CONNECT##*://} | sed 's|/system||')
host=${rhost:-$DEF_RHOST} ; host=${host:-$HOSTNAME}
[ "$host" == "$HOSTNAME" ] || {
	ping -q -c 1 -W 2 $host &> /dev/null || Error "host $host is unreachable" 
	rflag=1 ; iflag=1 ; SSH="ssh root@$host" 
}

# check virtual machine
virsh -c $hyp dominfo $vm &> /dev/null && Error "Virtual machine $vm already exists."

# check ram
ram=${ram:-$DEF_RAM}
chknum $ram ; [ $ram -ge $MIN_RAM ] || Error "not enough RAM allocated: $ram MB"

# check architecture
arch=${arch:-$DEF_ARCH}
arch=$(echo $arch | sed 's|i[456]|i3|;s|amd|x86_|')
[ "$arch" == "i386" -o "$arch" == "x86_64" ] || Error "unsupported architecture: $arch"

# check OS version
if [ $uflag -ge 2 ] ; then
	url=${os}_url ; url=${!url}
	[ "$url" ] || { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	[ "$rflag" ] && { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	VER=${os}_ver ; VER=${!VER} ; ver=${ver:-$VER}
	which links &>/dev/null || Error "links not found. Please install the links package first."
	[ "$url" ] && geturl $url $ver  || Error "invalid OS: $os"
# check iso image
elif [ $uflag -ge 1 ] ; then
	getiso $iso
fi

# check size of vm 
vmsize=${vmsize:-$DEF_VMSIZE}
chknum $vmsize && VMSIZE=$vmsize

# check video
[ "$video" ] || video=$DEF_VIDEO
[ "${video_models%%:$video:*}" == "$video_models" ] && Error "Invalid video device: $video"

# check destination path,
IMGPATH=${IMGPATH:-/var/lib/libvirt/images}
imgpath=${imgpath:-$IMGPATH}
[ -d $imgpath ] || Error "Destination directory $imgpath doesn't exist."

# check mac address
[ "$mac" ] || mac=`$SSH grep $vm /etc/ethers 2&gt;/dev/null | awk '{print $1}'`
[ "$mac" ] && chkMAC $mac

# OS and version

[ "$ver" ] && osver=" $ver"
[ "$os" ] && OSDESC=" ($os$osver)"
desc="$vm$OSDESC - vm guest on host $host"

# check os variant
Ver=${ver%%.*}
[ "$ostype" ] || ostype=$os$Ver
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$os$VER
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$(echo $os${OSTYPES##*$os} | sed 's| .*||')
[ "${OSTYPES%$ostype*}" == "$OSTYPES" -a $vflag -gt 0 ] && ostype=virtio26  
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$DEF_KERN  
[ "ostype" ] || ostype=generic  

# check number of disk
[ "$hds" == "1" ] || chknum $hds
[ $hds -gt 1 ] && VMSIZE=$(($vmsize * $hds))

i=0 
while [ $i -lt $hds ] ; do
	hd[$i]=${vm}_hd$i.img
	let i++
done

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if [ "$rflag" ] ; then
	virsh -c $hyp pool-info default | grep -q running || virsh -c $hyp pool-start default
	poolAvailableSize=$(virsh -c $hyp pool-info default | awk '/Available:/&&/GB/ { s=int($2 * 1024) ;  print s }')
	[ $(( ($VMSIZE + 1) * 1024)) -gt $poolAvailableSize ] && Error "Not enough space available on default pool" 
	if [ $hds -gt 1 ] ; then
		if [ $tflag -eq 0 ] ; then
			for hd in ${hd[li]} ; do
[/li]				echo " - creating raw volume $hd, please wait..."
				virsh -c $hyp vol-create-as default $hd ${vmsize}G
			done	
		fi
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk '{ printf " --disk vol=default/%s", $1}')
	else
		DISKOPTS=" --disk pool=default,size=$vmsize"
	fi
else
	# check if there is enough space to store image locally. 
	hyp=$DEF_CONNECT
	[ $(( ($VMSIZE + 1) * 1024)) -gt $(df -Pl -B $((1024*1024)) $imgpath | awk 'END { print $4 }') ] && Error "Not enough space available on $imgpath" 
	if [ $lflag -ge 1 ] ; then
		DISKOPTS=" --nodisk"
		[ $VIRTINST_VER -gt 5003 ] && BOOTOPTS=" --boot=cdrom"
	else
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk 'BEGIN {S='$vmsize' ; P="'"$imgpath"'"} ; {printf " --disk path=%s/%s,size=%s", P, $1, S}')
	fi
fi

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# networking 
gw=${GATEWAY:-$GW}

# which networking method to use
if [ $bflag -eq 1 -o "$DEF_NETWORK" == "bridge" ] ; then
	br=${br:-$DEF_BRIDGE}
	nic=${nic:-$DEF_NIC}
	ifconfig $nic &> /dev/null || Error "invalid network device: $nic"
	# create bridge on localhost if needed
	if [ "$rflag" ] ; then
		if [ $tflag -eq 0 ] ; then
			$SSH brctl show | grep -q "^$br[ 	]" || Error "bridge $br not present on host $host" 
		fi
	else
		if [ "x$CREATEBRIDGE" == "x1" -a $tflag -eq 0 ] ; then
			net=$(ifconfig $nic | awk '/inet addr/ { sub(/addr:/,"",$2) ; print $2 }')
			brctl show | grep -q "^$br[ 	]" && echo "bridge $br already set" || createBridge $br $nic $net 
		fi
	fi
	NETOPTS=" --network bridge=$br"
else
	# start virtual network (NAT) if needed
	if [ "x$STARTVIRBR" == "x1" ] ; then
		virsh -c $hyp net-list | grep -q 'active' || virsh -c $hyp net-start default
		NETOPTS=" --network network=default" 
	fi
fi
[ "$mac" ] && NETOPTS="$NETOPTS,mac=$mac"

case $os in
	openbsd) NETOPTS="$NETOPTS,model=e1000" ;;
	netbsd)  NETOPTS="$NETOPTS,model=ne2k_pci" ; ACPI=" --noacpi" ; ostype=openbsd4 ;;
	dflybsd) NETOPTS="$NETOPTS,model=e1000" ; ostype=freebsd7 ;;
	opensuse) ostype=virtio26 ;;
esac

[ "$ostype" ] && ostype=" --os-variant=$ostype"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# display 
[ $sflag -eq 1 ] && DISPLAYOPTS=" --sdl"
[ $aflag -ge 1 ] && audio=" --soundhw=$DEF_AUDIO"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if [ $tflag -ge 1 ] ; then
	printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm

" | sed 's|--|
--|g'
	[ "$DISTURL" ] && printf "iso URL: %s
" $DISTURL 
else
	virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm
fi

Version 2.3. Some minor bug fixes (like checking some more invalid input from user).

#! /bin/bash
#: Title       : vm-create
#: Date Created: Sat Jan 29 01:54:07 PST 2011 
#: Last Edit   : Sat Mar 12 09:21:59 PST 2011
#: Author      : please_try_again
#: Version     : 2.3
#: Description : install OS from the Internet or local iso in a kvm vm
# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
prg=$(basename $0)

# use the popup library if found
which popup &>/dev/null && source $(which popup)

# use popup functions if available
function Error { declare -F | grep -q error || exec echo "Error: $*" ; error "$*" ;}
function Warn  { declare -F | grep -q warn  && warn  "$*" || echo "Warning: $*" ;}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

which virt-install &> /dev/null || Error "virt-install not installed"
[ -r /etc/vm-create.cfg ] && source /etc/vm-create.cfg || Error "/etc/vm-create.cfg not found"
[ -r ~/.vm-create.cfg ] && source ~/.vm-create.cfg 

[ "x$(ps ax | grep libvirtd | awk '!/grep/ { print $1 }')" == "x" ] && Error "libvirtd is not running" 
VIRTINST_VER=$(virt-install --version | sed 's|^0||;s|\.||g')
LIVIRT_VER=$(libvirtd --version | sed 's|[^0-9]*\([0-9]\)|\1|;s|\.||g;s|^0||')

# recognized OS types to this day.
OSTYPES="debianetch debianlenny debiansqueeze \
fedora5 fedora6 fedora7 fedora8 fedora9 fedora10 fedora11 fedora12 fedora13 \
generic24 generic26 \
virtio26 \
mandriva2009 mandriva2010 \
mes5 mes5.1 \
rhel2.1 rhel3 rhel4 rhel5 rhel5.4 rhel6 \
sles10 sles11 \
ubuntuhardy ubuntuintrepid ubuntujaunty ubuntukarmic \
generic \
msdos \
netware4 netware5 netware6 \
opensolaris solaris10 solaris9 \
freebsd6 freebsd7 \
openbsd4 \
vista win2k win2k3 win2k8 win7 winxp winxp64"

[ $VIRTINST_VER -lt 5003 ] && Error "virt-install 0.500.3 or newer required"
[ $VIRTINST_VER -ge 5004 ] && { OSTYPES="$OSTYPES fedora14 ubuntulucid freebsd8" ; BOOTOPTS=" --boot=cdrom,hd,network";}
[ $VIRTINST_VER -ge 5005 ] && OSTYPES="$OSTYPES ubuntumaverick"
[ $LIVIRT_VER -ge 87 ] && Y="-- "

DEF_CONNECT=${VIRSH_DEFAULT_CONNECT_URI:-${DEF_HYPERVISOR}://$DEF_HOST/system}
DISPLAYOPTS=" --vnc --vnclisten=0.0.0.0"

# valid xen and qemu MAC addresses
qemu_mac="52:54:00:"
xen_mac="00:16:3e:"

# valid video adapters
video_models=":vga:vmvga:cirrus:xen:"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

function chknum {
	N=$(echo $1 | tr -d "[:alpha:]")
	[ "x$N" == "x$1" ] || Error "invalid numeric argument: $1" 
}

function chkMAC {
	Mac=$(echo ${1:0:9} | tr "[:upper:]" "[:lower:]")
	MAC=${HV}_mac ; MAC=${!MAC}
	[ "$Mac" == "$MAC" ] || Error "invalid $HV MAC address: $1"
	[ "$(echo ${1:9} | sed 's|[^:]*||g')" == "::" ] || Error "invalid $HV MAC address: $1"
	a=$(echo ${1:9} | sed 's|:||g;s|[^0-9a-fA-F]*||g')
	[ ${#1} -eq 17 -a ${#a} -eq 6 ] || Error "invalid $HV MAC address: $1"
}

function chkarg { [ "${1:0:1}" == "-" ] && Error "invalid argument: $1" ;}
function chkopt { [ "x$1" == "x" ] && Error "you should provide a vm name" ;}

function syntax {
	cat << EOFSYNTAX
usage:
   $prg vm name

options:
	-c --connect <host>    : remote host where to create the vm
	-r --ram <ram in MB>   : amount of ram (default 512MB)
	-a --arch <i386|amd64> : architecture (i386 or amd64)
	-o --os <os variant>   : os variant (example: fedora14, mandriva2010)
	-u --url <os>          : os (fedora, debian, ubuntu, opensuse, mandriva) 
	-i --iso <img.iso>     : iso image 
	-p --path <directory>  : where to store vm images
	-s --size <size>       : size of virtual machine in GB
	-V --virtio            : same as -o virtio26
	-v --video             : video device: cirrus, vga* or vmvga (for kvm)
	-R --release           : OS version to install (other than default)
	-S --sound             : enable sound device (ac97)
	-l --sdl               : use sdl display rather than vnc
	-L --live              : boot live CD
	-b --bridge <bridge>   : use network bridge (otherwise use DEF_NETWORK if defined or 'nat')
	-e --eth <nic>         : network interface to use for bridge (otherwise use DEF_NIC if defined or eth0)
	-x --xen               : use Xen hypervisor rather than kvm	
	-t --try               : just print but not execute the command
	-h --help              : show this help
	-d --disks             : number of virtual hard disks

recognized OS variants    :
`echo $OSTYPES | fmt`

available guest systems   :
$(awk -F "=" '/^[a-zA-Z0-9]*_url=|^[a-zA-Z0-9]*_iso=/ { sub(/_.*/,"",$1); print $1 }' /etc/vm-create.cfg | sort -u | tr "
" " ")
EOFSYNTAX
exit
}

function createBridge {
	[ "x$gw" == "x" ] && Error "no gateway defined"
	ping -q -c 1 -W 2 $gw > /dev/null || Error "gateway $gw is unreachable"
	echo  " - creating bridge $1"
	brctl addbr $1 
	ifconfig $2 0.0.0.0 promisc 
	brctl addif $1 $2
	ifconfig $1 inet $3 netmask $DEF_NETMASK
	brctl stp $br on
	route del default 2>/dev/null
	route add default gw $gw 2>/dev/null
}

function importPool {
	virsh -c $hyp pool-dumpxml $1 2>/dev/null | grep -q $2 && {
		virsh -c $hyp pool-info $1 | grep -q running || virsh -c $hyp pool-start $1
		virsh -c $hyp pool-refresh $1
		return
	}
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-destroy $1 2>/dev/null
	virsh -c $hyp pool-list --all | grep -q "\b$1\b" && virsh -c $hyp pool-undefine $1 2>/dev/null
	$SSH mkdir -m 600 $POOLPATH 2>/dev/null
	virsh -c $hyp pool-create-as $1 netfs $2 $ISOPATH -- -- $Y$POOLPATH
}

function getarch {
	case $1 in
	x86_64)
		case $os in
			centos|debian|dflybsd|freebsd|gentoo|netbsd|openbsd|ubuntu) echo amd64 ;;
			*) echo $1 ;;
		esac
	;;
	i386)
		case $os in
			mandriva|opensuse*) echo i586 ;;
			archlinux) echo i686 ;;
			gentoo) echo x86 ;;
			*) echo $1 ;;
		esac
	;;
	esac
}

function getiso {
	[ -d $ISOPATH ] && cd $ISOPATH || Error "$ISOPATH doesn't exist"
	IMG=$1 ; shift ; URL=$1
	ISO=$(basename $IMG .bz2) ; ISO=$(basename $ISO .tgz)
	if [ $tflag -eq 0 ] ; then
		if [ "$URL" ] ; then
			[ -f ./$ISO  ] || [ -f ./$IMG ] || wget -v $URL/$IMG
			[ -f ./$ISO ] || bunzip2 -v $IMG
		else
			[ -f $IMG ] || Error "File not found: $IMG"
			file $IMG | grep -q bootable || Error "$IMG is not a bootable iso image"
		fi
	fi 
	DIR=$(dirname $IMG)
	[ "$DIR" == "." ] && DIR=$(pwd)

	if [ "$rflag" ] ; then
		[ "$DIR" == "$ISOPATH" ] || Error "iso img $ISO not in shared pool $ISOPATH"
		[ $tflag -eq 0 ] && importPool $POOLNAME $HOSTNAME
		url=" --disk vol=$POOLNAME/$ISO,device=cdrom"
	else
		url=" --cdrom=$DIR/$ISO"
	fi
	cd - >/dev/null
}

# find out if we need to download a netinstall iso image
function geturl {
	ARCH=$(getarch $arch)
	URL=${1//ARCH/$ARCH} ; shift
	URL=${URL//VER/$1}
	if [ "$iflag" ] ; then
		imgs=($(links -dump $URL | awk '/dfly|netinst|mini|NET|boot|cd[-0-9]/&&/iso|iso.bz2/ { print $0}' | sed 's|.* \([^ ]*.iso[.bz2]*\).*|\1|;s|\.$||;s|^.*[]/]||' | sort -u))
		[ ${#imgs[li]} -eq 1 ] && img=${imgs[0]} || img=$(echo ${imgs[*]} | tr " " "[/li]" | grep $ARCH | tail -1)
		getiso $(basename $img) $URL
		DISTURL="$URL/$img"
	else
		url=" --location=$URL"
	fi
}

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

uflag=0 ; vflag=0 ; bflag=0 ; sflag=0 ; dflag=0 ; tflag=0 ; lflag=0 ; aflag=0 
url=""

HV=$DEF_HYPERVISOR
hds=1
# evaluate arguments
args=`getopt -q -u -o c:r:a:m:o:u:i:p:s:b:e:R:v:d:xSVlLht -l connect:,ram:,arch:,mac:,os:,url:,iso:,path:,size:,bridge:,eth:,release:,video:,disks:,xen,virtio,sdl,sound,live,help,try -- "$@"`

set -- $args

for i; do
	case "$i" in
		-c|--connect)  shift; chkarg $1 ; rhost="$1" ; shift ;;
		-r|--ram)      shift; chkarg $1 ; ram="$1" ; shift ;;
		-a|--arch)     shift; chkarg $1 ; arch="$1" ; shift ;;
		-m|--mac)  	   shift; chkarg $1 ; mac="$1" ; shift ;;
		-o|--os)       shift; chkarg $1 ; ostype="$1" ; shift ;;
		-i|--iso)      shift; chkarg $1 ; iso="$1" ; uflag=$(($uflag | 1)) ; shift ;;
		-u|--url)      shift; chkarg $1 ; os="$1" ; uflag=$(($uflag | 2)) ; shift ;;
		-p|--path)     shift; chkarg $1 ; imgpath="$1" ; shift ;;
		-s|--size)     shift; chkarg $1 ; vmsize="$1" ; shift ;;
		-b|--bridge)   shift; chkarg $1 ; br="$1"  ; bflag=1  ; shift ;;
		-e|--eth)  	   shift; chkarg $1 ; nic="$1" ; shift ;;
		-R|--release)  shift; chkarg $1 ; ver="$1" ; shift ;;
		-v|--video)    shift; chkarg $1 ; video="$1" ; shift ;;
		-d|--disks)    shift; chkarg $1 ; hds="$1" ; shift ;;
		-x|--xen)      XEN=xen ; shift ;;
		-V|--virtio)   vflag=1 ; shift ;;
		-S|--sound)    aflag=1 ; shift ;;
		-l|--sdl)  	   sflag=1 ; shift ;;
		-L|--live) 	   lflag=1 ; shift ;;
		-t|--try)      tflag=1 ; shift ;;
		-h|--help)     syntax ;;
		--) shift ;    chkopt $1 ; vm=$1 ; shift ;;
	esac
done

if [ $uflag -ge 3 ] ; then
	Error "options --url and --iso are mutually exclusive."
elif [ $uflag -le 0 ] ; then
	Error "No OS or iso image specified."
fi

# check remote host
[ "$rhost" ] && hyp=$HV+ssh://$rhost/system || { [ "$HV" == "xen" ] && hyp=$HV:///system || hyp=${DEF_CONNECT:-$HV:///system} ; }
DEF_RHOST=$(echo ${DEF_CONNECT##*://} | sed 's|/system||')
host=${rhost:-$DEF_RHOST} ; host=${host:-$HOSTNAME}
[ "$host" == "$HOSTNAME" ] || {
	ping -q -c 1 -W 2 $host &> /dev/null || Error "host $host is unreachable" 
	rflag=1 ; iflag=1 ; SSH="ssh root@$host" 
}

# check virtual machine
virsh -c $hyp dominfo $vm &> /dev/null && Error "Virtual machine $vm already exists."

# check ram
ram=${ram:-$DEF_RAM}
chknum $ram ; [ $ram -ge $MIN_RAM ] || Error "not enough RAM allocated: $ram MB"

# check architecture
arch=${arch:-$DEF_ARCH}
arch=$(echo $arch | sed 's|i[456]|i3|;s|amd|x86_|')
[ "$arch" == "i386" -o "$arch" == "x86_64" ] || Error "unsupported architecture: $arch"

# check OS version
if [ $uflag -ge 2 ] ; then
	url=${os}_url ; url=${!url}
	[ "$url" ] || { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	[ "$rflag" ] && { url=${os}_iso ; url=${!url} ; iflag=1 ;}
	VER=${os}_ver ; VER=${!VER} ; ver=${ver:-$VER}
	which links &>/dev/null || Error "links not found. Please install the links package first."
	[ "$url" ] && geturl $url $ver  || Error "invalid OS: $os"
# check iso image
elif [ $uflag -ge 1 ] ; then
	getiso $iso
fi

# check size of vm 
vmsize=${vmsize:-$DEF_VMSIZE}
chknum $vmsize && VMSIZE=$vmsize

# check video
[ "$video" ] || video=$DEF_VIDEO
[ "${video_models%%:$video:*}" == "$video_models" ] && Error "Invalid video device: $video"

# check destination path,
IMGPATH=${IMGPATH:-/var/lib/libvirt/images}
imgpath=${imgpath:-$IMGPATH}
[ -d $imgpath ] || Error "Destination directory $imgpath doesn't exist."

# check mac address
[ "$mac" ] || mac=`$SSH grep $vm /etc/ethers 2&gt;/dev/null | awk '{print $1}'`
[ "$mac" ] && chkMAC $mac

# OS and version

[ "$ver" ] && osver=" $ver"
[ "$os" ] && OSDESC=" ($os$osver)"
desc="$vm$OSDESC - vm guest on host $host"

# check os variant
Ver=${ver%%.*}
[ "$ostype" ] || ostype=$os$Ver
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$os$VER
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$(echo $os${OSTYPES##*$os} | sed 's| .*||')
[ "${OSTYPES%$ostype*}" == "$OSTYPES" -a $vflag -gt 0 ] && ostype=virtio26  
[ "${OSTYPES%$ostype*}" == "$OSTYPES" ] && ostype=$DEF_KERN  
[ "ostype" ] || ostype=generic  

# check number of disk
[ "$hds" == "1" ] || chknum $hds
[ $hds -gt 1 ] && VMSIZE=$(($vmsize * $hds))

i=0 
while [ $i -lt $hds ] ; do
	hd[$i]=${vm}_hd$i.img
	let i++
done

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

if [ "$rflag" ] ; then
	virsh -c $hyp pool-info default | grep -q running || virsh -c $hyp pool-start default
	poolAvailableSize=$(virsh -c $hyp pool-info default | awk '/Available:/&&/GB/ { s=int($2 * 1024) ;  print s }')
	[ $(( ($VMSIZE + 1) * 1024)) -gt $poolAvailableSize ] && Error "Not enough space available on default pool" 
	if [ $hds -gt 1 ] ; then
		if [ $tflag -eq 0 ] ; then
			for hd in ${hd[li]} ; do
[/li]				echo " - creating raw volume $hd, please wait..."
				virsh -c $hyp vol-create-as default $hd ${vmsize}G
			done	
		fi
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk '{ printf " --disk vol=default/%s", $1}')
	else
		DISKOPTS=" --disk pool=default,size=$vmsize"
	fi
else
	# check if there is enough space to store image locally. 
	hyp=$DEF_CONNECT
	[ $(( ($VMSIZE + 1) * 1024)) -gt $(df -Pl -B $((1024*1024)) $imgpath | awk 'END { print $4 }') ] && Error "Not enough space available on $imgpath" 
	if [ $lflag -ge 1 ] ; then
		DISKOPTS=" --nodisk"
		[ $VIRTINST_VER -gt 5003 ] && BOOTOPTS=" --boot=cdrom"
	else
		DISKOPTS=$(echo ${hd[li]} | tr " " "[/li]" | awk 'BEGIN {S='$vmsize' ; P="'"$imgpath"'"} ; {printf " --disk path=%s/%s,size=%s", P, $1, S}')
	fi
fi

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# networking 
gw=${GATEWAY:-$GW}

# which networking method to use
if [ $bflag -eq 1 -o "$DEF_NETWORK" == "bridge" ] ; then
	br=${br:-$DEF_BRIDGE}
	[ "${br#br*}" == "$br" ] && Error "invalid bridge device: $br"
	nic=${nic:-$DEF_NIC}
	ifconfig $nic &> /dev/null || Error "invalid network device: $nic"
	# create bridge on localhost if needed
	if [ "$rflag" ] ; then
		if [ $tflag -eq 0 ] ; then
			$SSH brctl show | grep -q "^$br[ 	]" || Error "bridge $br not present on host $host" 
		fi
	else
		if [ "x$CREATEBRIDGE" == "x1" -a $tflag -eq 0 ] ; then
			net=$(ifconfig $nic | awk '/inet addr/ { sub(/addr:/,"",$2) ; print $2 }')
			brctl show | grep -q "^$br[ 	]" && echo "bridge $br already set" || createBridge $br $nic $net 
		fi
	fi
	NETOPTS=" --network bridge=$br"
else
	# start virtual network (NAT) if needed
	if [ "x$STARTVIRBR" == "x1" ] ; then
		virsh -c $hyp net-list | grep -q 'active' || virsh -c $hyp net-start default
		NETOPTS=" --network network=default" 
	fi
fi
[ "$mac" ] && NETOPTS="$NETOPTS,mac=$mac"

case $os in
	openbsd) NETOPTS="$NETOPTS,model=e1000" ;;
	netbsd)  NETOPTS="$NETOPTS,model=ne2k_pci" ; ACPI=" --noacpi" ; ostype=openbsd4 ;;
	dflybsd) NETOPTS="$NETOPTS,model=e1000" ; ostype=freebsd7 ;;
	opensuse) ostype=virtio26 ;;
esac

[ "$ostype" ] && ostype=" --os-variant=$ostype"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# display 
[ $sflag -eq 1 ] && DISPLAYOPTS=" --sdl"
[ $aflag -ge 1 ] && audio=" --soundhw=$DEF_AUDIO"

# ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
if [ $tflag -ge 1 ] ; then
	printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm

" | sed 's|--|
--|g'
	[ "$DISTURL" ] && printf "iso URL: %s
" $DISTURL 
else
	virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm
fi

new /etc/vm-create.cfg now defaults to openSUSE 11.4
(Don’t forget to edit the paths!)

# system wide /etc/vm-create.cfg.
# variables  in this file can be ovrewritten in ~/.vm-create.cfg


# DEFINE THE VARIABLES IN THIS SECTIONS TO MATCH YOUR SYSTEM

IMGPATH=/misc/vm 	# where to put the vm images. 
ISOPATH=/misc/iso 	# where to save iso images
POOLNAME=install	# pollname for remote install
POOLPATH=/var/lib/libvirt/images/install

# your gateway (unless set in the environment variable GATEWAY) 
GW=192.168.101.1 		

# YOU MIGHT EDIT THE DEFAULTS BELOW

DEF_HYPERVISOR=qemu 	# qemu or xen
#DEF_HOST=				# default to localhost
DEF_RAM=512
DEF_CPUS=1
DEF_ARCH=i386
DEF_KERN=generic26
DEF_VIDEO=vga	 		# cirrus, vga or vmvga
DEF_AUDIO=default		# AC97 if the hypervisor supports it, otherwise it will be ES1370.
DEF_VMSIZE=10			# default size of virtual machines (10GB)


# network defaults
DEF_BRIDGE=br0
DEF_NIC=eth0
DEF_NAT=virbr0
DEF_NETWORK=nat	        # nat or bridge
DEF_NETMASK=0xffffff00	# should be fine for a class C network
CREATEBRIDGE=1			# create bridge if needed
STARTVIRBR=1			# start virtual network (NAT) if needed
MIN_RAM=64              # minimal amount of RAM for a vm

# You might change distros default versions
opensuse_ver=11.4
centos_ver=5
fedora_ver=14
ubuntu_ver=maverick
mandriva_ver=2010.2
debian_ver=squeeze
freebsd_ver=8.2
openbsd_ver=4.8
netbsd_ver=5.1
dflybsd_ver=2.8.2

# You might add/change ditros urls.
MIRROR="ftp://ftp.osuosl.org"

# mirrors
debian_mirror=$MIRROR
debiancd_mirror=$MIRROR
archlinux_mirror="$MIRROR/pub/archlinux"
centos_mirror="$MIRROR/pub/centos"
fedora_mirror="$MIRROR/pub/fedora"
gentoo_mirror="$MIRROR/pub/gentoo"
ubuntu_mirror="$MIRROR/pub/ubuntu"
#opensuse_mirror="$MIRROR/pub/opensuse"

# official sites 
freebsd_mirror="ftp://ftp.freebsd.org/pub/FreeBSD"
openbsd_mirror="ftp://ftp.openbsd.org/pub/OpenBSD"
netbsd_mirror="ftp://ftp.netbsd.org/pub/NetBSD"
dflybsd_mirror="ftp://ftp.dragonflybsd.org"
opensuse_mirror="http://download.opensuse.org"
mandriva_mirror="ftp://ftp.uwsg.indiana.edu"
#debian_mirror="ftp://ftp.debian.org"
#debiancd_mirror="htpp://cdimage.debian.org"
#archlinux_mirror="ftp://ftp.archlinux.org"
#fedora_mirror="http://download.fedoraproject.org/pub/fedora"
#gentoo_iso="http://distfiles.gentoo.org"
ubuntu_mirror="http://ca.archive.ubuntu.com/ubuntu"

debian_url="$debian_mirror/debian/dists/VER/main/installer-ARCH/"
fedora_url="$fedora_mirror/linux/releases/VER/Fedora/ARCH/os/"
mandriva_url="$mandriva_mirror/linux/mandrake/official/VER/ARCH/"
ubuntu_url="$ubuntu_mirror/dists/VER/main/installer-ARCH/"

archlinux_iso="$archlinux_mirror/iso/latest"
centos_iso="$centos_mirror/VER/isos/ARCH"
debian_iso="$debiancd_mirror/debian-cd/6.0.0/ARCH/iso-cd"
fedora_iso="$fedora_mirror/linux/releases/VER/Fedora/ARCH/iso"
gentoo_iso="$gentoo_mirror/releases/ARCH/current-iso"
mandriva_iso="$mandriva_mirror/linux/mandrake/official/VER/ARCH/install/images"
opensuse_iso="$opensuse_mirror/distribution/VER/iso"
ubuntu_iso="$ubuntu_mirror/dists/VER/main/installer-ARCH/current/images/netboot"
freebsd_iso="$freebsd_mirror/releases/ARCH/ISO-IMAGES/VER"
openbsd_iso="$openbsd_mirror/VER/ARCH"
netbsd_iso="$netbsd_mirror/iso/VER"
dflybsd_iso="$dflybsd_mirror/iso-images"

Any update to use something like those options on the scripts

–extra-args=‘console=tty0 console=ttyS0,115200n8 serial’
–graphics spice port=5930,listen=0.0.0.0,password=‘hiworld’

That one would be tricky to implement as an option because it contains spaces and a various number of arguments. But we could set the extra args in /etc/vm-create.cfg or the user’s ~/.vm-create.cfg in a variable that could be overwritten (or deleted) on the command line - before executing the script.

I noticed that the options --vnc and --sdl are deprecated now. And spice was not supported as I first wrote the script. It needs update indeed. I don’t have time to modify the options now. But one could set SPICE in the config file with the desired options. If this variable exists, it will be used instead of vnc. It’s a temporary hack.

Try adding these lines to ~/.vm-create.cfg (or system wide in /etc/vm-create.cfg):


# Additional kernel command line arguments to pass to the installer
KERNELARGS_DEF="console=tty0 console=ttyS0,115200n8 serial"

# uncomment the followin line to use spice (temporary hack)
SPICE=" --graphics spice port=5930,listen=0.0.0.0,password='hiworld'"

Apply this patch in /usr/bin (or in the directory where you have the script vm-create):


--- vm-create.orig      2012-05-15 02:29:12.607679734 -0700
+++ vm-create   2012-05-15 03:32:24.888987633 -0700
@@ -1,9 +1,9 @@
 #! /bin/bash
 #: Title       : vm-create
 #: Date Created: Sat Jan 29 01:54:07 PST 2011
-#: Last Edit   : Thu Apr  5 09:32:14 PDT 2012
+#: Last Edit   : Tue May 15 02:20:03 PDT 2012
 #: Author      : please_try_again (Agnelo de la Crotche)
-#: Version     : 2.5.1
+#: Version     : 2.5.2
 #: Description : install OS from the Internet or local iso in a kvm vm
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 prg=$(basename $0)
@@ -398,13 +398,17 @@
 
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 # display 
- $sflag -eq 1 ] && DISPLAYOPTS=" --sdl"
+ "$SPICE" ] && DISPLAYOPTS="$SPICE"
+ $sflag -eq 1 ] && DISPLAYOPTS=" --sdl" 
  $aflag -ge 1 ] && audio=" --soundhw=$DEF_AUDIO"
 
+# kernel arguments
+ "${KERNELARGS+x}" == "x" ] &&  -z "$KERNELARG" ] || KERNELARGS=${KERNELARGS:-$KERNELARGS_DEF}
+ "$KERNELARGS" ] && EXTRAOPTS=" --extra_args='$KERNELARGS'"
 # ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 if  $tflag -ge 1 ] ; then
-       printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm

" | sed 's|--|
--|g'
+       printf "
virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description=\"$desc\"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS}${EXTRAOPTS} --name=$vm

" | sed 's|--|
--|g'
         "$DISTURL" ] && printf "iso URL: %s
" $DISTURL 
 else
-       virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS} --name=$vm
+       virt-install --connect=$hyp --ram=$ram --arch=$arch --vcpus=$DEF_CPUS --video=$video${audio} --description="$desc"${ostype}${url}${ACPI}${BOOTOPTS}${DISKOPTS}${NETOPTS}${DISPLAYOPTS}${EXTRAOPTS} --name=$vm
 fi

This command should now print the following:


$  vm-create -t  -s 8 -S -b br0 -u opensuse oscar

virt-install 
--connect=qemu:///system 
--ram=1024 
--arch=i386 
--vcpus=1 
--video=vga 
--soundhw=default 
--description="oscar (opensuse 12.1) - vm guest on host neelix" 
--os-variant=virtio26 
--cdrom=/misc/iso/openSUSE-12.1-NET-i586.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/oscar_hd0.img,size=8 
--network bridge=br0 
--graphics spice port=5930,listen=0.0.0.0,password='hiworld' 
--extra_args='console=tty0 console=ttyS0,115200n8 serial' 
--name=oscar

iso URL: http://download.opensuse.org/distribution/12.1/iso/openSUSE-12.1-NET-i586.iso

To execute virt-install with thes options, just remove the option -t.

To modify or delete the extra args, set KERNELARGS either to another value or to an empty string. Example:


$ env KERNELARGS="" vm-create -t  -s 8 -S -b br0 -u opensuse oscar

virt-install 
--connect=qemu:///system 
--ram=1024 
--arch=i386 
--vcpus=1 
--video=vga 
--soundhw=default 
--description="oscar (opensuse 12.1) - vm guest on host neelix" 
--os-variant=virtio26 
--cdrom=/misc/iso/openSUSE-12.1-NET-i586.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/oscar_hd0.img,size=8 
--network bridge=br0 
--graphics spice port=5930,listen=0.0.0.0,password='hiworld' 
--name=oscar

iso URL: http://download.opensuse.org/distribution/12.1/iso/openSUSE-12.1-NET-i586.iso

or


$ env KERNELARGS="some options" vm-create -t  -s 8 -S -b br0 -u opensuse oscar

virt-install 
--connect=qemu:///system 
--ram=1024 
--arch=i386 
--vcpus=1 
--video=vga 
--soundhw=default 
--description="oscar (opensuse 12.1) - vm guest on host neelix" 
--os-variant=virtio26 
--cdrom=/misc/iso/openSUSE-12.1-NET-i586.iso 
--boot=cdrom,hd,network 
--disk path=/misc/vm/oscar_hd0.img,size=8 
--network bridge=br0 
--graphics spice port=5930,listen=0.0.0.0,password='hiworld' 
--extra_args='some options' 
--name=oscar

iso URL: http://download.opensuse.org/distribution/12.1/iso/openSUSE-12.1-NET-i586.iso


Apply this patch in /usr/bin (or in the directory where you have the script vm-create):

the patch seems to indicate there is a version 2.5.x, but the last version available on the page is 2.3.

is the script now available somewhere else ?

P

on Opensuse 12.3 i get this :

vi bin/vm-create testvm -r 2048 -a i386 -o winxp -i /dev/null
VIM - Vi IMproved 7.3 (2010 Aug 15)
Unknown option argument: "-a"
More info with: "vim -h"

I can’t seem to work out where in the script vi/vim is called/used.

p