#!/usr/bin/perl -w =head1 NAME xen-create-image - Create a new Xen instance of Debian Sarge. =head1 SYNOPSIS xen-create-image [options] Help Options: --help Show this scripts help information. --manual Read this scripts manual. Size / General options: --size Set the size of the primary disk image. --swap Set the size of the swap partition. --fs Specify the filesystem type to use. --memory Setup the amount of memory allocated to the instance. Networking options: --dhcp Setup the image to get an IP address via DHCP --network Setup the network the host is upon. --gateway Setup the iamge's network gateway. --broadcast Setup the image's network broadcast address. --mirror Setup the mirror to use when installing Sarge. Mandatory options: --dir Specify where the output images should go. --hostname Set the images hostname. =cut =head1 OPTIONS =over 8 =item B<--size> Specify the size of the virtual images primary drive. The size may be suffixed with either Mb, or Gb. =item B<--swap> Specify the size of the virtual images swap partition. The size may be suffixed with either Mb, or Gb. =item B<--help> Show the brief help information. =item B<--manual> Read the manual, with examples. =back =cut =head1 EXAMPLE 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 =cut =head1 DESCRIPTION This is a reasonably simple script which is more of a cheap hack than a production tool, however it does appear to work nicely for my own uses. The output of this script will be two files for each new instance: 1. One 2Gb file for the installations primary disk image. 2. One 128Mb file for the images swap partition. Usage: The initial two arguments '--hostname' and '--dir' are mandatory, and control the hostname and output directory respectively. Other parameters are all network based, either give the list as shown above or use the '--dhcp' option to setup the host with a DHCP allocated IP address. 'dir' is used to create the output disk images which are referenced in the Xen configuration file. They will be created as: $dir/domains/ $dir/domains/$hostname/disk.img $dir/domains/$hostname/swap.img The '/domains/' subdirectory, and sub-subdirectory named after the hostname will be created if necessary. Basic setup with your hosts file, resolv.conf, etc works just fine. The image will also have an OpenSSH server installed upon it, and an appropriate /etc/inittab file created. You are *strongly* advised to chroot() into the new instance once it has been setup to prune the users in the /etc/passwd file which will be copied from your hosts system. =head1 AUTHOR Steve -- http://www.steve.org.uk/ $Id: xen-create-image,v 1.15 2005-12-18 18:38:21 steve Exp $ =cut =head1 LICENSE Copyright (c) 2005 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 File::Copy; use File::Temp qw/ tempdir /; use Getopt::Long; use Pod::Usage; # # Configuration options. # my %CONFIG; # # Options set on the command line. # my $DIR; # Mandatory. # # Either *all* the relevent networking options must be setup, or # DHCP must be selected. # # my $MIRROR="http://ftp.us.debian.org/debian"; # set with '--mirror=http://www.etc.com"' # # Constants for filesystem usage. # my %FILESYSTEM_CREATE; my %FILESYSTEM_MOUNT; $FILESYSTEM_CREATE{'ext3'} = '/sbin/mkfs.ext3 -F '; $FILESYSTEM_CREATE{'xfs'} = '/sbin/mkfs.xfs -d name='; $FILESYSTEM_CREATE{'reiserfs'} = '/sbin/mkfs.reiserfs -f -q '; $FILESYSTEM_MOUNT{'ext3'} = '-t ext3'; $FILESYSTEM_MOUNT{'xfs'} = '-t xfs'; $FILESYSTEM_MOUNT{'reiserfs'} = '-t reiserfs'; # # Setup defaults: # # Memory = 96M, Image = 2000Mb, Swap = 128Mb, and filesystem is ext3. # # These may be overriden by one of the configuration files, or by the # command line arguments. # $CONFIG{'memory'} = '96Mb'; $CONFIG{'size'} = '2000Mb'; $CONFIG{'swap'} = '128M'; $CONFIG{'fs'} = 'ext3'; # # Read configuration file(s) if they exist. # if ( -e "etc/xen-tools.conf" ) { readConfigurationFile( "etc/xen-tools.conf" ); } if ( -e "/etc/xen-tools/xen-tools.conf" ) { readConfigurationFile( "/etc/xen-tools/xen-tools.conf" ); } if ( -e $ENV{'HOME'} . ".xen-tools.conf" ) { readConfigurationFile( $ENV{'HOME'} . ".xen-tools.conf" ); } # # Parse command line arguments, these override the values from the # configuration file. # parseCommandLineArguments(); # # Check that the arguments the user has supplied are both # valid, and complete. # checkArguments(); print "\n"; print "Hostname : $CONFIG{'hostname'}\n"; print "Image size: $CONFIG{'size'}\n"; print "Swap size: $CONFIG{'swap'}\n"; print "Fileystem: $CONFIG{'fs'}\n"; if ( $CONFIG{'dhcp'} ) { print "DHCP\n"; } else { $CONFIG{'ip'} && print "IP : $CONFIG{'ip'}\n"; $CONFIG{'network'} && print "Network : $CONFIG{'network'}\n"; $CONFIG{'broadcast'} && print "Broadcast: $CONFIG{'broadcast'}\n"; $CONFIG{'gateway'} && print "Gateway : $CONFIG{'gateway'}\n"; } print "---\n"; # # If the output directories don't exist then create them. # if ( ! -d $DIR . "/domains/" ) { mkdir $DIR . '/domains', 0777 || die "Cannot create $DIR/domains - $!"; } if ( ! -d $DIR . "/domains/" . $CONFIG{'hostname'} ) { mkdir $DIR. '/domains/' . $CONFIG{'hostname'}, 0777 || die "Cannot create $DIR/domains/$CONFIG{'hostname'} - $!" ; } # # The two images we'll use, one for the disk image, one for swap. # my $image = $DIR . '/domains/' . $CONFIG{'hostname'} . "/disk.img" ; my $swap = $DIR . '/domains/' . $CONFIG{'hostname'} . "/swap.img" ; # # Create swapfile and initialise it. # print "Creating swapfile : $swap\n"; $CONFIG{'swap'} =~ s/Mb*$//i; `/bin/dd if=/dev/zero of=$swap bs=1024k count=$CONFIG{'swap'} >/dev/null 2>/dev/null`; print "Initializing swap file\n"; `/sbin/mkswap $swap`; print "Done\n"; # # Create disk file and initialise it. # print "Creating disk image: $image\n"; $CONFIG{'size'} =~ s/Mb*$/k/i; `/bin/dd if=/dev/zero of=$image bs=$CONFIG{'size'} count=1 seek=1024 >/dev/null 2>/dev/null`; print "Creating $CONFIG{'fs'} filesystem\n"; my $create = $FILESYSTEM_CREATE{lc( $CONFIG{'fs'} ) } . $image; `$create`; print "Done\n"; # # Now mount the image, in a secure temporary location. # my $dir = tempdir( CLEANUP => 1 ); my $mount_cmd = "mount " . $FILESYSTEM_MOUNT{lc($CONFIG{'fs'})} . " -o loop $image $dir"; `$mount_cmd`; # Test that the mount worked my $mount = `/bin/mount`; if ( ! $mount =~ /$image/) { print "Something went wrong trying to mount the new filesystem\n"; exit; } # # Install the base system. # print "Running debootstrap to install the system. This will take a while!\n"; `debootstrap sarge $dir $MIRROR`; print "Done\n"; # # If the debootstrap failed then we'll setup the output directories # for the configuration files here. # `mkdir -p $dir/etc/apt`; `mkdir -p $dir/etc/network`; # # OK now we can do the basic setup. # print "Setting up APT sources .. "; open( APT, ">", $dir . "/etc/apt/sources.list" ); print APT<", $dir . "/etc/fstab" ); print TAB<", "/etc/xen/$CONFIG{'hostname'}.cfg" ); print XEN<) ) { 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 ); } =head2 parseCommandLineArguments Parse the arguments specified upon the command line. =cut sub parseCommandLineArguments { my $HELP = 0; my $MANUAL = 0; # Parse options. # GetOptions( "hostname=s", \$CONFIG{'hostname'}, "ip=s", \$CONFIG{'ip'}, "gateway=s", \$CONFIG{'gateway'}, "mask=s", \$CONFIG{'netmask'}, "broadcast=s",\$CONFIG{'broadcast'}, "network=s", \$CONFIG{'network'}, "dir=s", \$DIR, "dhcp", \$CONFIG{'dhcp'}, "mirror=s", \$MIRROR, "size=s", \$CONFIG{'size'}, "swap=s", \$CONFIG{'swap'}, "memory=s", \$CONFIG{'memory'}, "fs=s", \$CONFIG{'fs'}, "help", \$HELP, "manual", \$MANUAL ); pod2usage(1) if $HELP; pod2usage(-verbose => 2 ) if $MANUAL; } =head2 checkArguments Check that the arguments the user has specified are complete and make sense. =cut sub checkArguments { if (!defined( $CONFIG{'hostname'} ) ) { print< Mb if ( $CONFIG{'size'} =~ /^(\d+)Gb*$/i ) { $CONFIG{'size'} = $1 * 1024 . "M"; } if ( $CONFIG{'swap'} =~ /^(\d+)Gb*$/i ) { $CONFIG{'swap'} = $1 * 1024 . "M"; } # Strip trailing Mb from the memory size. if ( $CONFIG{'memory'} =~ /^(\d+)Mb*$/i ) { $CONFIG{'memory'} = $1; } # # Check mirror format # if (!($MIRROR =~ /^http/i)) { print "Please enter a valid mirror.\n"; exit; } # # Only one of DHCP / IP is required. # if ( $CONFIG{'ip'} && $CONFIG{'dhcp'}) { print "You've chosen both DHCP and an IP address.\n"; print "Only one is supported\n"; exit; } if ( $CONFIG{'dhcp'} ) { $CONFIG{'gateway'} = ''; $CONFIG{'netmask'} = ''; $CONFIG{'broadcast'} = ''; $CONFIG{'ip'} = ''; } # # Ensure we know how to create *and* mount the given filesystem. # if ( !defined( $FILESYSTEM_CREATE{lc( $CONFIG{'fs'} ) } ) || !defined( $FILESYSTEM_MOUNT{lc( $CONFIG{'fs'} ) } ) ) { print "Unknown filesystem. Valid choices are:\n"; foreach my $key (sort keys %FILESYSTEM_MOUNT ) { print "\t" . $key . "\n"; } exit; } } =head2 setupNetworking Setup the /etc/network/interfaces file, and the hostname upon the virtual instance. =cut sub setupNetworking { my ( $prefix ) = ( @_ ); `echo '$CONFIG{'hostname'}' > $prefix/etc/hostname`; open( IP, ">", $prefix . "/etc/network/interfaces" ); if ( $CONFIG{'dhcp'} ) { print IP< ) { chomp $line; if ( $line =~ /:respawn:/ ) { if ( $line =~ /^1/ ) { $line = "s0:12345:respawn:/sbin/getty 115200 ttyS0 linux" } else { $line = "#" . $line; } } push @init, $line; } close( INITTAB ); open( OUTPUT, ">", "$prefix/etc/inittab" ); foreach my $line ( @init ) { print OUTPUT $line . "\n"; } close( OUTPUT ) }