#!/usr/bin/perl -w =head1 NAME xen-create-image - Easily create new Xen instances with networking and OpenSSH. =cut =head1 SYNOPSIS Help Options: --help Show the help information for this script. --manual Read the manual, and examples, for this script. --verbose Show useful debugging information. --version Show the version number and exit. Size / General options: --accounts Copy all non-system accounts to the guest image --boot Boot the new instance after creating it. --cache Cache .deb files on the host when installing the new guest with the debootstrap tool. --force Force overwriting existing images. This will remove existing images or LVM volumes which match those which are liable to be used by the new invocation. --fs Specify the filesystem type to use for the new guest. Valid choices are 'ext3', 'reiserfs', or 'xfs'. --image Specify whether to create "sparse" or "full" disk images. Full images are mandatory when using LVM, so this setting is ignored in that case. --initrd Specify the initial ramdisk If an image is specified it must exist. --kernel Set the path to the kernel to use for domU. This image must exist on the host system. --memory Setup the amount of memory allocated to the new instance. --passwd Ask for a root password during setup. This is done interactively. --role Run a specific role command post-install. These role scripts are discussed in the manpage later. --roledir Specify the directory which contains the role scripts. This defaults to /etc/xen-tools/role.d/ --size Set the size of the primary disk image. --swap Set the size of the swap partition. --noswap Do not create a swap partition. When this option is used the system will not have a swap entry added to its /etc/fstab file either. --ide Use IDE names for virtual devices (hda not sda) Installation options: --arch Pass the given architecture to debootstrap or rpm strap. This argument is ignored if you install with a different installation type. --copy Install a new image by copying a directory recursively, this is assumed to be a previously installed system. This is much faster than installing from scratch. --copy-cmd This allows you to specify the command actually used for installation when using "--copy" NOTE This option is *only* valid in the configuration file. --dist Specify the distribution you wish to install. --debootstrap Use debootstrap to install the guest system. --dist should be used to specify a distribution debootstrap understands. --mirror Setup the mirror to use when installing via debootstrap. --rpmstrap Use rpmstrap to install the guest distribution. --dist should be used to specify a distribution rpmstrap understands. --tar Install the new image by untarring the specified file. Similar to --copy this is signficantly faster than installing via rpmstrap or debootstrap. --tar-cmd This allows you to specify the command actually used for installation when using "--tar". NOTE This option is *only* valid in the configuration file. --template Specify which template file to use when creating the Xen configuration file. Networking options: --dhcp The guest will fetch its networking details via DHCP. --gateway Setup the network gateway for the new instance. --ip Setup the IP address of the machine, multiple IPs are allowed. When specifying more than one IP the first one is setup as the "system" IP, and the additional ones are added as aliases. Note that Xen 3.x supports a maximum of three IP addresses per guest. This option conflicts with --dhcp. --netmask Setup the netmask for the new instance. Mandatory options: --dir Specify where the output images should go. Subdirectories will be created for each guest If you do not wish to use loopback images specify --lvm or --evms. (These three options are mutually exclusive.) --lvm Specify the volume group to save images within. If you do not wish to use LVM specify --dir or --evms. (These three options are mutually exclusive.) --evms Specify the container to save images within, i.e. '--evms lvm2/mycontainer'. If you do not wish to use EVMS specify --dir or --lvm. (These three options are mutually exclusive.) --hostname Set the hostname of the new guest system. Ideally this will be fully-qualified since several of the hook scripts will expect to be able to parse a domain name out of it for various purposes. =cut =head1 NOTES This script is a simple wrapper around three external tools =over 8 =item B Install a new distribution. =item B Run a collection of hook scripts to customise the freshly installed system. =item B Create a configuration file in /etc/xen so that xm can create the new image. =back The result of invoking these three scripts, and some minor glue between them is a simple means of creating new Xen guest domains. =cut =head1 DESCRIPTION xen-create-image is a simple script which allows you to create new Xen instances easily. The new image will be given two volumes. These volumes will be stored upon the host as either loopback files, or LVM logical volumes: 1. An image for the systems root disk. 2. An image for the systems swap device. The new virtual installations will be configured with networking, have OpenSSH installed upon it, and have most of its basic files setup correctly. =cut =head1 CONFIGURATION To reduce the length of the command line each of the supported options may be specified inside a configuration file. The global configuration file read for options is: /etc/xen-tools/xen-tools.conf The configuration file may contain comments which begin with the hash '#' character. Otherwise the format is 'key = value'. A sample configuration file would look like this: =for example begin # # Output directory. Images are stored beneath this directory, one # subdirectory per hostname. # dir = /home/xen # # LVM users should disable the 'dir' setting above, and instead # specify the name of the volume group to use. # # lvm = myvolume # # EVMS users should disable the dir setting above and instead specify # a container. For example, if you have an lvm2 container named box, # put lvm2/box. This is how it is named in the evms interface. # # Warning... this has not been tested with anything but lvm2 but should # be generalizable. # # evms= lvm2/myvolume # # Disk and Sizing options. # size = 2Gb # Disk image size. image = full # Allocate the full disk size immediately. memory = 128Mb # Memory size swap = 128Mb # Swap size fs = ext3 # use EXT3 filesystems dist = sarge # Default distribution to install. # # Kernel options. # initrd = /boot/initrd.img-2.6.16-2-xen-686 kernel = /boot/vmlinuz-2.6.16-2-xen-686 # # Networking options. # gateway = 192.168.1.1 netmask = 255.255.255.0 # # Installation options. # # copy = /path/to/pristine/image debootstrap = 1 # rpmstrap = 1 # tar = /path/to/img.tar =for example end Using this configuration file a new image may be created with the following command: xen-create-image --hostname=vm03.my.flat --ip=192.168.1.201 This makes use of loopback images stored beneath /home/xen and will be installed via the debootstrap command. =cut =head1 XEN CONFIGURATION FILE Once a new image has been created an appropriate configuration file for Xen will be saved in the directory /etc/xen. The configuration file is built up using the template file /etc/xen-tools/xm.tmpl - which is a file processed via the Text::Template perl module. If you wish to modify the files which are generated please make your changes to that input file. Alternatively you can create multiple configuration files and specify the one to use with the --template option. =cut =head1 LOOPBACK EXAMPLES The following will create a 2Gb disk image, along with a 128Mb swap file with Debian Sarge setup and running via DHCP. xen-create-image --size=2Gb --swap=128Mb --dhcp \ --dir=/home/xen --hostname=vm01.my.flat This next example sets up a host which has the name 'vm02.my.flat' and IP address 192.168.1.200, with the gateway address of 192.168.1.1 xen-create-image --size=2Gb --swap=128Mb \ --ip=192.168.1.200 \ --netmask=255.255.255.0 --gateway=192.168.1.1 \ --dir=/home/xen --hostname=vm02.my.flat The directory specified for the output will be used to store the volumes which are produced. To avoid clutter each host will have its images stored beneath the specified directory, named after the hostname. For example the images created above will be stored as: $dir/domains/vm01.my.flat/ $dir/domains/vm01.my.flat/disk.img $dir/domains/vm01.my.flat/swap.img $dir/domains/vm02.my.flat/ $dir/domains/vm02.my.flat/disk.img $dir/domains/vm02.my.flat/swap.img The '/domains/' subdirectory will be created if necessary. =cut =head1 LVM EXAMPLE If you wish to use an LVM volume group instead of a pair of loopback images as shown above you can instead use the --lvm argument to specify one. xen-create-image --size=2Gb --swap=128Mb --dhcp \ --lvm=myvolumegroup --hostname=vm01.my.flat The given volume group will have two new logical volumes created within it: ${hostname}-swap ${hostname}-disk The disk image may be mounted, as you would expect, with the following command: mkdir -p /mnt/foo mount /dev/myvolumegroup/vm01.my.flat-disk /mnt/foo =cut =head1 EVMS EXAMPLE If you wish to use an EVMS storage container instead of a pair of loopback images as shown above you can instead use the --evms argument to specify one. The below example assumes an lvm2 container. xen-create-image --size=2Gb --swap=128Mb --dhcp \ --evms=lvm2/myvolumegroup --hostname=vm01.my.flat The given storage container will have two new EVMS volumes created within it: ${hostname}-swap ${hostname}-disk The disk image may be mounted, as you would expect, with the following command: mkdir -p /mnt/foo mount /dev/evms/vm01.my.flat-disk /mnt/foo =cut =head1 INSTALLATION METHODS The new guest images may be installed in several different ways: 1. Using the debootstrap command, which must be installed and present. 2. Using the rpmstrap command, which must be installed and present. 3. By copying an existing installation. 4. By untarring a file containing a previous installation. These different methods can be selected by either the command line arguments, or settings in the configuration file. Only one installation method may be specified at a time; they are mutually-exclusive. =cut =head1 INSTALLATION SPEEDUPS After performing your first installation you can customize it, or use it untouched, as a new installation source. By doing this you'll achieve a significant speedup, even above using the debootstrap caching support. There are two different ways you can use the initial image as source for a new image: 1. By tarring it up and using the tar-file as an installation source. 2. By mounting the disk image of the first system and doing a literal copy. Tarring up a pristine, or customised, image will allow you to install with a command such as: xen-create-image --size=2Gb --swap=128Mb --dhcp \ --lvm=myvolumegroup --hostname=vm01.my.flat \ --tar=/path/to/tar.file.tar The advantage of the tarfile approach is that you'll not need to keep a disk image mounted if you were to use the --copy argument to create a new image using the old one as source: xen-create-image --size=2Gb --swap=128Mb --dhcp \ --lvm=myvolumegroup --hostname=vm01.my.flat \ --copy=/path/to/copy/from =cut =head1 DEBOOTSTRAP CACHING When installing new systems with the debootstrap tool there is a fair amount of network overhead. To minimize this the .deb files which are downloaded into the new instance are cached by default upon the host, in the directory /var/cache/apt/archives. When a new image is created these packages are copied into the new image - before the debootstrap process runs - this should help avoid expensive network reading. If you wish to clean the cache upon the host you may do so with apt-get, as you'd expect: apt-get clean (This feature can be disabled with the command line flag --cache=no, or by the matching setting in the configuration file.) =cut =head1 ROLES Currently there are some roles scripts included which work for the Debian Sarge and Etch distrubtions only. They are included primarily as examples of the kind of things you could accomplish. The supplied scripts are: =over 8 =item builder Setup the new virtual images with commonly used packages for rebuilding Debian packages from their source. =item gdm Install an X11 server, using VNC and GDM =item minimal Customise the generated images to remove some packages. =item xdm Install an X11 server, using VNC and XDM =back If you'd like to include your own role scripts you'll need to create a file in /etc/xen-tools/role.d, and then specify the name of that file with "--role=filename". For example the script /etc/xen-tools/role.d/gdm would be used by executing with "--role=gdm". Role scripts are invoked with a single argument - the directory where the installed system is mounted. =cut =head1 THE SKELETON DIRECTORY Any files present in the directory /etc/xen-tools/skel will be copied across to each new guest image. The role of this directory is analogous to the /etc/skel directory. A typical use for this would be to copy a public key across to each new system. You could do this by running: =for example start mkdir -p /etc/xen-tools/skel/root/.ssh chmod -R 700 /etc/xen-tools/skel/root cp /root/.ssh/id_rsa.pub /etc/xen-tools/skel/root/.ssh/authorized_keys2 chmod 644 /etc/xen-tools/skel/root/.ssh/authorized_keys2 =for example cut =head1 AUTHOR Steve -- http://www.steve.org.uk/ $Id: xen-create-image,v 1.100 2006-10-12 23:08:21 steve Exp $ =cut =head1 LICENSE Copyright (c) 2005-2006 by Steve Kemp. All rights reserved. This module is free software; you can redistribute it and/or modify it under the same terms as Perl itself. The LICENSE file contains the full text of the license. =cut use strict; use English; use Env; use File::Temp qw/ tempdir /; use Getopt::Long; use Pod::Usage; # # Configuration values read initially from the global configuration # file, then optionally overridden by the command line. # my %CONFIG; # # Global variable containing the temporary file where our image # is mounted for installation purposes. # # Why is this here? # # Well it makes sure that the magic "END" section can unmount it # if there are errors. # # my $MOUNT_POINT = undef; # # Release number. # my $RELEASE = '2.7'; # # Setup default options. # setupDefaultOptions(); # # Read the global configuration file. # readConfigurationFile( "/etc/xen-tools/xen-tools.conf" ); # # Parse the command line arguments. # parseCommandLineArguments(); # # Check the environment - after parsing arguments. # # This is required so that the "--help" flag will work even if our support # scripts are not installed. # checkSystem(); # # Ensure we're started by root at this point. This is required # to make sure we can create new LVM volumes, or mount loopback images. # testRootUser(); # # Check our arguments # checkArguments(); # # Make sure we have a log directory # mkdir( "/var/log/xen-tools", 0777 ) if ( ! -d "/var/log/xen-tools" ); # # Trash any existing for this run logfile. # open( TRASH, ">", "/var/log/xen-tools/$CONFIG{'hostname'}.log" ); print TRASH ""; close(TRASH); # # Check we have binaries installed which we expect to use. # checkBinariesPresent(); # # Show a summery of what we're going to do. # showSummery(); # # Create and format the images if we're using loopback filesystems. # if ( $CONFIG{'dir'} ) { # # Test to see if "loop" module is loaded. This is probably # not required, except for paranoia. # testLoopbackModule(); # # Create disk + swap images. # createLoopbackImages(); } elsif ( $CONFIG{'lvm'} ) { # # Create our LVM partitions. # createLVMBits(); } elsif ( $CONFIG{'evms'} ) { # # Create our evms partitions. # createEVMSBits(); } else { # Can't happen we didn't get an installation type. logprint( "Error: No recognised installation type.\n" ); exit; } # # Mount the image. # mountImage(); # # Call xt-install-image to perform the actual install. # installSystem(); # # Make sure the installation succeeded. # if ( ! -x $MOUNT_POINT . "/bin/ls" ) { logprint( "System installation failed. Aborting\n"); exit; } # # Call xt-customise-image to setup networking, and run any # distributuion-specific hooks. # runCustomisationHooks(); # # Run the user specified role script, if any. # runRoleScript(); # # Create the Xen configuration file. # runXenConfigCreation(); # # Setup the password if the user wanted that. # setupRootPassword() if ( $CONFIG{'passwd'} ); # # Report success. # logprint( "All done\n"); # # If we're supposed to start the new instance do so - note here we # have to unmount the image first. # if ( $CONFIG{'boot'} ) { # # Unmount the image and any subsequent mounts. # unMountImage( $MOUNT_POINT ); # # Mark us as unmounted. # $MOUNT_POINT = undef; # # Start the image # my $pid = fork(); if ( $pid ) { # Parent. exit; } else { # Child. system( "$CONFIG{'xm'} create $CONFIG{'hostname'}.cfg >/dev/null 2>/dev/null" ); logprint( "Started new Xen instance.\n" ); } } # # Finished. # exit; =begin doc Test that this system is fully setup for the new xen-create-image script. This means that the the companion scripts xt-* are present on the host and executable. =end doc =cut sub checkSystem { my @required = qw ( /xt-customize-image xt-install-image xt-create-xen-config / ); foreach my $bin ( @required ) { if ( ! -x "/usr/bin/" . $bin ) { logprint("The script '$bin' was not found.\n"); logprint( "Aborting\n\n" ); exit; } } } =begin doc Setup the default options we'd expect into our global configuration hash. =end doc =cut sub setupDefaultOptions { # # Paths and files. # $CONFIG{'dir'} = ''; $CONFIG{'xm'} = '/usr/sbin/xm'; $CONFIG{'kernel'} = '/boot/vmlinuz-2.6.16-1-xen-686'; $CONFIG{'initrd'} = ''; # # Sizing options. # $CONFIG{'memory'} = '96Mb'; $CONFIG{'size'} = '2000Mb'; $CONFIG{'swap'} = '128M'; $CONFIG{'cache'} = 'yes'; $CONFIG{'image'} = 'sparse'; # # Misc. options. # $CONFIG{'mirror'} = 'http://ftp.us.debian.org/debian'; $CONFIG{'arch'} = ''; $CONFIG{'dist'} = 'sarge'; $CONFIG{'fs'} = 'ext3'; $CONFIG{'force'} = 0; $CONFIG{'template'} = ''; $CONFIG{'roledir'} = '/etc/xen-tools/role.d'; # # Installation methods # $CONFIG{'rpmstrap'} = 0; $CONFIG{'debootstrap'} = 0; $CONFIG{'copy'} = ''; $CONFIG{'tar'} = ''; # # The program to run to create a filesystem. # # NOTE: These commands end in a trailing slash. The last parameter is # added as the loopback file/LVM volume to create the fs on.... # # NOTE 2: Each of these scripts will "force" the creation of a new # filesystem, even if it exists. This script must detect # prior existance itself. # $CONFIG{'make_fs_ext3'} = '/sbin/mkfs.ext3 -F '; $CONFIG{'make_fs_xfs'} = '/sbin/mkfs.xfs -f -d name='; $CONFIG{'make_fs_reiserfs'} = '/sbin/mkfs.reiserfs -f -q '; # # Flags to pass to "mount" to mount our image. # # NOTE: Kinda redundent and may go away since '-t auto' should do # the right thing. # $CONFIG{'mount_fs_ext3'} = '-t ext3'; $CONFIG{'mount_fs_xfs'} = '-t xfs'; $CONFIG{'mount_fs_reiserfs'} = '-t reiserfs'; } =begin doc read the global configuration file /etc/xen-tools/xen-tools.conf =end doc =cut sub readConfigurationFile { my ($file) = ( @_ ); # Don't read the file if it doesn't exist. return if ( ! -e $file ); my $line = ""; open( FILE, "<", $file ) or die "Cannot read file '$file' - $!"; while (defined($line = ) ) { chomp $line; if ($line =~ s/\\$//) { $line .= ; redo unless eof(FILE); } # Skip lines beginning with comments next if ( $line =~ /^([ \t]*)\#/ ); # Skip blank lines next if ( length( $line ) < 1 ); # Strip trailing comments. if ( $line =~ /(.*)\#(.*)/ ) { $line = $1; } # Find variable settings if ( $line =~ /([^=]+)=([^\n]+)/ ) { my $key = $1; my $val = $2; # Strip leading and trailing whitespace. $key =~ s/^\s+//; $key =~ s/\s+$//; $val =~ s/^\s+//; $val =~ s/\s+$//; # Store value. $CONFIG{ $key } = $val; } } close( FILE ); } =begin doc Parse the command line arguments this script was given. =end doc =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; my $VERSION = 0; # # We record the installation method here because we want # to ensure that we allow the method supplied upon the command line # to overwrite the one we might have ready read from the configuration # file. # my %install; $install{'debootstrap'} = 0; $install{'rpmstrap'} = 0; $install{'evms'} = undef; $install{'copy'} = undef; $install{'tar'} = undef; $install{'dir'} = undef; $install{'lvm'} = undef; # # Parse options. # GetOptions( # Mandatory "dist=s", \$CONFIG{'dist'}, # Size options. "size=s", \$CONFIG{'size'}, "swap=s", \$CONFIG{'swap'}, "noswap", \$CONFIG{'noswap'}, "image=s", \$CONFIG{'image'}, "memory=s", \$CONFIG{'memory'}, # Locations "dir=s", \$install{'dir'}, "evms=s", \$install{'evms'}, "kernel=s", \$CONFIG{'kernel'}, "initrd=s", \$CONFIG{'initrd'}, "mirror=s", \$CONFIG{'mirror'}, "lvm=s", \$install{'lvm'}, # Networking options "dhcp", \$CONFIG{'dhcp'}, "gateway=s", \$CONFIG{'gateway'}, "hostname=s", \$CONFIG{'hostname'}, "ip=s@", \$CONFIG{'ip'}, "mac=s", \$CONFIG{'mac'}, "netmask=s", \$CONFIG{'netmask'}, "p2p=s", \$CONFIG{'p2p'}, # Exclusive # # NOTE: We set the local variable here, not the global. # "copy=s", \$install{'copy'}, "copy-cmd=s", \$CONFIG{'copy-cmd'}, # NOP - IGNORED. "debootstrap", \$install{'debootstrap'}, "rpmstrap", \$install{'rpmstrap'}, "tar=s", \$install{'tar'}, "tar-cmd=s", \$CONFIG{'tar-cmd'}, # NOP - IGNORED. # Misc. options "accounts", \$CONFIG{'accounts'}, "arch=s", \$CONFIG{'arch'}, "fs=s", \$CONFIG{'fs'}, "boot", \$CONFIG{'boot'}, "cache=s", \$CONFIG{'cache'}, "ide", \$CONFIG{'ide'}, "passwd", \$CONFIG{'passwd'}, "role=s", \$CONFIG{'role'}, "roledir=s", \$CONFIG{'roledir'}, "force", \$CONFIG{'force'}, "template=s", \$CONFIG{'template'}, # Help options "debug", \$CONFIG{'verbose'}, "help", \$HELP, "manual", \$MANUAL, "verbose", \$CONFIG{'verbose'}, "version", \$VERSION ); pod2usage(1) if $HELP; pod2usage(-verbose => 2 ) if $MANUAL; if ( $VERSION ) { my $REVISION = '$Revision: 1.100 $'; if ( $REVISION =~ /1.([0-9.]+) / ) { $REVISION = $1; } logprint( "xen-create-image release $RELEASE - CVS: $REVISION\n" ); exit; } # # If we have had one of the local installation methods specified, # and *only* one of them the we'll reset the global option(s) which # came from the configuration file. # # Count the number which wer supplied # my $count = 0; foreach my $key ( qw/debootstrap rpmstrap copy tar/ ) { if ( $install{$key} ) { $count += 1; } } # # If we had exactly one specified then we can proceed. # if ( $count == 1 ) { foreach my $key ( qw/debootstrap rpmstrap copy tar/ ) { $CONFIG{$key} = $install{$key}; } } elsif ( $count > 1 ) { my $err =< Mb for the disk image size, and swap size. # if ( $disk_size =~ /^(\d+)Gb*$/i ) { $disk_size = $1 * 1024 . "M"; } if ( $swap_size =~ /^(\d+)Gb*$/i ) { $swap_size = $1 * 1024 . "M"; } # # Final adjustments to sizing. # $disk_size =~ s/Mb*$/k/i; if ( $swap_size =~ /^(\d+)Mb*$/i ) { $swap_size = $1; } # Use dd to create the swap # unless( $CONFIG{'noswap'} ) { logprint( "\nCreating swap image: $swap\n" ); my $swap_cmd = "/bin/dd if=/dev/zero of=$swap bs=1024k count=$swap_size"; runCommand( $swap_cmd ); logprint( "Done\n" ); } # # Use dd to create the disk image. # logprint( "\nCreating disk image: $disk\n" ); my $image_cmd; if ( $CONFIG{'image'} eq "sparse" ) { $CONFIG{'verbose'} && logprint( "Creating sparse image\n" ); $image_cmd = "/bin/dd if=/dev/zero of=$disk bs=$disk_size count=0 seek=1024"; } else { $CONFIG{'verbose'} && logprint( "Creating full-sized image\n" ); $image_cmd = "/bin/dd if=/dev/zero of=$disk bs=$disk_size count=1024"; } runCommand( $image_cmd ); logprint( "Done\n" ); # # Finally create the filesystem + swap # createFilesystem( $disk ); createSwap( $swap ) unless( $CONFIG{'noswap'} ); } =begin doc This function is responsible for creating two new logical volumes within a given LVM volume group. =end doc =cut sub createLVMBits { # The two volumes we will need to use.. my $disk = $CONFIG{'hostname'} . "-disk" ; my $swap = $CONFIG{'hostname'} . "-swap" ; # # Check whether the disk volume exists already, and if so abort # unless '--force' is specified. # my $lvm_disk = "/dev/$CONFIG{'lvm'}/$CONFIG{'hostname'}-disk"; if ( -e $lvm_disk ) { # Delete if forcing if ( $CONFIG{'force'} ) { logprint( "Removing $lvm_disk - since we're forcing the install\n" ); runCommand( "lvremove --force $lvm_disk" ); } else { logprint( "The LVM disk image already exists. Aborting.\n" ); logprint( "Specify '--force' to delete and recreate\n" ); exit; } } # # Check whether the swap volume exists already, and if so abort # unless '--force' is specified. # my $lvm_swap = "/dev/$CONFIG{'lvm'}/$CONFIG{'hostname'}-swap"; if ( -e $lvm_swap ) { # Delete if forcing if ( $CONFIG{'force'} ) { logprint( "Removing $lvm_swap - since we're forcing the install\n" ); runCommand( "lvremove --force $lvm_swap" ); } else { logprint( "The LVM swap image already exists. Aborting.\n" ); logprint( "Specify '--force' to delete and recreate\n" ); exit; } } # # Modify the sizes to something reasonable # my $disk_size = $CONFIG{'size'}; my $swap_size = $CONFIG{'swap'}; # # Convert Gb -> Mb for the disk image size, and swap size. # if ( $disk_size =~ /^(\d+)Gb*$/i ) { $disk_size = $1 * 1024 . "M"; } if ( $swap_size =~ /^(\d+)Gb*$/i ) { $swap_size = $1 * 1024 . "M"; } # # Final adjustments to sizing. # $disk_size =~ s/Mb*$/k/i; if ( $swap_size =~ /^(\d+)Mb*$/i ) { $swap_size = $1; } # # The commands to create the volumes. # my $disk_cmd = "/sbin/lvcreate $CONFIG{'lvm'} -L $CONFIG{'size'}M -n $disk"; my $swap_cmd = "/sbin/lvcreate $CONFIG{'lvm'} -L $CONFIG{'swap'} -n $swap"; # # Create the volumes # runCommand( $disk_cmd ); runCommand( $swap_cmd ) unless( $CONFIG{'noswap'} ); # # Initialise the partitions with the relevant filesystem. # createFilesystem( "/dev/$CONFIG{'lvm'}/$CONFIG{'hostname'}-disk" ); createSwap( "/dev/$CONFIG{'lvm'}/$CONFIG{'hostname'}-swap" ) unless ( $CONFIG{'noswap'} ); } =begin doc This function is responsible for creating two new logical volumes within a given EVMS container group (which at the moment is either LVM or LVM2), but should be compatible with any further extentions of evms. =end doc =cut sub createEVMSBits { # The two volumes we will need to use.. my $disk = $CONFIG{'hostname'} . "-disk" ; my $swap = $CONFIG{'hostname'} . "-swap" ; # # Check whether the disk volume exists already, and if so abort # unless '--force' is specified. This is two steps with evms, # because two things need to be checked, the volume and the object. # # Check whether the EVMS volume already exists, abort unless '--force' is specified. my $evms_volume_disk = "/dev/evms/$CONFIG{'hostname'}-disk"; if ( -e $evms_volume_disk ) { # Delete if forcing if ( $CONFIG{'force'} ) { logprint( "Removing $evms_volume_disk - since we're forcing the install\n" ); runCommand( "echo Delete : $evms_volume_disk | evms" ); } else { logprint( "The EVMS volume $evms_volume_disk already exists. Aborting.\n" ); logprint( "Specify '--force' to delete and recreate\n" ); exit; } } # # Check whether the EVMS object exists, abort unless '--force' # is specified. # # Note: $evms_object_disk is not specified directly as a device # my $evms_object_disk = "$CONFIG{'evms'}/$CONFIG{'hostname'}-disk"; if ( -e $evms_object_disk ) { # Delete if forcing if ( $CONFIG{'force'} ) { logprint( "Removing $evms_object_disk - since we're forcing the install\n" ); runCommand( "echo Delete : $evms_object_disk | evms" ); } else { logprint( "The EVMS object $evms_object_disk already exists. Aborting.\n" ); logprint( "Specify '--force' to delete and recreate\n" ); exit; } } # # Check whether the swap object and EVMS volume exists already, and # if so abort unless '--force' is specified. # # # Check whether the EVMS volume for swap already exists, abort # unless '--force' is specified. # my $evms_volume_swap = "/dev/evms/$CONFIG{'hostname'}-swap"; if ( -e $evms_volume_swap ) { # Delete if forcing if ( $CONFIG{'force'} ) { logprint( "Removing $evms_volume_swap - since we're forcing the install\n" ); runCommand( "echo Delete : $evms_volume_swap | evms" ); } else { logprint( "The EVMS volume $evms_volume_swap image already exists. Aborting.\n" ); logprint( "Specify '--force' to delete and recreate\n" ); exit; } } # Check whether the evms object for swap exists, abort unless '--force' is specified. # note that $evms_object_swap is not specified directly as a device my $evms_object_swap = "$CONFIG{'evms'}/$CONFIG{'hostname'}-swap"; if ( -e $evms_object_swap ) { # Delete if forcing if ( $CONFIG{'force'} ) { logprint( "Removing $evms_object_swap - since we're forcing the install\n" ); runCommand( "echo Delete : $evms_object_swap | evms" ); } else { logprint( "The EVMS object $evms_object_swap image already exists. Aborting.\n" ); logprint( "Specify '--force' to delete and recreate\n" ); exit; } } # # Modify the sizes to something reasonable # my $disk_size = $CONFIG{'size'}; my $swap_size = $CONFIG{'swap'}; # # Convert Gb -> Mb for the disk image size, and swap size. # if ( $disk_size =~ /^(\d+)Gb*$/i ) { $disk_size = $1 * 1024 . "M"; } if ( $swap_size =~ /^(\d+)Gb*$/i ) { $swap_size = $1 * 1024 . "M"; } # # Final adjustments to sizing. # $disk_size =~ s/Mb*$/k/i; if ( $swap_size =~ /^(\d+)Mb*$/i ) { $swap_size = $1; } # # The commands to create the objects and volumes. # # create the object # my $disk_cmd_object = "echo allocate : $CONFIG{'evms'}/Freespace, size=$CONFIG{'size'}, name=$disk | evms"; # # these will be piped to evms, but gotta check it first ## /sbin/lvcreate $CONFIG{'evms'} -L $CONFIG{'size'}M -n $disk"; #create the EVMS volume # my $disk_cmd_volume = "echo create : Volume, $CONFIG{'evms'}/$disk, name=$disk | evms"; ##/sbin/lvcreate $CONFIG{'evms'} -L $CONFIG{'swap'} -n $swap"; #repeat the same steps for the swap partition my $swap_cmd_object = "echo allocate : $CONFIG{'evms'}/Freespace, size=$CONFIG{'swap'}, name=$swap | evms"; # these will be piped to evms, but gotta check it first ## /sbin/lvcreate $CONFIG{'evms'} -L $CONFIG{'size'}M -n $disk"; #create the EVMS volume my $swap_cmd_volume = "echo create : Volume, $CONFIG{'evms'}/$swap, name=$swap | evms"; ##/sbin/lvcreate $CONFIG{'evms'} -L $CONFIG{'swap'} -n $swap"; # # Create the volumes # runCommand( $disk_cmd_object ); runCommand( $disk_cmd_volume ); runCommand( $swap_cmd_object ) unless( $CONFIG{'noswap'} ); runCommand( $swap_cmd_volume ) unless( $CONFIG{'noswap'} ); # # Initialise the partitions with the relevant filesystem. # createFilesystem( "/dev/evms/$CONFIG{'hostname'}-disk" ); createSwap( "/dev/evms/$CONFIG{'hostname'}-swap" ) unless ( $CONFIG{'noswap'} ); } =begin doc Format the given image in the users choice of filesystem. =end doc =cut sub createFilesystem { my( $image ) = ( @_ ); # # We have the filesystem the user wanted, make sure that the # binary exists. # my $command = $CONFIG{ "make_fs_" . $CONFIG{'fs'} }; # # Split the command into "binary" + "args". Make sure that # the binary exists and is executable. # if ( $command =~ /([^ ]+) (.*)$/ ) { my $binary = $1; my $args = $2; if ( ! -x $binary ) { logprint( "The binary '$binary' required to create the filesystem $CONFIG{'fs'} is missing\n" ); exit; } } else { logprint( "The filesystem creation hash is bogus for filesystem : $CONFIG{'fs'}\n" ); exit; } # # OK we have the command and the filesystem. Create it. # logprint( "\nCreating $CONFIG{'fs'} filesystem on $image\n" ); $command .= $image; runCommand( $command ); logprint( "Done\n" ); } =begin doc Create the swap filesystem on the given device. =end doc =cut sub createSwap { my ( $path ) = ( @_ ); runCommand( "/sbin/mkswap $path" ); } =begin doc Mount the loopback disk image into a temporary directory. Alternatively mount the relevant LVM volume instead. =end doc =cut sub mountImage { # # Determine what we're to mount # my $image; if ( $CONFIG{'lvm'} ) { $image = "/dev/" . $CONFIG{'lvm'} . "/" . $CONFIG{'hostname'} . '-disk'; } elsif ( $CONFIG{'evms'} ) { $image = "/dev/evms/" . $CONFIG{'hostname'} . '-disk'; } elsif ( $CONFIG{'dir'} ) { $image = $CONFIG{'dir'} . '/domains/' . $CONFIG{'hostname'} . "/disk.img" ; } else { logprint( "I don't know what to mount!\n" ); logprint( "Please specify '--dir' or '--lvm' or '--evms'\n" ); exit; } # # Create a temporary mount-point to use for the image/volume. # $MOUNT_POINT = tempdir( CLEANUP => 1 ); # # Lookup the correct arguments to pass to mount. # my $mount_cmd; my $mount_type = $CONFIG{'mount_fs_' . $CONFIG{'fs'} }; # # LVM partition # if ( $CONFIG{'lvm'} ) { $mount_cmd = "mount $mount_type $image $MOUNT_POINT"; } elsif ( $CONFIG{'evms'} ) { $mount_cmd = "mount $mount_type $image $MOUNT_POINT"; } else { $mount_cmd = "mount $mount_type -o loop $image $MOUNT_POINT"; } runCommand( $mount_cmd ); } =begin doc Install the system, by invoking the xt-install-system script. The script will be given the appropriate arguments from our environment. This means that it will be given one of "--debootstrap", "--rpmstrap", "--tar", and "--copy". There will also be other options passed on as appropriate. =end doc =cut sub installSystem { logprint( "\nInstalling your system with " ); # # Basic command # my $cmd = "/usr/bin/xt-install-image --hostname=$CONFIG{'hostname'} --location=$MOUNT_POINT --dist=$CONFIG{'dist'}"; # # Add on the current cache setting # $cmd .= " --cache=$CONFIG{'cache'}" if length( $CONFIG{'cache'} ); # # Installation method # if ( $CONFIG{'copy'} ) { $cmd .= " --copy=$CONFIG{'copy'}"; logprint( "copy from $CONFIG{'copy'}\n" ); } if ( $CONFIG{'debootstrap'} ) { $cmd .= " --debootstrap"; $cmd .= " --mirror=$CONFIG{'mirror'}"; logprint( "debootstrap mirror $CONFIG{'mirror'}\n" ); } if ( $CONFIG{'rpmstrap'} ) { $cmd .= " --rpmstrap"; logprint( "rpmstrap\n"); } if ( $CONFIG{'tar'} ) { $cmd .= " --tar=$CONFIG{'tar'}"; logprint( "tarfile $CONFIG{'tar'}\n" ); } # # Propogate --verbose # if ( $CONFIG{'verbose'} ) { $cmd .= " --verbose"; } # # Propogate --arche # if ( $CONFIG{'arch'} ) { $cmd .= " --arch=$CONFIG{'arch'}"; } # # Run the command. # runCommand( $cmd ); logprint( "Done\n" ); } =begin doc Run the xt-customise-system script to customize our fresh installation. Before we do this we must pass all the relevent options into our environment. =end doc =cut sub runCustomisationHooks { # # Setup the environment for the child processes. # foreach my $key ( keys %CONFIG ) { if ( defined( $CONFIG{$key} ) ) { $ENV{$key} = $CONFIG{$key}; } } # # Now update the environment for each defined IP address. # these are handled specially since we use arrays. # # Remove the value we set above. delete $ENV{'ip'}; # # Setup a seperate ip$count value for each IP address. # my $ips = $CONFIG{'ip'}; my $count = 1; foreach my $i ( @$ips ) { $ENV{'ip' . $count } = $i; $count += 1; } $ENV{'ip_count'} = ($count - 1); # # Now show the environment the children get # if ( $CONFIG{'verbose'} ) { logprint( "Customization Script Environment:\n" ); logprint( "---------------------------------\n" ); foreach my $key ( sort keys %ENV ) { logprint( "\t'" . $key . "' = '" . $ENV{$key} . "'\n" ); } } # # Actually run the appropriate hooks # my $customize = "xt-customize-image --dist=$CONFIG{'dist'} --location=$MOUNT_POINT"; if ( $CONFIG{'verbose'} ) { $customize .= " --verbose"; } logprint( "\nRunning hooks\n" ); runCommand( $customize ); logprint( "Done\n" ); } =begin doc If the user specified a role for the new instance then execute it. =end doc =cut sub runRoleScript { my $roleDir = $CONFIG{'roledir'}; my $role = $CONFIG{'role'}; if ( !defined( $role ) ) { logprint( "\nNo role script specified. Skipping\n" ); return; } # # The complete path to the role script # my $file = $roleDir . "/" . $role; if ( -x $file ) { logprint( "\nRunning role script '$file' for role '$role'\n" ); } else { logprint( "\nRole script not executable : $file for role '$role'\n" ); logprint( "Ignoring\n" ); return; } # # Our environment is already setup because of the call to # runCustomisationHooks. # # We just need to run the script with the mountpoint as its # single argument. # runCommand( $file . " " . $MOUNT_POINT ); logprint( "Done\n" ); } =begin doc Create the Xen configuration file. Note that we don't need to do any setup for the environment since we did this already before running the hook scripts. =end doc =cut sub runXenConfigCreation { my $command = '/usr/bin/xt-create-xen-config --output=/etc/xen'; if ( ( defined( $CONFIG{'template'} ) ) && ( -e $CONFIG{'template'} ) ) { $command .= " --template=" . $CONFIG{'template'}; } logprint( "\nCreating Xen configuration file\n" ); runCommand( $command ); logprint( "Done\n" ); } =begin doc chroot() into the new system and setup the password. =end doc =cut sub setupRootPassword { logprint( "Setting up root password\n" ); if ( -x $MOUNT_POINT . "/usr/bin/passwd" ) { system( "chroot $MOUNT_POINT /usr/bin/passwd" ); } else { logprint( "/usr/bin/passwd on the new system doesn't exist...\n" ); } } =begin doc Print the given string both to our screen, and to the logfile. =end doc =cut sub logprint { my ( $text ) = (@_); print $text; # # Log. # if ( $CONFIG{'hostname'} ) { open( LOGFILE, ">>", "/var/log/xen-tools/$CONFIG{'hostname'}.log" ) or return; print LOGFILE $text; close( LOGFILE ); } } =begin doc A utility method to run a system command. We will capture the return value and exit if the command files. When running verbosely we will also display any command output once it has finished. =end doc =cut sub runCommand { my ( $cmd ) = (@_ ); # # Header. # $CONFIG{'verbose'} && print "Executing : $cmd\n"; # # Copy stderr to stdout, so we can see it, and make sure we log it. # $cmd .= " 2>&1 | tee --append /var/log/xen-tools/$CONFIG{'hostname'}.log"; # # Run it. # my $output = `$cmd`; $CONFIG{'verbose'} && print "Finished : $cmd\n"; if ( $? != 0 ) { logprint( "Running command '$cmd' failed.\n" ); logpring( "Aborting\n" ); exit; } } =begin doc Unmount any mount-points which are below the given path. The mountpoints are chosen by looking at /proc/mounts which might not be portable, but works for me. (tm). =end doc =cut sub unMountImage { my ( $point ) = ( @_ ); # # Open /proc/mount and get a list of currently mounted paths # which begin with our mount point. # my @points; open( MOUNTED, "<", "/proc/mounts" ) or die "Failed to open mount list"; foreach my $line ( ) { # # Split into the device and mountpoint. # my ( $device, $path ) = split( / /, $line ); if ( $path =~ /\Q$point\E/ ) { push @points, $path; } } close( MOUNTED ); # # Now we have a list of mounts. We need to move the # longest first, we can do this by sorting and reversing. # # (ie. We unmount the children, then the parent.) # @points = sort @points; @points = reverse @points; foreach my $path ( @points ) { $CONFIG{'verbose'} && print "Unmounting : $path\n"; runCommand( "umount $path" ); } $MOUNT_POINT = undef; } =begin doc If we still have the temporary image mounted then make sure it is unmounted before we terminate. =end doc =cut sub END { if ( defined( $MOUNT_POINT ) ) { # # Unmount the image, taking care to remove any child mounts too. # unMountImage( $MOUNT_POINT ); } if ( ( defined( $CONFIG{'hostname'} ) ) && ( -e "/var/log/xen-tools/$CONFIG{'hostname'}.log" ) ) { print "\n\nLogfile produced at:\n"; print "\t /var/log/xen-tools/$CONFIG{'hostname'}.log\n"; } }