Any tag ending in ".gpg" will be considered to be the according keyring file in /usr/share/keyrings/. This is at least needed for some Ubuntu releases which were eol'ed in the past few years, especially because Ubuntu split up their keyrings much more finegrained than just "archive" and "archive-removed-keys".
4541 lines
116 KiB
Perl
Executable File
4541 lines
116 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
=encoding utf8
|
|
|
|
=head1 NAME
|
|
|
|
xen-create-image - Easily create new Xen instances with networking and OpenSSH.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
xen-create-image --hostname=<hostname> <further options>
|
|
|
|
|
|
=head1 EXAMPLES
|
|
|
|
xen-create-image --hostname=some-domu --dist=wheezy --lvm=vg0
|
|
|
|
xen-create-image --hostname=some-domu --dist=precise --dir=/srv/xen
|
|
|
|
See below for more specific examples: LOOPBACK EXAMPLES, LVM EXAMPLE
|
|
and EVMS EXAMPLE.
|
|
|
|
|
|
=head1 OPTIONS
|
|
|
|
Help Options:
|
|
|
|
--help Show the help information for this script.
|
|
|
|
--manual Read the manual, and examples, for this script.
|
|
|
|
--(no)verbose (Don't) show more of what xen-create-image is
|
|
currently doing.
|
|
|
|
--dumpconfig Show current configuration.
|
|
|
|
--version Show the version number and exit.
|
|
|
|
|
|
|
|
Size / General options:
|
|
|
|
--(no)accounts
|
|
(Don't) copy all non-system accounts to the guest
|
|
image
|
|
|
|
--admins Specify that some administrators should be created for
|
|
this image, using xen-shell.
|
|
|
|
--(no)boot (Don't) boot the new instance after creating it.
|
|
|
|
--cache=bool Cache .deb files on the host when installing the new
|
|
guest with the debootstrap tool. Accepted values:
|
|
"yes" (default) and "no".
|
|
|
|
--cachedir=/path/to/cache/directory
|
|
Override the default .deb cache directory. Defaults to
|
|
/var/cache/apt/archives/ if it exists (i.e. on Debian
|
|
and Ubuntu) and /var/cache/xen-tools/archives/ else
|
|
(i.e. on Fedora and CentOS).
|
|
|
|
--config=file
|
|
Read the specified file in addition to the global
|
|
configuration file.
|
|
|
|
--(no)copyhosts
|
|
(Don't) copy entries from the dom0's /etc/hosts file
|
|
to the guest
|
|
|
|
--copy-cmd NOP: Ignored.
|
|
|
|
--debootstrap-cmd=/path/to/command
|
|
Specify which debootstrap command is used. Defaults to
|
|
debootstrap if both, debootstrap and cdebootstrap are
|
|
installed. Specifying the path is optional.
|
|
|
|
--disk_device=diskname
|
|
Use specified device name for virtual devices instead
|
|
of the default value "xvda".
|
|
|
|
--extension=ext
|
|
Specify the suffix to give the Xen configuration
|
|
file. (Default value: ".cfg")
|
|
|
|
--(no)force (Don't) 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=fs Specify the filesystem type to use for the new guest.
|
|
Valid choices are 'ext2', 'ext3', 'ext4', 'reiserfs',
|
|
'xfs' or 'btrfs'. (Note: pygrub *DOES NOT* support
|
|
xfs)
|
|
|
|
--genpass=1 Generate a random root password (default, set to 0 to
|
|
turn off)
|
|
|
|
--genpass_len=N
|
|
Override the default password length of 8 and generate
|
|
a random password of length N. Note: this only works
|
|
in conjunction with --genpass
|
|
|
|
--hash_method=algorithm
|
|
Override the default hashing method of sha256 and use
|
|
the provided algorithm. Can be : md5, sha256 or sha512
|
|
|
|
--hooks=1 Specify whether to run hooks after the image is created.
|
|
|
|
--ide Use IDE names for virtual devices (i.e. hda not xvda)
|
|
|
|
--image=str Specify whether to create "sparse" or "full" disk
|
|
images. Full images are mandatory when using LVM, so
|
|
this setting is ignored in that case.
|
|
|
|
--image-dev=/path/to/device
|
|
Specify a physical/logical volume for the disk image.
|
|
|
|
--initrd=/path/to/initrd
|
|
Specify the initial ramdisk. If an image is specified
|
|
it must exist.
|
|
|
|
--install=1 Specify whether to install the guest system or not.
|
|
|
|
--(no)keep (Don't) keep our images if installation fails. It
|
|
maybe unmounted, though.
|
|
|
|
--keyring=/path/to/keyring
|
|
Set the path to the keyring debootstrap should use.
|
|
|
|
--kernel=/path/to/kernel
|
|
Set the path to the kernel to use for domU. If a
|
|
kernel is specified it must exist.
|
|
|
|
--memory=size
|
|
Setup the amount of memory allocated to the new
|
|
instance. As suffix recognized size units are "M",
|
|
"MB", "G" and "GB" (case does not matter). If there's
|
|
no unit given, megabytes are assumed.
|
|
|
|
--maxmem=size
|
|
Setup the maximum amount of memory that can be allocated
|
|
to the new instance. As suffix recognized size units are "M",
|
|
"MB", "G" and "GB" (case does not matter). If there's
|
|
no unit given, megabytes are assumed.
|
|
Required for dynamic memory ballooning.
|
|
|
|
--modules=/path/to/modules
|
|
Set the path to the kernel modules to use for domU.
|
|
If modules are specified they must exist.
|
|
|
|
--nohosts Don't touch /etc/hosts on the dom0.
|
|
|
|
--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.
|
|
|
|
--output=dir Specify the output directory to create the xen
|
|
configuration file within.
|
|
|
|
--partitions=file
|
|
Use a specific partition layout configuration file.
|
|
See /etc/xen-tools/partitions.d/sample-server for an
|
|
example partitioning configuration. Not supported
|
|
with the image-dev and swap-dev options. Parameters
|
|
fs, size, swap and noswap are ignored when using this
|
|
option.
|
|
|
|
--password=passphrase
|
|
Set the root password for the new guest.
|
|
Note: This overrides --genpass
|
|
|
|
--(no)passwd (Don't) ask for a root password interactively during
|
|
setup. NOTE: This overrides --genpass --password.
|
|
|
|
--(no)pygrub DomU should (not) be booted using pygrub.
|
|
|
|
--role=role Run the specified role script(s) post-install. Role
|
|
scripts are discussed later in this manpage. Can be
|
|
an absolute path. Otherwise it's relative to the value
|
|
of --roledir.
|
|
|
|
--role-args="--arg1 --arg2"
|
|
Pass the named string literally to any role script.
|
|
This is useful for site-specific roles.
|
|
|
|
--finalrole=role
|
|
Similar to role scripts. Run the specified role
|
|
script(s) after cfg file creation.
|
|
|
|
--roledir=/path/to/directory
|
|
Specify the directory which contains the role scripts.
|
|
This defaults to /etc/xen-tools/role.d/
|
|
|
|
--scsi Use SCSI names for virtual devices (i.e. sda not xvda)
|
|
|
|
--serial_device=serialname
|
|
Install a getty on the specified serial device instead
|
|
of the default device.
|
|
|
|
--size=size Set the size of the primary disk image.
|
|
|
|
--swap=size Set the size of the swap partition.
|
|
|
|
--swap-dev=/path/to/device
|
|
Specify a physical/logical volume for swap usage.
|
|
|
|
--tar-cmd NOP: Ignored.
|
|
|
|
--dontformat Do not format the devices specified for installation.
|
|
Useful if you want tighter control over the filesystem
|
|
creation. Requires the filesystems to be created
|
|
beforehand.
|
|
|
|
--vcpus=num
|
|
Set the number of vcpus that the new instance will
|
|
have instead of the default value of "1".
|
|
|
|
|
|
|
|
Installation options:
|
|
|
|
--arch=arch Pass the given architecture to debootstrap, rinse, or
|
|
rpmstrap when installing the system. This argument is
|
|
ignored for other install methods.
|
|
|
|
--dist=dist Specify the distribution you wish to install.
|
|
|
|
--install-method=method
|
|
Specify the installation method to use. Valid methods
|
|
are:
|
|
|
|
* debootstrap
|
|
* cdebootstrap
|
|
* rinse
|
|
* rpmstrap (deprecated)
|
|
* tar (needs --install-source=tarball.tar)
|
|
* copy (needs --install-source=/path/to/copy/from)
|
|
|
|
(Default value for Debian and Ubuntu: debootstrap)
|
|
|
|
--install-source=/path/to/tarball
|
|
Specify the source path to use when installing via
|
|
a copy or tarball installation.
|
|
|
|
--mirror=url Setup the mirror to use when installing via
|
|
debootstrap. (Default value: mirror used in
|
|
/etc/apt/sources.list or for Debian
|
|
"http://deb.debian.org/debian/" and for Ubuntu
|
|
"http://archive.ubuntu.com/ubuntu/")
|
|
|
|
The above mentioned Debian mirror hostname
|
|
automatically tries to choose a more or less close
|
|
Debian mirror. See http://deb.debian.org/ for
|
|
details.
|
|
|
|
--apt_proxy=protocol://hostname:port/
|
|
Specify a proxy to be used by debootstrap, and within
|
|
the guest. Needs the same syntax as APT's
|
|
Acquire::http::Proxy. See apt.conf(5).
|
|
|
|
--template=tmpl
|
|
Specify which template file to use when creating the
|
|
Xen configuration file.
|
|
|
|
|
|
|
|
Networking options:
|
|
|
|
--bridge=brname
|
|
Optionally, set a specific bridge for the new
|
|
instance. This can be especially useful when running
|
|
multiple bridges on a dom0.
|
|
|
|
--broadcast=123.456.789.ABC
|
|
Setup the broadcast address for the new instance.
|
|
|
|
--(no)dhcp The guest will (not) be configured to fetch its
|
|
networking details via DHCP.
|
|
|
|
--gateway=gw Setup the network gateway for the new instance.
|
|
|
|
--ip=123.456.789.ABC
|
|
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 vif
|
|
statements per guest. This option conflicts with
|
|
--dhcp.
|
|
|
|
--mac=AA:BB:CC:DD:EE:FF
|
|
Specify the MAC address to use for a given interface.
|
|
This is only valid for the first IP address specified,
|
|
or for DHCP usage. (ie. you can add multiple --ip
|
|
flags, but the specific MAC address will only be used
|
|
for the first interface.)
|
|
|
|
--randommac Creates a random MAC address.
|
|
|
|
--netmask=123.456.789.ABC
|
|
Setup the netmask for the new instance.
|
|
|
|
--nameserver="123.456.789.ABC 123.456.789.DEF"
|
|
Setup the nameserver of the machine, multiple space
|
|
separated nameservers are allowed. If not provided,
|
|
Dom0's /etc/resolv.conf will be copied to guest.
|
|
|
|
--vifname=vifname
|
|
Optionally, set a specific vif name for the new
|
|
instance.
|
|
|
|
--vlan=1 OpenvSwitch related, optionally you can specify a vlan
|
|
where the virtual machine has connectivity.
|
|
|
|
|
|
|
|
Mandatory options:
|
|
|
|
--dir=/path/to/directory
|
|
|
|
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, --evms or --zpool. (These four options are
|
|
mutually exclusive.)
|
|
|
|
--evms=lvm2/container
|
|
Specify the container to save images within,
|
|
i.e. '--evms lvm2/mycontainer'. If you do not wish to
|
|
use EVMS specify --dir, --lvm or --zpool. (These four
|
|
options are mutually exclusive.)
|
|
|
|
--hostname=host.example.org
|
|
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.
|
|
|
|
--lvm=vg Specify the volume group to save images within.
|
|
If you do not wish to use LVM specify --dir, --evms or
|
|
--zpool. (These three options are mutually exclusive.)
|
|
|
|
--lvm_thin=thin pool
|
|
Specify the thin pool name on which thin LVM volumes
|
|
are created.
|
|
|
|
This enables thin provisioned LVM volumes. Note that
|
|
you need a LVM version which supports this.
|
|
|
|
--zpool=pool Specify the ZFS pool to save images within. A new ZFS
|
|
volume will be created for each guest.
|
|
If you do not wish to use ZFS specify --dir, --evms or
|
|
--lvm. (These four options are mutually exclusive.)
|
|
|
|
=head1 NOTES
|
|
|
|
This script is a wrapper around three distinct external tools which
|
|
complete various aspects of the new system installation.
|
|
|
|
=over 8
|
|
|
|
=item B<xt-install-image>
|
|
Install a new distribution.
|
|
|
|
=item B<xt-customize-image>
|
|
Run a collection of hook scripts to customise the freshly installed system.
|
|
|
|
=item B<xt-create-xen-config>
|
|
Create a Xen configuration file in so that xm/xl can start the new domain.
|
|
|
|
=back
|
|
|
|
The result of invoking these three scripts, and some minor glue between
|
|
them, is a simple means of creating new Xen guest domains.
|
|
|
|
|
|
=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.
|
|
|
|
If you wish you can configure arbitrary partitioning schemes, rather
|
|
than being restricted to just the two standard volumes. For more
|
|
details on this please see the later section in this manual "PARTITIONING".
|
|
|
|
If you wish to install additional packages or do any additional
|
|
configuration of your new guests, please read the section on "ROLES".
|
|
|
|
|
|
=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
|
|
maxmem = 512Mb # Memory size
|
|
swap = 128Mb # Swap size
|
|
fs = ext3 # use EXT3 filesystems
|
|
dist = stable # Default distribution to install.
|
|
|
|
#
|
|
# Kernel options.
|
|
#
|
|
kernel = /boot/vmlinuz-`uname -r`
|
|
initrd = /boot/initrd.img-`uname -r`
|
|
|
|
#
|
|
# Networking options.
|
|
#
|
|
gateway = 192.168.1.1
|
|
broadcast = 192.168.1.255
|
|
netmask = 255.255.255.0
|
|
|
|
|
|
#
|
|
# Installation method:
|
|
# One of "copy", "debootstrap", "cdebootstrap", "rinse", "rpmstrap", or "tar".
|
|
#
|
|
install-method = debootstrap
|
|
|
|
=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.
|
|
|
|
|
|
=head1 NETWORKING AUTO-SETUP
|
|
|
|
We've already seen how the "gateway" and "netmask" options can
|
|
be used to specify the networking options of the freshly created
|
|
Xen guests.
|
|
|
|
One other useful shortcut is the use of an automatic IP address.
|
|
You can specify '--ip=auto' and the system will choose and use
|
|
an IP address from those listed in /etc/xen-tools/ips.txt.
|
|
|
|
For example if you wished to have Xen guests automatically
|
|
take an address from the range 192.168.1.100-192.168.1.200 you
|
|
would first prepare the system by running this:
|
|
|
|
=for example start
|
|
|
|
rm /etc/xen-tools/ips.txt
|
|
for i in $(seq 100 200) ; do echo 192.168.1.$i >> /etc/xen-tools/ips.txt ; done
|
|
|
|
=for example end
|
|
|
|
Now you can create a guest with the command:
|
|
|
|
=for example start
|
|
|
|
xen-create-image --ip=auto --hostname=blah [--dist=...]
|
|
|
|
=for example end
|
|
|
|
The first time this ran the machine would receive an IP address
|
|
from the pool which we've created. This IP would be marked as used,
|
|
and would no longer be available. If all the IP addresses are taken
|
|
then the system will fail.
|
|
|
|
|
|
=head1 PARTITIONING
|
|
|
|
By default all new guests are created with two "volumes", one
|
|
for the root filesystem and one for the new system's swap.
|
|
|
|
If you wish you may specify an alternative partitioning scheme.
|
|
Simply create a file inside the directory /etc/xen-tools/partitions.d/
|
|
specifying your partition layout. (Use the existing file "sample-server"
|
|
as a template).
|
|
|
|
Now when you create a new image specify the name of this file with as
|
|
an argument to the --partition option.
|
|
|
|
|
|
=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 by default. However
|
|
you may change the output directory with the --output flag.
|
|
|
|
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.
|
|
|
|
|
|
=head1 LOOPBACK EXAMPLES
|
|
|
|
The following will create a 2Gb disk image, along with a 128Mb
|
|
swap file with Debian Stable setup and running via DHCP.
|
|
|
|
xen-create-image --size=2Gb --swap=128Mb --dhcp --dist=stable \
|
|
--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 \
|
|
--nameserver=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.
|
|
|
|
|
|
=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
|
|
|
|
|
|
=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
|
|
|
|
|
|
=head1 INSTALLATION METHODS
|
|
|
|
The new guest images may be installed in several different ways:
|
|
|
|
1. Using the [c]debootstrap command, which must be installed and present.
|
|
2. Using the rpmstrap command, which must be installed and present.
|
|
3. using the rinse command, which must be installed and present.
|
|
4. By copying an existing installation.
|
|
5. 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.
|
|
|
|
|
|
=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 \
|
|
--install-method=tar --install-source=/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 \
|
|
--install-method=copy --install-source=/path/to/copy/from
|
|
|
|
|
|
=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 or, if this does not exist, in
|
|
/var/cache/xen-tools/archives. This can be overridden with the
|
|
--cache-dir command-line and configuration option.
|
|
|
|
This feature can be disabled with the command line flag --cache=no,
|
|
or by the matching setting in the configuration file.
|
|
|
|
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 host's apt cache (/var/cache/apt/archivees)
|
|
you may do so with apt-get, namely:
|
|
|
|
apt-get clean
|
|
|
|
If you set your cache directory to anything else, simply rm the
|
|
contents of the directory.
|
|
|
|
|
|
=head1 ROLES
|
|
|
|
Currently there are some roles scripts included which work for
|
|
the Debian and Ubuntu distributions 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 cfengine
|
|
Install cfengine2 on the virtual image and copy the cfengine
|
|
configuration from Dom0.
|
|
|
|
=item editor
|
|
Allows generalised editing of files for guests.
|
|
|
|
This script works via a skeleton directory containing small sed files
|
|
which will contain edits to be applied to an arbitrary tree of files
|
|
upon the new domU.
|
|
|
|
For example if we have the following sed file:
|
|
|
|
/etc/xen-tools/sed.d/etc/ssh/sshd_config.sed
|
|
|
|
this will be applied to /etc/ssh/sshd_config upon the new guest *if*
|
|
it exists. If the file encoded in the name doesn't exist then it will
|
|
be ignored.
|
|
|
|
=item gdm
|
|
Install an X11 server, using VNC and GDM
|
|
|
|
=item minimal
|
|
Customise the generated images to remove some packages.
|
|
|
|
=item puppet
|
|
Install puppet on the virtual image and copy the cfengine
|
|
configuration from Dom0.
|
|
|
|
=item tmpfs
|
|
Sets up /tmp, /var/run and /var/lock as tmpfs in the DomU.
|
|
|
|
=item udev
|
|
Install udev in the DomU. Most distributions install udev by default
|
|
nowadays, so this role is probably only interesting for legacy systems
|
|
which need udev anyway.
|
|
|
|
=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". Additionally you
|
|
may pass options to your role-script with the --role-args
|
|
flag.
|
|
|
|
For example the script /etc/xen-tools/role.d/gdm would be used
|
|
by executing with "--role=gdm".
|
|
|
|
Role scripts are invoked with the directory containing the
|
|
installed system as their first argument, and anything passed
|
|
as a role-arg will be passed along as additional arguments.
|
|
|
|
NOTE: Role scripts are invoked before the config file generation.
|
|
If you need access to the config file from within your role,
|
|
use --finalrole.
|
|
NOTE: Multiple role scripts may be invoked if you separate their
|
|
names with commas.
|
|
|
|
|
|
=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 AUTHORS
|
|
|
|
Steve Kemp, https://steve.fi/
|
|
Axel Beckert, https://axel.beckert.ch/
|
|
Dmitry Nedospasov, http://www.nedos.net/
|
|
Stéphane Jourdois
|
|
|
|
|
|
=head1 LICENSE
|
|
|
|
Copyright (c) 2005-2009 by Steve Kemp, (c) 2010-2013 by The Xen-Tools
|
|
Development Team. 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
|
|
|
|
$SIG{INT} = \&clean_up;
|
|
|
|
use strict;
|
|
use English;
|
|
use Digest::MD5 qw/ md5_hex /;
|
|
use Env;
|
|
use File::Path qw/ mkpath /;
|
|
use File::Temp qw/ tempdir /;
|
|
use File::Copy qw/ mv cp /;
|
|
use File::Slurp;
|
|
use File::Which;
|
|
use Getopt::Long;
|
|
use Pod::Usage;
|
|
use Data::Dumper;
|
|
use Data::Validate::URI qw/ is_uri /;
|
|
use Data::Validate::IP qw/ is_ipv4 /;
|
|
use Data::Validate::Domain qw/ is_hostname /;
|
|
use Term::UI;
|
|
use Term::ReadLine;
|
|
use Sort::Versions;
|
|
use Xen::Tools::Common;
|
|
|
|
|
|
#
|
|
# Configuration values read initially from the global configuration
|
|
# file, then optionally overridden by the command line.
|
|
#
|
|
my %CONFIG;
|
|
|
|
|
|
#
|
|
# Distribution meta data
|
|
#
|
|
my %DIST;
|
|
|
|
|
|
#
|
|
# Mirror meta data
|
|
#
|
|
my %MIRROR;
|
|
|
|
|
|
#
|
|
# Partition layout information values read from the partitions file,
|
|
# or constructed automatically if no partitions file is specified.
|
|
#
|
|
my @PARTITIONS = ();
|
|
|
|
|
|
#
|
|
# 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 = '4.8';
|
|
|
|
|
|
#
|
|
# Variable for ip addresses for output
|
|
#
|
|
my $IP_ADDRESSES = '';
|
|
|
|
|
|
#
|
|
# Variable for generated password
|
|
#
|
|
my $PASSWORD = '';
|
|
|
|
|
|
#
|
|
# Define some fallback password length
|
|
#
|
|
my $default_genpass_len = 23;
|
|
|
|
|
|
# Minor helpers for reducing code duplication
|
|
sub fail ($) { fail_with_config($_[0], \%CONFIG); }
|
|
sub logprint ($) { logprint_with_config($_[0], \%CONFIG); }
|
|
|
|
#
|
|
# Read the global distributions meta data file.
|
|
#
|
|
readConfigurationFile("/etc/xen-tools/distributions.conf", \%DIST);
|
|
|
|
|
|
#
|
|
# Read the global default mirrors file.
|
|
#
|
|
readConfigurationFile("/etc/xen-tools/mirrors.conf", \%MIRROR);
|
|
|
|
|
|
#
|
|
# Setup default options.
|
|
#
|
|
setupDefaultOptions();
|
|
|
|
|
|
#
|
|
# Read the global configuration file.
|
|
#
|
|
readConfigurationFile("/etc/xen-tools/xen-tools.conf", \%CONFIG);
|
|
|
|
|
|
#
|
|
# Parse the command line arguments.
|
|
#
|
|
parseCommandLineArguments();
|
|
|
|
|
|
#
|
|
# If we received an additional configuration file then read it.
|
|
#
|
|
if ( $CONFIG{ 'config' } )
|
|
{
|
|
my $path = $CONFIG{ 'config' };
|
|
|
|
# If not fully-qualified then read from /etc/xen-tools.
|
|
if ( $path !~ /^[\/]/ )
|
|
{
|
|
$path = "/etc/xen-tools/" . $path;
|
|
}
|
|
|
|
# Read the file, if it exists.
|
|
if ( -e $path )
|
|
{
|
|
readConfigurationFile($path, \%CONFIG);
|
|
}
|
|
else
|
|
{
|
|
logprint( "The specified configuration file does not exist: '$path'\n".
|
|
"Aborting\n\n" );
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# Process --debug
|
|
#
|
|
if ( $CONFIG{ 'debug' } )
|
|
{
|
|
foreach my $key ( sort keys %CONFIG )
|
|
{
|
|
print $key;
|
|
print " : " . $CONFIG{ $key } if ( $CONFIG{ $key } );
|
|
print "\n";
|
|
}
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
|
|
|
|
#
|
|
# Check the environment - after parsing arguments.
|
|
#
|
|
# This is required so that the "--help" flag will work even if our support
|
|
# scripts are not installed, etc.
|
|
#
|
|
checkSystem();
|
|
|
|
|
|
#
|
|
# Ensure we're started by root at this point. This is required
|
|
# to make sure we can create new LVM volumes, mount loopback images, or
|
|
# carry out other privileged actions.
|
|
#
|
|
testRootUser();
|
|
|
|
|
|
#
|
|
# Check our arguments were sane and complete.
|
|
#
|
|
checkArguments();
|
|
|
|
|
|
#
|
|
# Make sure we have a log directory
|
|
#
|
|
setupLogFile();
|
|
|
|
|
|
#
|
|
# Check we have binaries installed which we expect to use.
|
|
#
|
|
checkBinariesPresent();
|
|
|
|
|
|
#
|
|
# Setup default partitioning scheme if we don't have one.
|
|
#
|
|
# NOTE: This must be done before we call "showSummary".
|
|
#
|
|
if ( !@PARTITIONS )
|
|
{
|
|
populatePartitionsData()
|
|
if ( ( $CONFIG{ 'dir' } ) ||
|
|
( $CONFIG{ 'evms' } ) ||
|
|
( $CONFIG{ 'lvm' } ) ||
|
|
( $CONFIG{ 'zpool' } ) );
|
|
}
|
|
|
|
|
|
#
|
|
# Show a summary of what we're going to do.
|
|
#
|
|
showSummary();
|
|
|
|
|
|
|
|
#
|
|
# 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();
|
|
}
|
|
elsif ( $CONFIG{ 'image-dev' } )
|
|
{
|
|
|
|
#
|
|
# Use physical disc
|
|
#
|
|
usePhysicalDevice();
|
|
}
|
|
elsif ( $CONFIG{ 'zpool' } )
|
|
{
|
|
|
|
#
|
|
# Create our ZFS volumes
|
|
#
|
|
createZFSBits();
|
|
}
|
|
else
|
|
{
|
|
|
|
# Can't happen we didn't get an installation type.
|
|
logprint( "Error: No recognised installation type.\n".
|
|
"Please specify a directory, lvm, zpool, or evms volume to use.\n" );
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
|
|
#
|
|
# Mount the image.
|
|
#
|
|
mountImage();
|
|
|
|
|
|
#
|
|
# Export our environment for the hooks/role script we might be
|
|
# running later.
|
|
#
|
|
# Do this unconditionally now, so that we're all setup to run
|
|
# a hook even if we're not installing a system.
|
|
#
|
|
exportEnvironment();
|
|
|
|
|
|
#
|
|
# If we're installing then do so, and test that it worked with
|
|
# a binary name that is reasonably likely to exist under any
|
|
# distribution of GNU/Linux.
|
|
#
|
|
if ( $CONFIG{ 'install' } )
|
|
{
|
|
|
|
#
|
|
# Install the system.
|
|
#
|
|
installSystem();
|
|
|
|
#
|
|
# Did that work?
|
|
#
|
|
if ( !-x $MOUNT_POINT . "/bin/ls" )
|
|
{
|
|
logprint("System installation failed: /bin/ls missing. Aborting.\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Now customize the installation - setting up networking, etc.
|
|
#
|
|
if ( $CONFIG{ 'hooks' } )
|
|
{
|
|
runCustomisationHooks();
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#
|
|
# Run any specified role scripts.
|
|
#
|
|
runRoleScripts( $CONFIG{ 'role' } );
|
|
|
|
|
|
#
|
|
# Create the Xen configuration file.
|
|
#
|
|
runXenConfigCreation();
|
|
|
|
|
|
#
|
|
# Run any specified role scripts.
|
|
#
|
|
runRoleScripts( $CONFIG{ 'finalrole' } );
|
|
|
|
|
|
#
|
|
# Setup the password if the user wanted that.
|
|
#
|
|
setupRootPassword() if ( $CONFIG{ 'passwd' } or $CONFIG{ 'genpass' } or $CONFIG{ 'password' });
|
|
|
|
|
|
#
|
|
# Report success.
|
|
#
|
|
logprint("All done\n");
|
|
|
|
|
|
#
|
|
# Finished.
|
|
#
|
|
exit 0;
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Test that this system is fully setup for the new xen-create-image
|
|
script.
|
|
|
|
This means that the companion scripts xt-* are present on the
|
|
host and executable.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub checkSystem
|
|
{
|
|
#
|
|
# Make sure that we have Text::Template installed - this
|
|
# will be used by `xt-create-xen-config` and if that fails then
|
|
# running is pointless.
|
|
#
|
|
my $test = "use Text::Template";
|
|
eval($test);
|
|
if ( ($@) && ( !$CONFIG{ 'force' } ) )
|
|
{
|
|
print <<EOERROR;
|
|
|
|
Aborting: The Text::Template perl module isn\'t installed or available.
|
|
|
|
Specify '--force' to skip this check and continue regardless.
|
|
|
|
EOERROR
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
|
|
|
|
#
|
|
# Check that all the binaries have been installed properly
|
|
#
|
|
my @required =
|
|
qw(xt-customize-image xt-install-image xt-create-xen-config);
|
|
|
|
foreach my $bin (@required)
|
|
{
|
|
if ( !defined( which($bin) ) )
|
|
{
|
|
logprint( "The script '$bin' was not found.\nAborting\n\n" );
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Make sure that xen-shell is installed if we've got an --admin
|
|
# flag specified
|
|
#
|
|
if ( $CONFIG{ 'admins' } )
|
|
{
|
|
my $shell = undef;
|
|
$shell = "/usr/bin/xen-login-shell"
|
|
if ( -x "/usr/bin/xen-login-shell" );
|
|
$shell = "/usr/local/bin/xen-login-shell"
|
|
if ( -x "/usr/bin/local/xen-login-shell" );
|
|
|
|
if ( !defined($shell) )
|
|
{
|
|
print <<EOF;
|
|
|
|
You've specified administrator accounts for use with the xen-shell,
|
|
however the xen-shell doesn't appear to be installed.
|
|
|
|
Aborting.
|
|
EOF
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Setup the default options we'd expect into our global configuration hash.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub setupDefaultOptions
|
|
{
|
|
#
|
|
# This flag is set upon failure, after images have been created.
|
|
#
|
|
# It is used so that we can automatically "rollback" upon failure.
|
|
#
|
|
$CONFIG{'FAIL'} = 0;
|
|
|
|
#
|
|
# Paths and files.
|
|
#
|
|
$CONFIG{ 'dir' } = '';
|
|
$CONFIG{ 'xm' } = findXenToolstack();
|
|
$CONFIG{ 'kernel' } = '';
|
|
$CONFIG{ 'modules' } = '';
|
|
$CONFIG{ 'initrd' } = '';
|
|
$CONFIG{ 'serial_device' } = 'hvc0';
|
|
$CONFIG{ 'disk_device' } = 'xvda';
|
|
|
|
#
|
|
# Sizing options.
|
|
#
|
|
$CONFIG{ 'memory' } = '96Mb';
|
|
$CONFIG{ 'size' } = '2000Mb';
|
|
$CONFIG{ 'swap' } = '128M';
|
|
$CONFIG{ 'cache' } = 'yes';
|
|
$CONFIG{ 'cachedir' } = '/var/cache/apt/archives/';
|
|
$CONFIG{ 'image' } = 'sparse';
|
|
$CONFIG{ 'vcpus' } = '1';
|
|
|
|
#
|
|
# Misc. options.
|
|
#
|
|
|
|
# Default distribution is Debian Stable
|
|
$CONFIG{ 'dist' } = 'stable';
|
|
$CONFIG{ 'mirror' } = '';
|
|
$CONFIG{ 'keyring' } = '';
|
|
|
|
# Initialize per distribution mirror defaults
|
|
foreach my $debdist (keys %DIST) {
|
|
my $debdistinfo = $DIST{$debdist};
|
|
foreach my $dist (qw(debian ubuntu)) {
|
|
if ($debdistinfo =~ /$dist/) {
|
|
if ($debdistinfo =~ /eol/) {
|
|
$CONFIG{ 'mirror_'.$debdist } = $MIRROR{$dist.'_archive'} or
|
|
die $dist.'_archive not defined in /etc/xen-tools/mirrors.conf';
|
|
my $removed_keys = "/usr/share/keyrings/$dist-archive-removed-keys.gpg";
|
|
if ($debdistinfo =~ /(\S*\.gpg)($|\s)/ and -s "/usr/share/keyrings/${1}") {
|
|
$CONFIG{ 'keyring_'.$debdist } = "/usr/share/keyrings/${1}";
|
|
} elsif ($debdistinfo !~ /default-keyring/ and -s $removed_keys) {
|
|
$CONFIG{ 'keyring_'.$debdist } = $removed_keys;
|
|
}
|
|
} elsif ($debdistinfo =~ /(\S*\.gpg)($|\s)/ and -s "/usr/share/keyrings/${1}") {
|
|
$CONFIG{ 'keyring_'.$debdist } = "/usr/share/keyrings/${1}";
|
|
} else {
|
|
$CONFIG{ 'mirror_'.$debdist } = $MIRROR{$dist} or
|
|
die $dist.' not defined in /etc/xen-tools/mirrors.conf';
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
$CONFIG{ 'apt_proxy' } = '';
|
|
$CONFIG{ 'arch' } =
|
|
which('dpkg') ? `dpkg --print-architecture` : '';
|
|
chomp($CONFIG{ 'arch' });
|
|
$CONFIG{ 'fs' } = 'ext3';
|
|
$CONFIG{ 'force' } = 0;
|
|
$CONFIG{ 'install' } = 1;
|
|
$CONFIG{ 'hooks' } = 1;
|
|
$CONFIG{ 'partitions' } = '';
|
|
$CONFIG{ 'pid' } = 0;
|
|
$CONFIG{ 'template' } = '';
|
|
$CONFIG{ 'roledir' } = '/etc/xen-tools/role.d';
|
|
$CONFIG{ 'partitionsdir' } = '/etc/xen-tools/partitions.d';
|
|
$CONFIG{ 'ipfile' } = '/etc/xen-tools/ips.txt';
|
|
$CONFIG{ 'output' } = '/etc/xen';
|
|
$CONFIG{ 'extension' } = '.cfg';
|
|
|
|
#
|
|
# Installation method defaults to "debootstrap" using
|
|
# "debootstrap" (instead of cdebootstrap).
|
|
#
|
|
$CONFIG{ 'install-method' } = 'debootstrap';
|
|
$CONFIG{ 'debootstrap-cmd' } = '';
|
|
|
|
#
|
|
# Default values for passwords
|
|
#
|
|
$CONFIG{ 'genpass' } = 1;
|
|
$CONFIG{ 'genpass_len' } = $default_genpass_len;
|
|
$CONFIG{ 'password' } = undef;
|
|
$CONFIG{ 'hash_method' } = 'md5';
|
|
|
|
#
|
|
# 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 existence itself.
|
|
#
|
|
$CONFIG{ 'make_fs_ext2' } = 'mkfs.ext2 -F';
|
|
$CONFIG{ 'make_fs_ext3' } = 'mkfs.ext3 -F';
|
|
$CONFIG{ 'make_fs_ext4' } = 'mkfs.ext4 -F';
|
|
$CONFIG{ 'make_fs_xfs' } = 'mkfs.xfs -f';
|
|
$CONFIG{ 'make_fs_reiserfs' } = 'mkfs.reiserfs -f -q';
|
|
$CONFIG{ 'make_fs_btrfs' } = 'mkfs.btrfs';
|
|
|
|
#
|
|
# 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_ext2' } = '-t ext2';
|
|
$CONFIG{ 'mount_fs_ext3' } = '-t ext3';
|
|
$CONFIG{ 'mount_fs_ext4' } = '-t ext4';
|
|
$CONFIG{ 'mount_fs_xfs' } = '-t xfs';
|
|
$CONFIG{ 'mount_fs_reiserfs' } = '-t reiserfs';
|
|
$CONFIG{ 'mount_fs_btrfs' } = '-t btrfs';
|
|
|
|
#
|
|
# Network options.
|
|
#
|
|
$CONFIG{ 'nameserver' } = '';
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Validate options and do what is necessary with them.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub checkOption
|
|
{
|
|
my ($option, $value) = @_;
|
|
|
|
# Define argument types
|
|
my %types = (
|
|
integerWithSuffix => {
|
|
check => qr/^[0-9.]+[GM]B?$/i,
|
|
message => "takes a suffixed (mb, MB, G, etc.) integer.\n",
|
|
},
|
|
distribution => {
|
|
check => sub { -d "/usr/share/xen-tools/$_[0].d" },
|
|
message => "takes a distribution name " .
|
|
"(see /usr/share/xen-tools for valid values).\n",
|
|
},
|
|
imageType => {
|
|
check => qr/^sparse|full$/,
|
|
message => "must be 'sparse' or 'full'.\n",
|
|
},
|
|
existingFile => {
|
|
check => sub { -e $_[0] },
|
|
message => "must be an existing file.\n",
|
|
},
|
|
existingDir => {
|
|
check => sub { -d $_[0] },
|
|
message => "must be an existing directory.\n",
|
|
},
|
|
serialDev => {
|
|
check => qr/^(?:\/dev\/)?(?:tty|[xh]vc)[0-9]+$/,
|
|
message => "must be a serial device (tty[0-9]+, hvc[0-9]+ or xvc[0-9]+).\n",
|
|
},
|
|
diskDev => {
|
|
check => qr/^(?:\/dev\/)?(?:xvd|sd)[a-z]+$/,
|
|
message => "must be a disk device (xvd[a-z]+, sd[a-z]+).\n",
|
|
},
|
|
ipv4 => {
|
|
check => sub { is_ipv4($_[0]) },
|
|
message => "must be valid IPv4.\n",
|
|
},
|
|
ipv4_or_auto => {
|
|
check => sub { is_ipv4($_[0]) or $_[0] eq 'auto' },
|
|
message => "must be valid IPv4 or the keyword 'auto'.\n",
|
|
},
|
|
hostname => {
|
|
check => sub { is_hostname($_[0]) },
|
|
message => "must be a valid hostname.\n",
|
|
},
|
|
supportedFs => {
|
|
check => qr/^(ext[234]|xfs|reiserfs|btrfs)$/,
|
|
message => "must be a supported filesystem (ext2, ext3, ext4, xfs, reiserfs or btrfs).\n",
|
|
},
|
|
yesNo => {
|
|
check => qr/^yes|no$/i,
|
|
message => "must be 'yes' or 'no'.\n",
|
|
},
|
|
zeroOne => {
|
|
check => qr/^0|1$/i,
|
|
message => "must be '0' or '1'.\n",
|
|
},
|
|
configFile => {
|
|
check => sub { -e $_[0] or -e "/etc/xen-tools/" . $_[0] },
|
|
message => "must be an existing file.\n",
|
|
},
|
|
filename => {
|
|
check => qr/^[a-z0-9_.-]*$/,
|
|
message => "must be a valid filename.\n",
|
|
},
|
|
mac => {
|
|
check => qr/^(?:[0-9a-f]{2}:){5}[0-9a-f]{2}$/i,
|
|
message => "must be a valid ethernet mac address.\n",
|
|
},
|
|
hashMethod => {
|
|
check => qr/^md5|sha256|sha512$/i,
|
|
message => "must be md5, sha256 or sha512.\n",
|
|
},
|
|
uri => {
|
|
check => sub { is_uri($_[0]) },
|
|
message => "must be an URI including the protocol\n",
|
|
},
|
|
vlan => {
|
|
check => qr/^([1-9][0-9]{0,2}|10[01][0-9]|102[0-4])$/i,
|
|
message => "must be a number between 1 and 1024.\n",
|
|
},
|
|
);
|
|
|
|
# Define what argument each option accepts.
|
|
# Arguments for options not listed here will always be accepted.
|
|
my %optionsTypes = (
|
|
size => 'integerWithSuffix',
|
|
dist => 'distribution',
|
|
swap => 'integerWithSuffix',
|
|
image => 'imageType',
|
|
memory => 'integerWithSuffix',
|
|
maxmem => 'integerWithSuffix',
|
|
kernel => 'existingFile',
|
|
keyring => 'existingFile',
|
|
initrd => 'existingFile',
|
|
modules => 'existingDir',
|
|
serial_device => 'serialDev',
|
|
disk_device => 'diskDev',
|
|
gateway => 'ipv4',
|
|
netmask => 'ipv4', # This is dubious.
|
|
broadcast => 'ipv4',
|
|
hostname => 'hostname',
|
|
nameserver => 'ipv4',
|
|
pointopoint => 'ipv4',
|
|
fs => 'supportedFs',
|
|
cache => 'yesNo',
|
|
cachedir => 'existingDir',
|
|
config => 'configFile',
|
|
install => 'zeroOne',
|
|
hooks => 'zeroOne',
|
|
roledir => 'existingDir',
|
|
template => 'configFile',
|
|
output => 'existingDir',
|
|
extension => 'filename',
|
|
mac => 'mac',
|
|
ip => 'ipv4_or_auto',
|
|
hash_method => 'hashMethod',
|
|
apt_proxy => 'uri',
|
|
vlan => 'vlan',
|
|
);
|
|
|
|
# If given option does not exists in optionsTypes,
|
|
# we just copy it to %CONFIG.
|
|
unless ( exists $optionsTypes{ $option } ) {
|
|
$CONFIG{ $option } = $value;
|
|
} else { # we validate it before copying
|
|
my $type = $optionsTypes{ $option };
|
|
|
|
# First, check if type exists
|
|
fail("Type $type does not exist") unless exists $types{ $type };
|
|
my $check = $types{ $type }{ 'check' };
|
|
|
|
if (
|
|
(ref $check eq 'Regexp' and $value =~ $check) or
|
|
(ref $check eq 'CODE' and &$check( $value ) )
|
|
) {
|
|
# Option did validate, copy it
|
|
if ( $option eq "ip" )
|
|
{
|
|
push @{ $CONFIG{ $option } }, $value;
|
|
} else {
|
|
$CONFIG{ $option } = $value;
|
|
}
|
|
} else {
|
|
# Option did _not_ validate
|
|
fail("ERROR: '$option' argument " . $types{ $type }{ 'message' });
|
|
}
|
|
}
|
|
}
|
|
|
|
=begin doc
|
|
|
|
Parse the command line arguments this script was given.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
my $HELP = 0;
|
|
my $MANUAL = 0;
|
|
my $DUMPCONFIG = 0;
|
|
my $VERSION = 0;
|
|
|
|
sub parseCommandLineArguments
|
|
{
|
|
#
|
|
# 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{ 'evms' } = undef;
|
|
$install{ 'dir' } = undef;
|
|
$install{ 'lvm' } = undef;
|
|
$install{ 'image-dev' } = undef;
|
|
$install{ 'zpool' } = undef;
|
|
|
|
#
|
|
# Parse options.
|
|
#
|
|
if (
|
|
!GetOptions(
|
|
|
|
# Mandatory
|
|
"dist=s", \&checkOption,
|
|
|
|
# Size options.
|
|
"size=s", \&checkOption,
|
|
"swap=s", \&checkOption,
|
|
"noswap", \&checkOption,
|
|
"image=s", \&checkOption,
|
|
"memory=s", \&checkOption,
|
|
"maxmem=s", \&checkOption,
|
|
"vcpus=i", \&checkOption,
|
|
|
|
# Locations
|
|
"dir=s", \$install{ 'dir' },
|
|
"evms=s", \$install{ 'evms' },
|
|
"kernel=s", \&checkOption,
|
|
"initrd=s", \&checkOption,
|
|
"mirror=s", \&checkOption,
|
|
"keyring=s", \&checkOption,
|
|
"apt_proxy=s", \&checkOption,
|
|
"modules=s", \&checkOption,
|
|
"lvm=s", \$install{ 'lvm' },
|
|
"zpool=s", \$install{ 'zpool' },
|
|
"image-dev=s", \$install{ 'image-dev' },
|
|
"swap-dev=s", \$install{ 'swap-dev' },
|
|
"serial_device=s", \&checkOption,
|
|
"disk_device=s", \&checkOption,
|
|
|
|
# Hosts options
|
|
"nohosts", \$CONFIG{ 'nohosts' },
|
|
"copyhosts!", \$CONFIG{ 'copyhosts' },
|
|
# Deprecated legacy options for backwards compatibility
|
|
"no-hosts", \$CONFIG{ 'nohosts' },
|
|
"copy-hosts!", \$CONFIG{ 'copyhosts' },
|
|
|
|
# Networking options
|
|
"dhcp!", \$CONFIG{ 'dhcp' },
|
|
"bridge=s", \&checkOption,
|
|
"gateway=s", \&checkOption,
|
|
"hostname=s", \&checkOption,
|
|
"ip=s@", \&checkOption,
|
|
"mac=s", \&checkOption,
|
|
"randommac", \$CONFIG{ 'randommac' },
|
|
"netmask=s", \&checkOption,
|
|
"broadcast=s", \&checkOption,
|
|
"nameserver=s", \&checkOption,
|
|
"vifname=s", \&checkOption,
|
|
"p2p=s", \&checkOption,
|
|
"vlan=s", \&checkOption,
|
|
|
|
# Exclusive
|
|
#
|
|
# NOTE: We set the local variable here, not the global.
|
|
#
|
|
"install-method=s", \$CONFIG{ 'install-method' },
|
|
"install-source=s", \$CONFIG{ 'install-source' },
|
|
"debootstrap-cmd=s", \$CONFIG{ 'debootstrap-cmd' },
|
|
|
|
# Misc. options
|
|
"accounts!", \$CONFIG{ 'accounts' },
|
|
"admins=s", \&checkOption,
|
|
"arch=s", \&checkOption,
|
|
"fs=s", \&checkOption,
|
|
"boot!", \$CONFIG{ 'boot' },
|
|
"cache=s", \&checkOption,
|
|
"cachedir=s", \&checkOption,
|
|
"config=s", \&checkOption,
|
|
"ide", \$CONFIG{ 'ide' },
|
|
"scsi", \$CONFIG{ 'scsi' },
|
|
"install=i", \&checkOption,
|
|
"hooks=i", \&checkOption,
|
|
"pygrub!", \$CONFIG{ 'pygrub' },
|
|
"passwd!", \$CONFIG{ 'passwd' },
|
|
"genpass=i", \&checkOption,
|
|
"genpass-len=i",\&checkOption,
|
|
"genpass_len=i",\&checkOption,
|
|
"password=s", \&checkOption,
|
|
"hash_method=s",\&checkOption,
|
|
"partitions=s", \&checkOption,
|
|
"role=s", \&checkOption,
|
|
"role-args=s", \&checkOption,
|
|
"finalrole=s", \&checkOption,
|
|
"roledir=s", \&checkOption,
|
|
"force!", \$CONFIG{ 'force' },
|
|
"no-xen-ok", sub { warn "Option --no-xen-ok is deprecated and ignored."; },
|
|
"keep!", \$CONFIG{ 'keep' },
|
|
"template=s", \&checkOption,
|
|
"output=s", \&checkOption,
|
|
"extension:s", \&checkOption,
|
|
"dontformat", \&checkOption,
|
|
"lvm_thin=s", \$CONFIG{ 'lvm_thin' },
|
|
|
|
# Help options
|
|
"debug!", \$CONFIG{ 'debug' },
|
|
"help", \$HELP,
|
|
"manual", \$MANUAL,
|
|
"dumpconfig", \$DUMPCONFIG,
|
|
"verbose!", \$CONFIG{ 'verbose' },
|
|
"version", \$VERSION
|
|
) )
|
|
{
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit;
|
|
}
|
|
|
|
if ( $HELP ) {
|
|
$CONFIG{'FAIL'}=-1;
|
|
pod2usage(1);
|
|
}
|
|
|
|
if ( $MANUAL ) {
|
|
$CONFIG{'FAIL'}=-1;
|
|
pod2usage( -verbose => 2 );
|
|
}
|
|
|
|
if ($VERSION)
|
|
{
|
|
logprint("xen-create-image release $RELEASE\n");
|
|
exit 0;
|
|
}
|
|
|
|
|
|
#
|
|
# Now make ensure that the command line setting of '--lvm', '--evms', '--zpool'
|
|
# and '--dir=x' override anything specified in the configuration file.
|
|
#
|
|
if ( $install{ 'dir' } )
|
|
{
|
|
$CONFIG{ 'dir' } = $install{ 'dir' };
|
|
$CONFIG{ 'evms' } = undef;
|
|
$CONFIG{ 'lvm' } = undef;
|
|
$CONFIG{ 'image-dev' } = undef;
|
|
$CONFIG{ 'zpool' } = undef;
|
|
}
|
|
if ( $install{ 'evms' } )
|
|
{
|
|
$CONFIG{ 'dir' } = undef;
|
|
$CONFIG{ 'evms' } = $install{ 'evms' };
|
|
$CONFIG{ 'lvm' } = undef;
|
|
$CONFIG{ 'image-dev' } = undef;
|
|
$CONFIG{ 'zpool' } = undef;
|
|
}
|
|
if ( $install{ 'lvm' } )
|
|
{
|
|
$CONFIG{ 'dir' } = undef;
|
|
$CONFIG{ 'evms' } = undef;
|
|
$CONFIG{ 'lvm' } = $install{ 'lvm' };
|
|
$CONFIG{ 'image-dev' } = undef;
|
|
$CONFIG{ 'zpool' } = undef;
|
|
}
|
|
if ( $install{ 'zpool' } )
|
|
{
|
|
$CONFIG{ 'dir' } = undef;
|
|
$CONFIG{ 'evms' } = undef;
|
|
$CONFIG{ 'lvm' } = undef;
|
|
$CONFIG{ 'image-dev' } = undef;
|
|
$CONFIG{ 'zpool' } = $install{ 'zpool' };
|
|
}
|
|
if ( $install{ 'image-dev' } )
|
|
{
|
|
$CONFIG{ 'dir' } = undef;
|
|
$CONFIG{ 'evms' } = undef;
|
|
$CONFIG{ 'lvm' } = undef;
|
|
$CONFIG{ 'image-dev' } = $install{ 'image-dev' };
|
|
$CONFIG{ 'zpool' } = undef;
|
|
$CONFIG{ 'size' } = undef;
|
|
$CONFIG{ 'swap' } = undef;
|
|
|
|
$CONFIG{ 'swap-dev' } = $install{ 'swap-dev' }
|
|
if ( defined( $install{ 'swap-dev' } ) );
|
|
}
|
|
|
|
if ($DUMPCONFIG)
|
|
{
|
|
print Dumper \%CONFIG;
|
|
exit 0;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Make sure this script is being run by a user with UID 0.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub testRootUser
|
|
{
|
|
if ( $EFFECTIVE_USER_ID != 0 )
|
|
{
|
|
my $err = <<EOROOT;
|
|
|
|
In order to use this script you must be running with root privileges.
|
|
|
|
(This is necessary to mount the disk images which are created.)
|
|
|
|
EOROOT
|
|
|
|
logprint($err);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Test that the command line arguments we were given make sense.
|
|
|
|
Here we make sure that mutually exclusive options are not selected
|
|
for the installation method, etc.
|
|
|
|
We also warn when some variables are not set.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub checkArguments
|
|
{
|
|
|
|
#
|
|
# We require a distribution name.
|
|
#
|
|
if ( !defined( $CONFIG{ 'dist' } ) )
|
|
{
|
|
logprint("The '--dist' argument is mandatory\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# We require a hostname.
|
|
#
|
|
if ( !defined( $CONFIG{ 'hostname' } ) )
|
|
{
|
|
logprint("The '--hostname' argument is mandatory.\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Lucid and probably all later Ubuntus, too, don't work without pygrub
|
|
#
|
|
if ( $DIST{ $CONFIG{ 'dist' } } =~ /pygrub/ )
|
|
{
|
|
$CONFIG{ 'pygrub' } = 1;
|
|
}
|
|
|
|
#
|
|
# Sarge amd64 needs different default mirror
|
|
#
|
|
if ( $CONFIG{ 'dist' } =~ /sarge/ and
|
|
$CONFIG{ 'arch' } =~ /amd64/ and
|
|
$CONFIG{ 'mirror_sarge' } =~ m(/debian-archive/debian/?$))
|
|
{
|
|
$CONFIG{ 'mirror_sarge' } =~
|
|
s(/debian-archive/debian/?)(/debian-archive/debian-amd64);
|
|
}
|
|
|
|
#
|
|
# If no mirror is set, use the default per-distro mirror
|
|
#
|
|
my $distMirror = "mirror_" . $CONFIG{ 'dist' };
|
|
if ( !$CONFIG{ 'mirror' } and
|
|
$CONFIG{ $distMirror } and
|
|
length( $CONFIG{ $distMirror } ) )
|
|
{
|
|
$CONFIG{ 'mirror' } = $CONFIG{ $distMirror };
|
|
}
|
|
|
|
#
|
|
# If no keyring is set, use the default per-distro keyring if present
|
|
#
|
|
my $distKeyring = "keyring_" . $CONFIG{ 'dist' };
|
|
if ( !$CONFIG{ 'keyring' } and
|
|
$CONFIG{ $distKeyring } and
|
|
length( $CONFIG{ $distKeyring } ) )
|
|
{
|
|
$CONFIG{ 'keyring' } = $CONFIG{ $distKeyring };
|
|
}
|
|
|
|
#
|
|
# NOTE: FAKE!
|
|
#
|
|
if ( $CONFIG{ 'dist' } eq 'fedora-core4' )
|
|
{
|
|
$CONFIG{ 'dist' } = 'stentz';
|
|
}
|
|
|
|
#
|
|
# If using LVM or EVMS then the images may not be sparse
|
|
#
|
|
$CONFIG{ 'image' } = "full"
|
|
if ( $CONFIG{ 'lvm' } ||
|
|
$CONFIG{ 'evms' } ||
|
|
$CONFIG{ 'image-dev' } );
|
|
|
|
|
|
|
|
#
|
|
# Make sure that our installation method is specified.
|
|
#
|
|
my $valid = 0;
|
|
if ( defined( $CONFIG{ 'install-method' } ) )
|
|
{
|
|
foreach my $recognised (
|
|
qw/ copy debootstrap cdebootstrap rinse rpmstrap tar /)
|
|
{
|
|
$valid = 1
|
|
if ( lc( $CONFIG{ 'install-method' } ) eq lc($recognised) );
|
|
}
|
|
|
|
#
|
|
# If we have "cdebootstrap", set it to "debootstrap" and set
|
|
# debootstrap-cmd to cdebootstrap instead.
|
|
#
|
|
if ( lc( $CONFIG{ 'install-method' } ) eq 'cdebootstrap' )
|
|
{
|
|
$CONFIG{ 'install-method' } = 'debootstrap';
|
|
$CONFIG{ 'debootstrap-cmd' } = 'cdebootstrap';
|
|
}
|
|
|
|
#
|
|
# If we have "copy", or "tar" method then make sure we have a source.
|
|
#
|
|
if ( ( lc( $CONFIG{ 'install-method' } ) eq "copy" ) ||
|
|
( lc( $CONFIG{ 'install-method' } ) eq "tar" ) )
|
|
{
|
|
|
|
# not defined.
|
|
$valid = 0 if ( !defined( $CONFIG{ 'install-source' } ) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$valid = 1;
|
|
}
|
|
|
|
if ( !$valid )
|
|
{
|
|
print <<EOF;
|
|
Please specify the installation method to use, along with a source
|
|
if that is required.
|
|
|
|
For example:
|
|
|
|
--install-method=copy --install-source=/some/path
|
|
--install-method=debootstrap
|
|
--install-method=rinse
|
|
--install-method=rpmstrap
|
|
--install-method=tar --install-source=/some/file.tar
|
|
|
|
EOF
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
|
|
#
|
|
# Make sure that any specified template file exists.
|
|
#
|
|
if ( defined( $CONFIG{ 'template' } ) &&
|
|
length( $CONFIG{ 'template' } ) )
|
|
{
|
|
if ( -e $CONFIG{ 'template' } )
|
|
{
|
|
|
|
# nop
|
|
}
|
|
elsif ( -e "/etc/xen-tools/$CONFIG{'template'}" )
|
|
{
|
|
$CONFIG{ 'template' } = "/etc/xen-tools/$CONFIG{'template'}";
|
|
}
|
|
else
|
|
{
|
|
|
|
# failed to find either by fully qualified path,
|
|
# or inside /etc/xen-tools.
|
|
logprint(
|
|
"The specified template file, $CONFIG{'template'}, does not exist.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# If we've got a role directory specified then it must exist.
|
|
#
|
|
if ( defined( $CONFIG{ 'roledir' } ) && length( $CONFIG{ 'roledir' } ) )
|
|
{
|
|
if ( !-d $CONFIG{ 'roledir' } )
|
|
{
|
|
logprint(
|
|
"The specified role directory '$CONFIG{'roledir'}' does not exist\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# If we've got a partitions directory specified then it must exist.
|
|
#
|
|
if ( defined( $CONFIG{ 'partitionsdir' } ) &&
|
|
length( $CONFIG{ 'partitionsdir' } ) )
|
|
{
|
|
if ( !-d $CONFIG{ 'partitionsdir' } )
|
|
{
|
|
logprint(
|
|
"The specified partitions directory '$CONFIG{'partitionsdir'}' does not exist\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# Make sure that any specified partitions file exists.
|
|
#
|
|
if ( ( defined( $CONFIG{ 'partitions' } ) ) &&
|
|
( length( $CONFIG{ 'partitions' } ) ) )
|
|
{
|
|
if ( !( $CONFIG{ 'partitions' } =~ /\// ) )
|
|
{
|
|
$CONFIG{ 'partitions' } =
|
|
$CONFIG{ 'partitionsdir' } . '/' . $CONFIG{ 'partitions' };
|
|
}
|
|
|
|
if ( !-e $CONFIG{ 'partitions' } )
|
|
{
|
|
logprint(
|
|
"The specified partitions file, $CONFIG{'partitions'}, does not exist.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
loadAndCheckPartitionsFile();
|
|
}
|
|
|
|
if ( $CONFIG{ 'swap-dev' } && $CONFIG{ 'noswap' } )
|
|
{
|
|
logprint("Please choose either swap-dev or noswap, not both!\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( $CONFIG{ 'swap-dev' } && $CONFIG{ 'partitions' } )
|
|
{
|
|
logprint("Please choose either swap-dev or partitions, not both!\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( $CONFIG{ 'image-dev' } )
|
|
{
|
|
if ( $CONFIG{ 'partitions' } )
|
|
{
|
|
logprint("Please choose either image-dev or partitions, not both!\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( !$CONFIG{ 'swap-dev' } && !$CONFIG{ 'noswap' } )
|
|
{
|
|
logprint("Please choose swap-dev or noswap with image-dev!\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# The user must choose either DHCP *or* Static IP. not both
|
|
#
|
|
if ( $CONFIG{ 'dhcp' } && $CONFIG{ 'ip' } )
|
|
{
|
|
|
|
#
|
|
# However we will allow the DHCP setting to override a *partially*
|
|
# specified IP address.
|
|
#
|
|
if ( $CONFIG{ 'ip' } =~ /^([0-9]+)\.([0-9]+)\.([0-9]+)\.*$/ )
|
|
{
|
|
delete $CONFIG{ 'ip' };
|
|
}
|
|
else
|
|
{
|
|
logprint("Please choose either DHCP or static usage, not both!\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# The user must specify one or the other.
|
|
#
|
|
if ( ( !$CONFIG{ 'dhcp' } ) && ( !$CONFIG{ 'ip' } ) )
|
|
{
|
|
logprint("Please choose one of:\n");
|
|
logprint(" --dhcp\n");
|
|
logprint(" --ip xx.xx.xx.xx\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# If we're using static addresses warn if there are variables
|
|
# missing
|
|
#
|
|
if ( $CONFIG{ 'ip' } )
|
|
{
|
|
logprint("WARNING: No gateway address specified!\n")
|
|
unless ( defined( $CONFIG{ 'gateway' } ) );
|
|
|
|
logprint("WARNING: No netmask address specified!\n")
|
|
unless ( defined( $CONFIG{ 'netmask' } ) );
|
|
}
|
|
|
|
#
|
|
# If we don't have a MAC address specified then generate one.
|
|
# If randommac is specified, generate random MAC.
|
|
#
|
|
if ( !$CONFIG{ 'mac' } )
|
|
{
|
|
if ( $CONFIG{ 'randommac' } )
|
|
{
|
|
$CONFIG{ 'mac' } = generateRandomMACAddress();
|
|
}
|
|
else
|
|
{
|
|
$CONFIG{ 'mac' } = generateMACAddress();
|
|
}
|
|
}
|
|
|
|
#
|
|
# Make sure our output directory exists.
|
|
#
|
|
if ( !-d $CONFIG{ 'output' } )
|
|
{
|
|
print <<EOF;
|
|
The output directory for creating the xen configuration file within
|
|
doesn\'t exist:
|
|
|
|
$CONFIG{ 'output' }
|
|
|
|
Aborting.
|
|
|
|
EOF
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Already present?
|
|
#
|
|
my $cfg =
|
|
$CONFIG{ 'output' } . "/" . $CONFIG{ 'hostname' } .
|
|
$CONFIG{ 'extension' };
|
|
if ( -e $cfg )
|
|
{
|
|
if ( $CONFIG{ 'force' } )
|
|
{
|
|
$CONFIG{ 'verbose' } && print "Removing existing file: $cfg\n";
|
|
unlink($cfg);
|
|
}
|
|
else
|
|
{
|
|
print "Configuration file already exists; $cfg\n";
|
|
print "Aborting\n";
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Generate a pseudo-random MAC address.
|
|
|
|
The MAC address is constructed based upon :
|
|
|
|
1. The standard Xen prefix.
|
|
|
|
2. The hostname + IP address of the new guest.
|
|
|
|
3. The distribution which is to be installed.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub generateMACAddress
|
|
{
|
|
|
|
#
|
|
# Start with the xen prefix
|
|
#
|
|
my $mac = '00:16:3E';
|
|
|
|
#
|
|
# Build up ( hostname + ip + dhcp + dist );
|
|
#
|
|
my $hash = '';
|
|
foreach my $key (qw/ hostname ip dhcp dist /)
|
|
{
|
|
if ( $CONFIG{ $key } ) {
|
|
if (ref($CONFIG{ $key }) eq 'ARRAY') {
|
|
$hash .= join(',',@{$CONFIG{ $key }});
|
|
} elsif (ref($CONFIG{ $key }) eq 'HASH') {
|
|
$hash .= join(',',values %{$CONFIG{ $key }});
|
|
} else {
|
|
$hash .= $CONFIG{ $key };
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# Generate an MD5 hash of this data.
|
|
#
|
|
$hash = md5_hex($hash);
|
|
|
|
#
|
|
# Now build up a MAC address
|
|
#
|
|
while ( length($mac) < 17 )
|
|
{
|
|
$mac .= ":" . substr( $hash, 0, 2 );
|
|
$hash = substr( $hash, 2 );
|
|
}
|
|
|
|
return ( uc($mac) );
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Generate a MAC address based on the Xen prefix and a really random local part.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
|
|
sub generateRandomMACAddress
|
|
{
|
|
|
|
#
|
|
# Start with the xen prefix
|
|
#
|
|
my $mac = '00:16:3E';
|
|
|
|
#
|
|
# Generate random local part and append to $mac
|
|
#
|
|
for ( my $count=0; $count < 3; $count++ )
|
|
{
|
|
$mac = $mac . ":" . sprintf("%02X", int(rand(255)));
|
|
}
|
|
|
|
return ( $mac );
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Make sure we have a log directory, and create an empty logfile
|
|
for this run.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub setupLogFile
|
|
{
|
|
|
|
mkdir( "/var/log/xen-tools", 0750 ) if ( !-d "/var/log/xen-tools" );
|
|
|
|
#
|
|
# Move any existing for this run logfile.
|
|
# (Hint: read from the end to understand how this works).
|
|
#
|
|
my $logname = "/var/log/xen-tools/$CONFIG{'hostname'}.log";
|
|
map {
|
|
(my $new = $_) =~ s/(?<=\.)\d+(?=\.log$)/$& + 1/e;
|
|
mv $_, $new; # increment file number
|
|
} sort {
|
|
$a =~ /\.(\d+)\.log$/;
|
|
my $aa = $1;
|
|
$b =~ /\.(\d+)\.log$/;
|
|
my $bb = $1;
|
|
$bb <=> $aa; # sort in reverse order
|
|
} grep /\.\d+\.log$/, # we only care in numeric filenames
|
|
glob( "/var/log/xen-tools/$CONFIG{'hostname'}.*.log" );
|
|
|
|
# Move the non-numeric filename also
|
|
mv $logname, "/var/log/xen-tools/$CONFIG{'hostname'}.0.log"
|
|
if -f $logname;
|
|
|
|
#
|
|
# Now create an empty file.
|
|
#
|
|
open STUB, '>', $logname;
|
|
close STUB;
|
|
|
|
#
|
|
# Make sure the logfile is 0640 - avoid leaking root passwords.
|
|
#
|
|
chmod( oct("0640"), $logname );
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Check that we have some required binaries present.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub checkBinariesPresent
|
|
{
|
|
|
|
#
|
|
# Files we demand are present in all cases.
|
|
#
|
|
my @required = qw ( mount mkswap );
|
|
|
|
foreach my $file (@required)
|
|
{
|
|
if ( !defined( which($file) ) )
|
|
{
|
|
logprint("The following binary is required to run this tool\n");
|
|
logprint("\t$file\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Image type specific binaries
|
|
#
|
|
if ( defined( $CONFIG{ 'dir' } ) )
|
|
{
|
|
|
|
# loopback image
|
|
if ( !defined( which("dd") ) )
|
|
{
|
|
logprint("The following binary is required to run this tool\n");
|
|
logprint("\tdd\n");
|
|
logprint(
|
|
"(This only required for loopback images, which you've selected)\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
elsif ( defined( $CONFIG{ 'evms' } ) )
|
|
{
|
|
|
|
#
|
|
# EVMS-specific binaries.
|
|
#
|
|
my @evms = qw ( evms echo );
|
|
|
|
foreach my $file (@evms)
|
|
{
|
|
if ( !defined( which($file) ) )
|
|
{
|
|
logprint("The following binary is required to run this tool\n");
|
|
logprint("\t$file\n");
|
|
logprint(
|
|
"(This is only required for EVMS volumes, which you've selected)\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
elsif (defined( $CONFIG{ 'lvm' } ) )
|
|
{
|
|
|
|
# LVM-specific binaries.
|
|
my @lvm = qw ( lvcreate lvremove );
|
|
|
|
foreach my $file (@lvm)
|
|
{
|
|
if ( !defined( which($file) ) )
|
|
{
|
|
logprint("The following binary is required to run this tool\n");
|
|
logprint("\t$file\n");
|
|
logprint(
|
|
"(This is only required for LVM volumes, which you've selected)\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
elsif (defined( $CONFIG{ 'zpool' } ) )
|
|
{
|
|
|
|
# ZFS-specific binaries.
|
|
my @zfs = qw ( zfs );
|
|
|
|
foreach my $file (@zfs)
|
|
{
|
|
if ( !defined( which($file) ) )
|
|
{
|
|
logprint("The following binary is required to run this tool\n");
|
|
logprint("\t$file\n");
|
|
logprint(
|
|
"(This is only required for ZFS volumes, which you've selected)\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Loads a partitions file, checks the syntax and updates the configuration
|
|
variables with it
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub loadAndCheckPartitionsFile
|
|
{
|
|
my %partitions;
|
|
|
|
#
|
|
# Here we'll test for the required Perl module.
|
|
#
|
|
# This allows us to:
|
|
#
|
|
# a) Degrade usefully if the module isn't available.
|
|
#
|
|
# b) Not require the module unless the user specifies a custom
|
|
# partitioning scheme.
|
|
#
|
|
my $test = "use Config::IniFiles";
|
|
eval($test);
|
|
if ($@)
|
|
{
|
|
print <<EOF;
|
|
|
|
Aborting - To use the custom partitioning code you need to have the
|
|
following Perl module installed:
|
|
|
|
Config::IniFiles
|
|
|
|
On a Debian system you can get this with:
|
|
|
|
apt-get install libconfig-inifiles-perl
|
|
|
|
Otherwise fetch it from CPAN.
|
|
EOF
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
tie %partitions, 'Config::IniFiles', ( -file => $CONFIG{ 'partitions' } );
|
|
|
|
@PARTITIONS = ();
|
|
|
|
my $name;
|
|
my $details;
|
|
my $foundroot = 0;
|
|
while ( ( $name, $details ) = each %partitions )
|
|
{
|
|
if ( !( $name =~ /^[a-zA-Z0-9-]+$/ ) )
|
|
{
|
|
logprint("The partition name $name contains invalid characters.\n");
|
|
logprint(
|
|
"Only alphanumeric characters and the hyphen are allowed\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( !( $details->{ 'size' } =~ /^[0-9.]+[GgMmKk]b?$/ ) )
|
|
{
|
|
logprint(
|
|
"The size $details->{'size'} of partition $name contains is not recognized.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( $details->{ 'type' } eq 'swap' )
|
|
{
|
|
push( @PARTITIONS,
|
|
{ 'name' => $name,
|
|
'size' => $details->{ 'size' },
|
|
'type' => 'swap',
|
|
'mountpoint' => '',
|
|
'options' => ''
|
|
} );
|
|
}
|
|
else
|
|
{
|
|
if ( !$CONFIG{ 'make_fs_' . $details->{ 'type' } } )
|
|
{
|
|
logprint(
|
|
"The type $details->{'type'} of partition $name is not recognized.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( !( $details->{ 'mountpoint' } =~ /^\/[^: \t\r\n]*$/ ) )
|
|
{
|
|
logprint(
|
|
"The mount point $details->{'mountpoint'} of partition $name is invalid.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( !( $details->{ 'options' } =~ /^[^: \t\r\n]*$/ ) )
|
|
{
|
|
logprint(
|
|
"The mount options $details->{'options'} of partition $name are invalid.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
if ( !$details->{ 'options' } )
|
|
{
|
|
$details->{ 'options' } = 'defaults';
|
|
}
|
|
|
|
if ( $details->{ 'mountpoint' } eq '/' )
|
|
{
|
|
$foundroot = 1;
|
|
}
|
|
|
|
push( @PARTITIONS,
|
|
{ 'name' => $name,
|
|
'size' => $details->{ 'size' },
|
|
'type' => $details->{ 'type' },
|
|
'mountpoint' => $details->{ 'mountpoint' },
|
|
'options' => $details->{ 'options' } } );
|
|
}
|
|
}
|
|
|
|
if ( !$foundroot )
|
|
{
|
|
logprint("The root partition was not specified.\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Sort by length of the mountpoint.
|
|
#
|
|
# This makes it easy to mount parent folders first
|
|
# (e.g. /var before /var/tmp)
|
|
#
|
|
@PARTITIONS =
|
|
sort {length $a->{ 'mountpoint' } <=> length $b->{ 'mountpoint' }}
|
|
@PARTITIONS;
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Populates the partition information using the supplied configuration
|
|
arguments when not using the partitions file
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub populatePartitionsData
|
|
{
|
|
@PARTITIONS = ();
|
|
|
|
#
|
|
# [swap]
|
|
#
|
|
push( @PARTITIONS,
|
|
{ 'name' => 'swap',
|
|
'size' => $CONFIG{ 'swap' },
|
|
'type' => 'swap',
|
|
'mountpoint' => '',
|
|
'options' => ''
|
|
} ) unless ( $CONFIG{ 'noswap' } );
|
|
|
|
#
|
|
# read the default filesystem options from the configuration file.
|
|
#
|
|
my $options = $CONFIG{ $CONFIG{ 'fs' } . "_options" } || undef;
|
|
|
|
#
|
|
# If there weren't any options in the configuration file then
|
|
# revert to our defaults.
|
|
#
|
|
if ( !defined($options) )
|
|
{
|
|
|
|
#
|
|
# XFS has different default options.
|
|
#
|
|
$options = "errors=remount-ro";
|
|
$options = "defaults" if ( $CONFIG{ 'fs' } eq "xfs" );
|
|
}
|
|
|
|
|
|
#
|
|
# [root]
|
|
#
|
|
push( @PARTITIONS,
|
|
{ 'name' => 'disk',
|
|
'size' => $CONFIG{ 'size' },
|
|
'type' => $CONFIG{ 'fs' },
|
|
'mountpoint' => '/',
|
|
'options' => $options
|
|
} );
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Converts the internal partitions array into a text representation
|
|
suitable for passing to other scripts.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub exportPartitionsToConfig
|
|
{
|
|
$CONFIG{ 'NUMPARTITIONS' } = $#PARTITIONS + 1;
|
|
|
|
my $i;
|
|
for ( $i = 0 ; $i < $CONFIG{ 'NUMPARTITIONS' } ; $i++ )
|
|
{
|
|
$CONFIG{ 'PARTITION' . ( $i + 1 ) } =
|
|
$PARTITIONS[$i]{ 'name' } . ':' . $PARTITIONS[$i]{ 'size' } . ':' .
|
|
$PARTITIONS[$i]{ 'type' } . ':' . $PARTITIONS[$i]{ 'mountpoint' } .
|
|
':' . $PARTITIONS[$i]{ 'options' } . ':' .
|
|
$PARTITIONS[$i]{ 'imagetype' } . ':' . $PARTITIONS[$i]{ 'image' };
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Show the user a summary of what is going to be created for them
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub showSummary
|
|
{
|
|
|
|
#
|
|
# Show the user what to expect.
|
|
#
|
|
logprint("\nGeneral Information\n");
|
|
logprint("--------------------\n");
|
|
logprint("Hostname : $CONFIG{'hostname'}\n");
|
|
logprint("Distribution : $CONFIG{'dist'}\n");
|
|
logprint("Mirror : $CONFIG{'mirror'}\n");
|
|
|
|
if ( defined $CONFIG{ 'image-dev' } )
|
|
{
|
|
logprint("Root Device : $CONFIG{'image-dev'}\n");
|
|
}
|
|
if ( defined $CONFIG{ 'swap-dev' } )
|
|
{
|
|
logprint("Swap Device : $CONFIG{'swap-dev'}\n");
|
|
}
|
|
|
|
my $info;
|
|
my $partcount = 0;
|
|
|
|
logprint("Partitions : ");
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
next if ( !$partition );
|
|
$info = sprintf( '%-15s %-5s (%s)',
|
|
( $partition->{ 'type' } ne 'swap' ) ?
|
|
$partition->{ 'mountpoint' } :
|
|
'swap',
|
|
$partition->{ 'size' },
|
|
$partition->{ 'type' } );
|
|
|
|
if ( $partcount++ )
|
|
{
|
|
logprint(" $info\n");
|
|
}
|
|
else
|
|
{
|
|
logprint("$info\n");
|
|
}
|
|
}
|
|
|
|
logprint("Image type : $CONFIG{'image'}\n");
|
|
logprint("Memory size : $CONFIG{'memory'}\n");
|
|
|
|
if ( defined( $CONFIG{ 'maxmem' } ) )
|
|
{
|
|
logprint("Max mem size : $CONFIG{'maxmem'}\n");
|
|
}
|
|
|
|
if ( exists( $CONFIG{ 'pygrub' } ) &&
|
|
$CONFIG{ 'pygrub' } ) {
|
|
logprint("Bootloader : pygrub\n");
|
|
} else {
|
|
if ( defined( $CONFIG{ 'kernel' } ) && length( $CONFIG{ 'kernel' } ) )
|
|
{
|
|
logprint("Kernel path : $CONFIG{'kernel'}\n");
|
|
}
|
|
|
|
if ( defined( $CONFIG{ 'modules' } ) && length( $CONFIG{ 'modules' } ) )
|
|
{
|
|
logprint("Module path : $CONFIG{'modules'}\n");
|
|
}
|
|
|
|
if ( defined( $CONFIG{ 'initrd' } ) && length( $CONFIG{ 'initrd' } ) )
|
|
{
|
|
logprint("Initrd path : $CONFIG{'initrd'}\n");
|
|
}
|
|
}
|
|
|
|
logprint("\nNetworking Information\n");
|
|
logprint("----------------------\n");
|
|
|
|
#
|
|
# Show each IP address added.
|
|
#
|
|
# Note we only allow the first IP address to have a MAC address specified.
|
|
#
|
|
my $ips = $CONFIG{ 'ip' };
|
|
my $mac = $CONFIG{ 'mac' };
|
|
my $count = 1;
|
|
|
|
if ( defined $ips )
|
|
{
|
|
|
|
#
|
|
# Scary magic.
|
|
#
|
|
if ( !UNIVERSAL::isa( $ips, "ARRAY" ) )
|
|
{
|
|
|
|
#
|
|
# If we're reading the value of "ip = xxx" from the configuration
|
|
# file we'll have a single (scalar) value in $CONFIG{'ip'}.
|
|
#
|
|
# BUT we actually assume this hash element contains a reference
|
|
# to an array - since that is what the command-line parsing code
|
|
# sets up for us.
|
|
#
|
|
# So here we fake it - that was what the test above as for,
|
|
# if we didn't have an array already, then fake one up.
|
|
#
|
|
# We reset the $ips reference to undef, then coerce it to be an
|
|
# (empty) array and push on our single IP.
|
|
#
|
|
# It works. Even if it's nasty, (or if it is a clever hack!)
|
|
#
|
|
$ips = undef;
|
|
push( @$ips, $CONFIG{ 'ip' } );
|
|
$CONFIG{ 'ip' } = $ips;
|
|
}
|
|
}
|
|
|
|
|
|
if ( defined $ips )
|
|
{
|
|
|
|
#
|
|
# Print out each network address, and if there is a mac address
|
|
# associated with it then use it too.
|
|
#
|
|
foreach my $i (@$ips)
|
|
{
|
|
my $m = undef;
|
|
|
|
if ( ( $count == 1 ) && ( defined($mac) ) )
|
|
{
|
|
$m = $mac;
|
|
}
|
|
|
|
|
|
#
|
|
# Here we have special handling for the case where
|
|
# IP addresses to be generated automatically.
|
|
#
|
|
#
|
|
if ( $i =~ /auto/i )
|
|
{
|
|
$CONFIG{ 'verbose' } &&
|
|
logprint("Automatically determining an IP.");
|
|
|
|
$i = findIP($i);
|
|
if ( defined($i) )
|
|
{
|
|
$CONFIG{ 'verbose' } && logprint("Claimed $i\n");
|
|
}
|
|
else
|
|
{
|
|
print <<EOF;
|
|
|
|
ERROR: You specified the automatic choosing of an IP address and
|
|
none are left in $CONFIG{'ipfile'}.
|
|
|
|
EOF
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Show the IP address.
|
|
#
|
|
logprint("IP Address $count : $i");
|
|
|
|
#
|
|
# Log the ip addresses for later output
|
|
#
|
|
$IP_ADDRESSES .= "$i ";
|
|
|
|
# Option MAC address.
|
|
if ( defined($m) )
|
|
{
|
|
logprint(" [MAC: $m]");
|
|
}
|
|
logprint("\n");
|
|
|
|
$count += 1;
|
|
}
|
|
}
|
|
|
|
#
|
|
# mac address setting still works even for DHCP, but in that
|
|
# case only the first one works.
|
|
#
|
|
if ( $CONFIG{ 'dhcp' } )
|
|
{
|
|
if ( defined( $CONFIG{ 'mac' } ) )
|
|
{
|
|
logprint("IP Address : DHCP [MAC: $CONFIG{'mac'}]\n");
|
|
}
|
|
else
|
|
{
|
|
logprint("IP Address : DHCP\n");
|
|
}
|
|
}
|
|
|
|
$CONFIG{ 'netmask' } && logprint("Netmask : $CONFIG{'netmask'}\n");
|
|
$CONFIG{ 'broadcast' } &&
|
|
logprint("Broadcast : $CONFIG{'broadcast'}\n");
|
|
$CONFIG{ 'gateway' } && logprint("Gateway : $CONFIG{'gateway'}\n");
|
|
$CONFIG{ 'nameserver' } && logprint("Nameserver : $CONFIG{'nameserver'}\n");
|
|
$CONFIG{ 'p2p' } && logprint("Point to Point : $CONFIG{'p2p'}\n");
|
|
$CONFIG{ 'vlan' } && logprint("VLAN : $CONFIG{'vlan'}\n");
|
|
print "\n";
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Test that the user has the "loop" module loaded and present,
|
|
this is just a warning useful to newcomers.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub testLoopbackModule
|
|
{
|
|
if ( -e "/proc/modules" )
|
|
{
|
|
my $modules = `cat /proc/modules`;
|
|
|
|
if ( $modules !~ m/loop/ )
|
|
{
|
|
logprint("WARNING\n");
|
|
logprint("-------\n");
|
|
logprint(
|
|
"Loopback module not loaded and you're using loopback images\n"
|
|
);
|
|
logprint("Run the following to load the module:\n\n");
|
|
logprint("modprobe loop max_loop=255\n\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Create the two images "swap.img" and "disk.img" in the directory
|
|
we've been given.
|
|
|
|
We also will call the filesystem creation routine to make sure we
|
|
have a valid filesystem.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub createLoopbackImages
|
|
{
|
|
|
|
#
|
|
# Make sure we have the relevant output directory.
|
|
#
|
|
my $output = $CONFIG{ 'dir' } . "/domains/" . $CONFIG{ 'hostname' };
|
|
|
|
if ( !-d $output )
|
|
{
|
|
|
|
#
|
|
# Catch errors with eval.
|
|
#
|
|
eval {mkpath( $output, 0, 0755 );};
|
|
if ($@)
|
|
{
|
|
fail("Cannot create directory tree $output - $@");
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# Only proceed overwritting if we have --force specified.
|
|
#
|
|
if ( !$CONFIG{ 'force' } )
|
|
{
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk =
|
|
$CONFIG{ 'dir' } . '/domains/' . $CONFIG{ 'hostname' } . '/' .
|
|
$partition->{ 'name' } . '.img';
|
|
|
|
if ( -e $disk )
|
|
{
|
|
logprint("The partition image already exists. Aborting.\n");
|
|
logprint(
|
|
"Specify '--force' to overwrite, or remove the following file\n"
|
|
);
|
|
logprint( $disk . "\n" );
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk =
|
|
$CONFIG{ 'dir' } . '/domains/' . $CONFIG{ 'hostname' } . '/' .
|
|
$partition->{ 'name' } . '.img';
|
|
|
|
#
|
|
# Save the image path to the partitions array
|
|
#
|
|
$partition->{ 'imagetype' } = 'file:';
|
|
$partition->{ 'image' } = $disk;
|
|
|
|
#
|
|
# Modify the size to something reasonable
|
|
#
|
|
my $size = $partition->{ 'size' };
|
|
|
|
#
|
|
# Convert Gb -> Mb for the partition image size.
|
|
#
|
|
if ( $size =~ /^([0-9.]+)Gb*$/i )
|
|
{
|
|
$size = $1 * 1024 . "M";
|
|
}
|
|
|
|
#
|
|
# Final adjustments to sizing.
|
|
#
|
|
$size =~ s/Mb*$/k/i;
|
|
|
|
#
|
|
# Use dd to create the partition image.
|
|
#
|
|
logprint("\nCreating partition image: $disk\n");
|
|
my $image_cmd;
|
|
if ( $CONFIG{ 'image' } eq "sparse" )
|
|
{
|
|
$CONFIG{ 'verbose' } && logprint("Creating sparse image\n");
|
|
$image_cmd = "dd if=/dev/zero of=$disk bs=$size count=0 seek=1024";
|
|
}
|
|
else
|
|
{
|
|
$CONFIG{ 'verbose' } && logprint("Creating full-sized image\n");
|
|
$image_cmd = "dd if=/dev/zero of=$disk bs=$size count=1024";
|
|
}
|
|
|
|
# Set the umask so that the images are not world readable.
|
|
my $oldumask = umask;
|
|
umask(0077);
|
|
|
|
# run the image creation command
|
|
runCommand($image_cmd, \%CONFIG);
|
|
logprint("Done\n");
|
|
|
|
# Reset the umask to the previous value
|
|
umask($oldumask);
|
|
|
|
if ( !-e $disk )
|
|
{
|
|
logprint("The partition image creation failed to create $disk.\n");
|
|
logprint("aborting\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Finally create the filesystem / swap
|
|
#
|
|
if ( $partition->{ 'type' } eq 'swap' )
|
|
{
|
|
createSwap($disk);
|
|
}
|
|
else
|
|
{
|
|
createFilesystem( $disk, $partition->{ 'type' } );
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
This function is used if you want your new system be installed to a
|
|
physical drive (e.g. partition /dev/hda4) or to an already existing
|
|
logical volume (e.g. /dev/root_vg/xen_root_lv).
|
|
|
|
Walter Reiner
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub usePhysicalDevice
|
|
{
|
|
my $phys_img;
|
|
my $swap_img;
|
|
|
|
@PARTITIONS = ();
|
|
|
|
if ( defined $CONFIG{ 'swap-dev' } )
|
|
{
|
|
$swap_img = $CONFIG{ 'swap-dev' };
|
|
|
|
if ( !-e $swap_img )
|
|
{
|
|
logprint(
|
|
"The physical device or logical volume for swap-dev $swap_img doesn't exist. Aborting.\n"
|
|
);
|
|
logprint(
|
|
"NOTE: Please provide full path to your physical device or logical volume.\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
push( @PARTITIONS,
|
|
{ 'name' => 'swap',
|
|
'size' => '',
|
|
'type' => 'swap',
|
|
'mountpoint' => '',
|
|
'options' => '',
|
|
'imagetype' => 'phy:',
|
|
'image' => $swap_img
|
|
} ) unless ( $CONFIG{ 'noswap' } );
|
|
}
|
|
|
|
my $options = 'errors=remount-ro';
|
|
if ( $CONFIG{ 'fs' } eq 'xfs' )
|
|
{
|
|
$options = 'defaults';
|
|
}
|
|
|
|
if ( defined $CONFIG{ 'image-dev' } )
|
|
{
|
|
$phys_img = $CONFIG{ 'image-dev' };
|
|
|
|
push( @PARTITIONS,
|
|
{ 'name' => 'disk',
|
|
'size' => '',
|
|
'type' => $CONFIG{ 'fs' },
|
|
'mountpoint' => '/',
|
|
'options' => $options,
|
|
'imagetype' => 'phy:',
|
|
'image' => $phys_img
|
|
} );
|
|
}
|
|
else
|
|
{
|
|
logprint("No image-dev parameter given. Aborting.\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
createFilesystem( $phys_img, $CONFIG{ 'fs' } );
|
|
createSwap($swap_img) 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
|
|
{
|
|
|
|
#
|
|
# Check whether the disk volume exists already, and if so abort
|
|
# unless '--force' is specified.
|
|
#
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk = $CONFIG{ 'hostname' } . '-' . $partition->{ 'name' };
|
|
my $lvm_disk = "/dev/$CONFIG{'lvm'}/$disk";
|
|
|
|
if ( -e $lvm_disk )
|
|
{
|
|
|
|
# Delete if forcing
|
|
if ( $CONFIG{ 'force' } )
|
|
{
|
|
unless ( xenRunning($CONFIG{ 'hostname' }, \%CONFIG)) {
|
|
logprint(
|
|
"Removing $lvm_disk - since we're forcing the install\n");
|
|
runCommand("lvremove --force $lvm_disk", \%CONFIG);
|
|
runCommand("sync", \%CONFIG);
|
|
logprint(
|
|
"Sleeping a few seconds to avoid LVM race conditions...\n");
|
|
sleep(3);
|
|
} else {
|
|
fail("ERROR: Xen guest $CONFIG{'hostname'} appears to be running.\nAborting.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logprint("The LVM disk image already exists. Aborting.\n");
|
|
logprint("Specify '--force' to delete and recreate\n");
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
|
|
#
|
|
# For the calls to lvcreate below, we first need to check for the
|
|
# version of LVM running as their have been incompatible API
|
|
# changes somewhere between "2.02.95(2) (2012-03-06)" in Debian 7
|
|
# Wheezy and "2.02.111(2) (2014-09-01)" in Debian 8 Jessie. *sigh*
|
|
#
|
|
# See https://bugs.debian.org/754517
|
|
#
|
|
# I currently assume that all LVM version starting with 2.02.99
|
|
# should be fine with passing --yes as that version has an
|
|
# upstream changelog entry "Accept --yes in all commands so test
|
|
# scripts can be simpler".
|
|
#
|
|
# Assumes --yes is necessary if the LVM version can't be parsed.
|
|
|
|
my $lvm_needs_yes = 1;
|
|
my $lvm_version_output = `lvm version`;
|
|
if ($lvm_version_output =~ /^\s*LVM\s*version:\s*(\d[.\d]*)[\s(]/) {
|
|
my $lvm_version = $1;
|
|
my $no_yes_below_version = '2.02.99';
|
|
|
|
$lvm_needs_yes =
|
|
versioncmp($lvm_version, $no_yes_below_version) >= 0;
|
|
}
|
|
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk = $CONFIG{ 'hostname' } . '-' . $partition->{ 'name' };
|
|
my $lvm_disk = "/dev/$CONFIG{'lvm'}/$disk";
|
|
|
|
#
|
|
# Save the image path to the partitions array
|
|
#
|
|
$partition->{ 'imagetype' } = 'phy:';
|
|
$partition->{ 'image' } = $lvm_disk;
|
|
|
|
#
|
|
# The commands to create the volume.
|
|
#
|
|
my $disk_cmd =
|
|
"lvcreate $CONFIG{'lvm'} ".
|
|
($lvm_needs_yes ? '--yes' : '').
|
|
' '.
|
|
($CONFIG{ 'lvm_thin' } ?
|
|
"-T $CONFIG{'lvm'}/$CONFIG{'lvm_thin'} -V" :
|
|
'-L').
|
|
" $partition->{'size'} -n $disk";
|
|
|
|
#
|
|
# Create the volume
|
|
#
|
|
runCommand($disk_cmd, \%CONFIG);
|
|
|
|
#
|
|
# Make sure that worked.
|
|
#
|
|
if ( !-e $lvm_disk )
|
|
{
|
|
logprint(
|
|
"The LVM partition image creation failed to create $lvm_disk.\n"
|
|
);
|
|
logprint("aborting\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Finally create the filesystem / swap
|
|
#
|
|
if ( $partition->{ 'type' } eq 'swap' )
|
|
{
|
|
createSwap($lvm_disk);
|
|
}
|
|
else
|
|
{
|
|
createFilesystem( $lvm_disk, $partition->{ 'type' } );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
This function is responsible for creating two new ZFS volumes within
|
|
a given ZFS pool.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub createZFSBits
|
|
{
|
|
|
|
#
|
|
# Check whether the ZFS volume exists already, and if so abort
|
|
# unless '--force' is specified.
|
|
#
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk = $CONFIG{ 'hostname' } . '-' . $partition->{ 'name' };
|
|
my $zfs_disk = "/dev/$CONFIG{'zpool'}/$disk";
|
|
my $zfs_vol = "$CONFIG{'zpool'}/$disk";
|
|
|
|
if ( -e $zfs_disk )
|
|
{
|
|
|
|
# Delete if forcing
|
|
if ( $CONFIG{ 'force' } )
|
|
{
|
|
unless ( xenRunning($CONFIG{ 'hostname' }, \%CONFIG)) {
|
|
logprint(
|
|
"Removing $zfs_disk - since we're forcing the install\n");
|
|
runCommand("zfs destroy $zfs_vol", \%CONFIG);
|
|
runCommand("sync", \%CONFIG);
|
|
logprint(
|
|
"Sleeping a few seconds to avoid ZFS race conditions...\n");
|
|
sleep(3);
|
|
} else {
|
|
fail("ERROR: Xen guest $CONFIG{'hostname'} appears to be running.\nAborting.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logprint("The ZFS volume already exists. Aborting.\n");
|
|
logprint("Specify '--force' to delete and recreate\n");
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk = $CONFIG{ 'hostname' } . '-' . $partition->{ 'name' };
|
|
my $zfs_disk = "/dev/$CONFIG{'zpool'}/$disk";
|
|
my $zfs_vol = "$CONFIG{'zpool'}/$disk";
|
|
|
|
#
|
|
# Save the image path to the partitions array
|
|
#
|
|
$partition->{ 'imagetype' } = 'phy:';
|
|
$partition->{ 'image' } = $zfs_disk;
|
|
|
|
#
|
|
# The commands to create the volume.
|
|
#
|
|
my $disk_cmd =
|
|
"zfs create ".
|
|
($CONFIG{'image'} eq 'sparse' ? '-s' : '').
|
|
" -V $partition->{'size'} $zfs_vol";
|
|
|
|
#
|
|
# Create the volume
|
|
#
|
|
runCommand($disk_cmd, \%CONFIG);
|
|
sleep(2);
|
|
|
|
#
|
|
# Make sure that worked.
|
|
#
|
|
if ( !-e "$zfs_disk" )
|
|
{
|
|
logprint(
|
|
"The ZFS volume creation failed to create $zfs_disk.\n"
|
|
);
|
|
logprint("aborting\n");
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
#
|
|
# Finally create the filesystem / swap
|
|
#
|
|
if ( $partition->{ 'type' } eq 'swap' )
|
|
{
|
|
createSwap($zfs_disk);
|
|
}
|
|
else
|
|
{
|
|
createFilesystem( $zfs_disk, $partition->{ 'type' } );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
=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 extensions of evms.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub createEVMSBits
|
|
{
|
|
|
|
#
|
|
# 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.
|
|
#
|
|
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
|
|
# Check whether the EVMS volume already exists, abort unless '--force' is specified.
|
|
my $evms_volume_disk =
|
|
"/dev/evms/$CONFIG{'hostname'}-$partition->{'name'}";
|
|
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", \%CONFIG);
|
|
}
|
|
else
|
|
{
|
|
logprint(
|
|
"The EVMS volume $evms_volume_disk already exists. Aborting.\n"
|
|
);
|
|
logprint("Specify '--force' to delete and recreate\n");
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# 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'}-$partition->{'name'}";
|
|
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", \%CONFIG);
|
|
}
|
|
else
|
|
{
|
|
logprint(
|
|
"The EVMS object $evms_object_disk already exists. Aborting.\n"
|
|
);
|
|
logprint("Specify '--force' to delete and recreate\n");
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach my $partition (@PARTITIONS)
|
|
{
|
|
my $disk = $CONFIG{ 'hostname' } . '-' . $partition->{ 'name' };
|
|
my $evms_disk = "/dev/evms/$disk";
|
|
|
|
#
|
|
# Save the image path to the partitions array
|
|
#
|
|
$partition->{ 'imagetype' } = 'phy:';
|
|
$partition->{ 'image' } = $evms_disk;
|
|
|
|
#
|
|
# Modify the size to something reasonable
|
|
#
|
|
my $size = $partition->{ 'size' };
|
|
|
|
#
|
|
# Convert Gb -> Mb for the partition image size.
|
|
#
|
|
if ( $size =~ /^([0-9.]+)Gb*$/i )
|
|
{
|
|
$size = $1 * 1024 . "M";
|
|
}
|
|
|
|
#
|
|
# Final adjustments to sizing.
|
|
#
|
|
$size =~ s/Mb*$/k/i;
|
|
|
|
#
|
|
# 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
|
|
#
|
|
my $disk_cmd_volume =
|
|
"echo create : Volume, $CONFIG{'evms'}/$disk, name=$disk | evms";
|
|
|
|
#
|
|
# Create the volumes
|
|
#
|
|
runCommand($disk_cmd_object, \%CONFIG);
|
|
runCommand($disk_cmd_volume, \%CONFIG);
|
|
|
|
#
|
|
# Initialise the partition with the relevant filesystem.
|
|
#
|
|
if ( $partition->{ 'type' } eq 'swap' )
|
|
{
|
|
createSwap($disk_cmd_volume);
|
|
}
|
|
else
|
|
{
|
|
createFilesystem( $disk_cmd_volume, $partition->{ 'type' } );
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Format the given image in the users choice of filesystem.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub createFilesystem
|
|
{
|
|
my ( $image, $fs ) = (@_);
|
|
|
|
#
|
|
# We have the filesystem the user wanted, make sure that the
|
|
# binary exists.
|
|
#
|
|
my $command = $CONFIG{ "make_fs_" . $fs };
|
|
|
|
#
|
|
# Split the command into "binary" + "args". Make sure that
|
|
# the binary exists and is executable.
|
|
#
|
|
my ($binary, $args) = split(/ /, $command, 2);
|
|
|
|
if ( !defined( which($binary) ) )
|
|
{
|
|
logprint(
|
|
"The binary '$binary' required to create the filesystem $fs is missing\n"
|
|
);
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
|
|
unless ( $CONFIG{ 'dontformat' } ) {
|
|
#
|
|
# OK we have the command and the filesystem. Create it.
|
|
#
|
|
logprint("\nCreating $fs filesystem on $image\n");
|
|
|
|
$command .= " " . $image;
|
|
|
|
runCommand($command, \%CONFIG);
|
|
logprint("Done\n");
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Create the swap filesystem on the given device.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub createSwap
|
|
{
|
|
my ($path) = (@_);
|
|
|
|
logprint("\nCreating swap on $path\n");
|
|
|
|
runCommand("mkswap $path", \%CONFIG);
|
|
logprint("Done\n");
|
|
}
|
|
|
|
|
|
=begin doc
|
|
|
|
Mount the loopback disk image into a temporary directory.
|
|
|
|
Alternatively mount the relevant LVM volume instead.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub mountImage
|
|
{
|
|
|
|
#
|
|
# Create a temporary mount-point to use for the image/volume.
|
|
#
|
|
$MOUNT_POINT = tempdir( CLEANUP => 1 );
|
|
|
|
foreach my $partition (sort { length($a->{'mountpoint'}) <=> length($b->{'mountpoint'}) } @PARTITIONS)
|
|
{
|
|
if ( $partition->{ 'type' } ne 'swap' )
|
|
{
|
|
my $image = $partition->{ 'image' };
|
|
my $mountpoint = $MOUNT_POINT . $partition->{ 'mountpoint' };
|
|
|
|
mkpath( $mountpoint, 0, 0755 );
|
|
|
|
#
|
|
# Lookup the correct arguments to pass to mount.
|
|
#
|
|
my $mount_cmd;
|
|
my $mount_type = $CONFIG{ 'mount_fs_' . $partition->{ 'type' } };
|
|
|
|
#
|
|
# LVM partition
|
|
#
|
|
if ( $CONFIG{ 'lvm' } )
|
|
{
|
|
$mount_cmd = "mount $mount_type $image $mountpoint";
|
|
}
|
|
elsif ( $CONFIG{ 'evms' } )
|
|
{
|
|
$mount_cmd = "mount $mount_type $image $mountpoint";
|
|
}
|
|
elsif ( $CONFIG{ 'image-dev' } )
|
|
{
|
|
$mount_cmd = "mount $mount_type $image $mountpoint";
|
|
}
|
|
elsif ( $CONFIG{ 'zpool' } )
|
|
{
|
|
$mount_cmd = "mount $mount_type $image $mountpoint";
|
|
}
|
|
else
|
|
{
|
|
$mount_cmd = "mount $mount_type -o loop $image $mountpoint";
|
|
}
|
|
runCommand($mount_cmd, \%CONFIG);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Install the system, by invoking the xt-install-image script.
|
|
|
|
The script will be given the appropriate arguments from our environment.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub installSystem
|
|
{
|
|
|
|
#
|
|
#
|
|
# Basic command
|
|
#
|
|
my $cmd =
|
|
"xt-install-image --hostname=$CONFIG{'hostname'} --location=$MOUNT_POINT --dist=$CONFIG{'dist'} --install-method=$CONFIG{'install-method'}";
|
|
|
|
#
|
|
# Add on the install source if required.
|
|
#
|
|
$cmd .= " --install-source=$CONFIG{'install-source'}"
|
|
if ( defined( $CONFIG{ 'install-source' } ) );
|
|
|
|
#
|
|
# Do we have a per-image configuration file?
|
|
#
|
|
$cmd .= " --config=$CONFIG{'config'}" if ( defined( $CONFIG{ 'config' } ) );
|
|
|
|
#
|
|
# Add on the mirror, if defined
|
|
#
|
|
$cmd .= " --mirror=$CONFIG{'mirror'}" if ( defined( $CONFIG{ 'mirror' } ) );
|
|
|
|
#
|
|
# Add on the current cache setting
|
|
#
|
|
$cmd .= " --cache=$CONFIG{'cache'}" if length( $CONFIG{ 'cache' } );
|
|
|
|
#
|
|
# Add on the current cachedir setting
|
|
#
|
|
$cmd .= " --cachedir=$CONFIG{'cachedir'}" if length( $CONFIG{ 'cachedir' } );
|
|
|
|
#
|
|
# Propagate --verbose
|
|
#
|
|
if ( $CONFIG{ 'verbose' } )
|
|
{
|
|
$cmd .= " --verbose";
|
|
}
|
|
|
|
#
|
|
# Propagate --arch
|
|
#
|
|
if ( $CONFIG{ 'arch' } )
|
|
{
|
|
$cmd .= " --arch=$CONFIG{'arch'}";
|
|
}
|
|
|
|
#
|
|
# Propagate --keyring
|
|
#
|
|
if ( $CONFIG{ 'keyring' } )
|
|
{
|
|
$cmd .= " --keyring=$CONFIG{'keyring'}";
|
|
}
|
|
|
|
|
|
#
|
|
# Propagate --debootstrap-cmd if install-method is debootstrap
|
|
#
|
|
if ( $CONFIG{ 'install-method' } eq 'debootstrap' and
|
|
$CONFIG{ 'debootstrap-cmd' } )
|
|
{
|
|
$cmd .= " --debootstrap-cmd='$CONFIG{'debootstrap-cmd'}'";
|
|
}
|
|
|
|
|
|
#
|
|
# Propagate --apt_proxy
|
|
#
|
|
if ( $CONFIG{ 'apt_proxy' } )
|
|
{
|
|
$cmd .= " --apt_proxy=$CONFIG{'apt_proxy'}";
|
|
}
|
|
|
|
|
|
#
|
|
# Show the user what they are installing
|
|
#
|
|
logprint("Installation method: $CONFIG{'install-method'}\n");
|
|
|
|
#
|
|
# And where from, if relevant.
|
|
#
|
|
if ( ( lc( $CONFIG{ 'install-method' } ) eq "copy" ) ||
|
|
( lc( $CONFIG{ 'install-method' } ) eq "tar" ) )
|
|
{
|
|
logprint("(Source: $CONFIG{'install-source'})\n");
|
|
}
|
|
|
|
|
|
#
|
|
# Run the command.
|
|
#
|
|
runCommand($cmd, \%CONFIG);
|
|
logprint("Done\n");
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Export our configuratione variables as a series of environmental
|
|
variables.
|
|
|
|
This is required so that our hook and role scripts can easily
|
|
read the settings without access to the command line / configuration
|
|
file we were invoked with.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub exportEnvironment
|
|
{
|
|
|
|
#
|
|
# Export partitions array to configuration
|
|
#
|
|
exportPartitionsToConfig();
|
|
|
|
foreach my $key ( keys %CONFIG )
|
|
{
|
|
if ( defined( $CONFIG{ $key } ) )
|
|
{
|
|
my $envkey = $key;
|
|
$envkey =~ s/-/_/g;
|
|
$ENV{ $envkey } = $CONFIG{ $key };
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Run the xt-customise-system script to customize our fresh installation.
|
|
|
|
Before we do this we must pass all the relevant options into our
|
|
environment and mount /proc and /dev/pts.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub runCustomisationHooks
|
|
{
|
|
|
|
#
|
|
# Before running any scripts we'll mount /proc in the guest.
|
|
#
|
|
# 1. Make sure there is a directory.
|
|
mkdir( $MOUNT_POINT . "/proc", 0755 ) if ( !-d $MOUNT_POINT . "/proc" );
|
|
|
|
# 2. Mount
|
|
runCommand("mount -o bind /proc $MOUNT_POINT/proc", \%CONFIG);
|
|
|
|
#
|
|
# Before running any scripts we'll mount /dev/pts in the guest, too.
|
|
#
|
|
# 1. Make sure there is a directory.
|
|
mkdir( $MOUNT_POINT . "/dev", 0755 ) if ( !-d $MOUNT_POINT . "/dev" );
|
|
mkdir( $MOUNT_POINT . "/dev/pts", 0755 )
|
|
if ( !-d $MOUNT_POINT . "/dev/pts" );
|
|
|
|
# 2. Mount
|
|
runCommand("mount -t devpts devpts $MOUNT_POINT/dev/pts", \%CONFIG);
|
|
|
|
#
|
|
# 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 separate 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" );
|
|
}
|
|
}
|
|
|
|
#
|
|
# Copy dom0's resolv.conf to domU
|
|
#
|
|
mv("$MOUNT_POINT/etc/resolv.conf", "$MOUNT_POINT/etc/resolv.conf.old") if -f "$MOUNT_POINT/etc/resolv.conf";
|
|
cp("/etc/resolv.conf", "$MOUNT_POINT/etc/resolv.conf");
|
|
|
|
#
|
|
# 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, \%CONFIG);
|
|
logprint("Done\n");
|
|
|
|
#
|
|
# Restore domU's resolv.conf if needed
|
|
#
|
|
if (-f "$MOUNT_POINT/etc/resolv.conf") {
|
|
mv("$MOUNT_POINT/etc/resolv.conf.old", "$MOUNT_POINT/etc/resolv.conf");
|
|
} else {
|
|
unlink "$MOUNT_POINT/etc/resolv.conf";
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Find a useable IP address from the file /etc/xen-tools/ips.txt.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub findIP
|
|
{
|
|
|
|
# Abort if we don't have the IP file.
|
|
return undef if ( !-e $CONFIG{ 'ipfile' } );
|
|
|
|
#
|
|
# Open and read the file.
|
|
#
|
|
open( RANGE, "<", $CONFIG{ 'ipfile' } ) or
|
|
fail("Failed to read $CONFIG{'ipfile'} - $!");
|
|
my @lines = <RANGE>;
|
|
my @updated;
|
|
close(RANGE);
|
|
|
|
#
|
|
# Find an unclaimed line.
|
|
#
|
|
my $ip = undef;
|
|
foreach my $line (@lines)
|
|
{
|
|
|
|
# skip empty lines.
|
|
next if ( !defined($line) );
|
|
next if ( !length($line) );
|
|
chomp($line);
|
|
|
|
# find an IP.
|
|
if ( ( !defined($ip) ) && ( $line =~ /^([0-9\.]+)$/ ) )
|
|
{
|
|
$ip = $line;
|
|
$line = $ip . ": used";
|
|
}
|
|
|
|
push( @updated, $line );
|
|
}
|
|
|
|
#
|
|
# Now write out the new entries.
|
|
#
|
|
open( RANGE, ">", $CONFIG{ 'ipfile' } ) or
|
|
fail("Failed to write to $CONFIG{'ipfile'} - $!");
|
|
print RANGE join( "\n", @updated );
|
|
close(RANGE);
|
|
|
|
#
|
|
# Sanity check - handle the old case where the format of the file
|
|
# was different.
|
|
#
|
|
if ( defined($ip) )
|
|
{
|
|
my @tmp = split( /\./, $ip );
|
|
if ( scalar(@tmp) < 3 )
|
|
{
|
|
print <<EOF;
|
|
ERROR
|
|
-----
|
|
|
|
The $CONFIG{'ipfile'} file must contain full IP addresses, for example:
|
|
|
|
192.168.1.100
|
|
192.168.1.101
|
|
192.168.1.102
|
|
..
|
|
192.168.1.200
|
|
|
|
Aborting. Please update the file or specify an IP address manually.
|
|
|
|
EOF
|
|
$CONFIG{'FAIL'} = 1;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Return
|
|
#
|
|
return ($ip);
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Run *all* specified role scripts.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub runRoleScripts
|
|
{
|
|
my ($scripts) = (@_);
|
|
|
|
if ( !defined($scripts) )
|
|
{
|
|
logprint("\nNo role scripts were specified. Skipping\n");
|
|
return;
|
|
}
|
|
|
|
#
|
|
# OK we have at least one script specified. Split it up
|
|
# and try it out.
|
|
#
|
|
foreach my $name ( split( /,/, $scripts ) )
|
|
{
|
|
|
|
# ignore empty ones.
|
|
next if ( ( !defined($name) ) || ( !length($name) ) );
|
|
|
|
# strip leading + triling space.
|
|
$name =~ s/^\s+//;
|
|
$name =~ s/\s+$//;
|
|
|
|
# run the script
|
|
runRoleScript($name);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Run the specified role script.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub runRoleScript
|
|
{
|
|
my ($role) = (@_);
|
|
|
|
my $roleDir = $CONFIG{ 'roledir' };
|
|
|
|
#
|
|
# Role-script arguments are optional. If present prepare to
|
|
# append.
|
|
#
|
|
my $args = '';
|
|
$args = " " . $CONFIG{ 'role-args' } if ( $CONFIG{ 'role-args' } );
|
|
|
|
#
|
|
# The complete path to the role script
|
|
#
|
|
my $file = $role;
|
|
$file = $roleDir . "/" . $file unless $file =~ /\//;
|
|
|
|
if ( -x $file )
|
|
{
|
|
logprint("\nRole: $role\n");
|
|
logprint("\tFile: $file\n");
|
|
logprint("\tArgs: $args\n") if ( length($args) );
|
|
}
|
|
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 two arguments:
|
|
#
|
|
# - The mountpoint to the new system.
|
|
# - Any, optional, supplied arguments.
|
|
#
|
|
# NOTE: Space added to $args as prefix ..
|
|
#
|
|
runCommand( $file . " " . $MOUNT_POINT . $args, \%CONFIG );
|
|
|
|
logprint("Role script completed.\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
|
|
{
|
|
|
|
#
|
|
# Configuration file we'll create
|
|
#
|
|
my $dir = $CONFIG{ 'output' };
|
|
my $ext = $CONFIG{ 'extension' };
|
|
my $file = $dir . "/" . $CONFIG{ 'hostname' } . $ext;
|
|
|
|
|
|
#
|
|
# Abort if it exists.
|
|
#
|
|
if ( -e $file )
|
|
{
|
|
unless ( $CONFIG{ 'force' } )
|
|
{
|
|
logprint("The Xen configuration file $file exists\n");
|
|
logprint("Specify --force to force overwriting it.\n");
|
|
logprint("Aborting\n");
|
|
# FAIL = 2 will keep existing config file
|
|
$CONFIG{'FAIL'} = 2;
|
|
exit 127;
|
|
}
|
|
}
|
|
|
|
#
|
|
# Create the config.
|
|
#
|
|
my $command = "xt-create-xen-config --output=$dir --extension=$ext";
|
|
|
|
#
|
|
# Add the template if specified
|
|
#
|
|
if ( ( defined( $CONFIG{ 'template' } ) ) &&
|
|
( -e $CONFIG{ 'template' } ) )
|
|
{
|
|
$command .= " --template=" . $CONFIG{ 'template' };
|
|
}
|
|
|
|
#
|
|
# Add the admins, if any.
|
|
#
|
|
if ( defined( $CONFIG{ 'admins' } ) )
|
|
{
|
|
$command .= " --admins=$CONFIG{'admins'}";
|
|
}
|
|
|
|
#
|
|
# Make sure the template gets a list of all IPs
|
|
#
|
|
$ENV{ 'ips' } = $IP_ADDRESSES;
|
|
|
|
|
|
logprint("\nCreating Xen configuration file\n");
|
|
runCommand($command, \%CONFIG);
|
|
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 ( $CONFIG{ 'passwd' } )
|
|
{
|
|
if ( -x $MOUNT_POINT . "/usr/bin/passwd" )
|
|
{
|
|
my $tryagain = 1;
|
|
my $term = Term::ReadLine->new('Password change failed');
|
|
while ($tryagain) {
|
|
my $rc = system("chroot $MOUNT_POINT /usr/bin/passwd");
|
|
if ($rc >> 8) {
|
|
$tryagain = $term->ask_yn(
|
|
prompt => 'Do you want to try to change the password again??',
|
|
default => 'y',
|
|
);
|
|
} else {
|
|
$tryagain=0;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logprint("'passwd' command not found in the new install.\n");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
logprint("Generating a password for the new guest.\n");
|
|
#
|
|
# Replace the password in the /etc/shadow file
|
|
#
|
|
my $shadow_path = $MOUNT_POINT . '/etc/shadow';
|
|
if ( -e $shadow_path )
|
|
{
|
|
#
|
|
# Generate a password, salt and use that to generating a hash
|
|
#
|
|
if ( defined( $CONFIG{ 'password' } ) )
|
|
{
|
|
$PASSWORD = $CONFIG { 'password' };
|
|
}
|
|
elsif ( $CONFIG{ 'genpass' } )
|
|
{
|
|
$PASSWORD = generatePassword( $CONFIG{ 'genpass_len' } );
|
|
}
|
|
else
|
|
{
|
|
fail("oops... neither passwd nor password nor genpass are set, should not happen!");
|
|
}
|
|
|
|
my $salt = generatePassword(8);
|
|
|
|
my $hash_method;
|
|
if ($CONFIG{ 'hash_method' } eq 'md5')
|
|
{
|
|
$hash_method = '$1$';
|
|
}
|
|
elsif ($CONFIG{ 'hash_method' } eq 'sha256')
|
|
{
|
|
$hash_method = '$5$';
|
|
}
|
|
elsif ($CONFIG{ 'hash_method' } eq 'sha512')
|
|
{
|
|
$hash_method = '$6$';
|
|
}
|
|
else
|
|
{
|
|
fail("oops... unknown hashing method, should not happen!");
|
|
}
|
|
|
|
my $hash = crypt($PASSWORD, $hash_method . $salt);
|
|
|
|
#
|
|
# Copy the file to ensure the original retains the correct
|
|
# permissions set by the System
|
|
#
|
|
my $tmp_shadow_path = "$shadow_path.tmp";
|
|
cp("$shadow_path","$tmp_shadow_path");
|
|
open(TMP, "<", $tmp_shadow_path) or fail($!);
|
|
open(SHADOW, ">", $shadow_path) or fail($!);
|
|
my $line;
|
|
while(defined($line = <TMP>))
|
|
{
|
|
$line =~ s#^root:[^:]*:#root:$hash:#;
|
|
print SHADOW $line;
|
|
}
|
|
|
|
#
|
|
# Close the files and delete the temporary file
|
|
#
|
|
close(SHADOW);
|
|
close(TMP);
|
|
unlink($tmp_shadow_path);
|
|
}
|
|
else
|
|
{
|
|
logprint("Failed to find /etc/passwd in the install.\n");
|
|
}
|
|
}
|
|
}
|
|
|
|
=begin doc
|
|
|
|
create a random "string"
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub generatePassword {
|
|
my $length = $_[0];
|
|
unless ($length and $length > 0) {
|
|
warn "generatePassword: No (sane) password length given. Using $default_genpass_len instead.";
|
|
$length = $default_genpass_len;
|
|
}
|
|
my $possible = 'abcdefghijkmnpqrstuvwxyz23456789ABCDEFGHJKLMNPQRSTUVWXYZ';
|
|
my $password = '';
|
|
while (length($password) < $length) {
|
|
$password .= substr($possible, (int(rand(length($possible)))), 1);
|
|
}
|
|
return $password;
|
|
}
|
|
|
|
|
|
=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, $fail_ok) = (@_);
|
|
|
|
#
|
|
# First we unmount /proc and /dev/pts in the guest install.
|
|
#
|
|
runCommand("umount $point/proc", \%CONFIG, $fail_ok);
|
|
#runCommand("umount $point/dev/pts", \%CONFIG, $fail_ok);
|
|
|
|
#
|
|
# Open /proc/mount and get a list of currently mounted paths
|
|
# which begin with our mount point.
|
|
#
|
|
my @points;
|
|
|
|
open( MOUNTED, "<", "/proc/mounts" ) or
|
|
fail("Failed to open mount list");
|
|
foreach my $line (<MOUNTED>)
|
|
{
|
|
|
|
#
|
|
# 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", \%CONFIG, $fail_ok);
|
|
}
|
|
|
|
$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 clean_up () {
|
|
if ( defined($MOUNT_POINT) )
|
|
{
|
|
unMountImage($MOUNT_POINT, 1);
|
|
}
|
|
}
|
|
|
|
END
|
|
{
|
|
# Capture exit code
|
|
my $exitcode = $?;
|
|
|
|
exit $exitcode if $VERSION || $HELP || $MANUAL || $DUMPCONFIG;
|
|
|
|
my %host_key = ();
|
|
#
|
|
# Unmount the image if it is still mounted.
|
|
#
|
|
if ( defined($MOUNT_POINT) )
|
|
{
|
|
#
|
|
# Before we unmount get the host's SSH keys' fingerprints
|
|
#
|
|
my $key_dir = $MOUNT_POINT.'/etc/ssh';
|
|
my @pubkey_files =
|
|
grep { /^ssh_host_.*\.pub$/; } read_dir($key_dir);
|
|
foreach my $pubkey_file (@pubkey_files) {
|
|
my $pubkey_path = "$key_dir/$pubkey_file";
|
|
my $fingerprint_line = `ssh-keygen -lf "$pubkey_path"`;
|
|
if ($fingerprint_line =~ /^(\S+)\s+(\S+)/ ) {
|
|
my $fingerprint = $2;
|
|
my $algo = '[unspecified hashing algorithm]';
|
|
if ($fingerprint_line =~ /^\S+\s+\S+\s+\S+\s+\((\S+)\)/ ) {
|
|
$algo = $1;
|
|
} elsif ($pubkey_file =~ /^ssh_host_(\S+)_key\.pub$/) {
|
|
$algo = uc($1);
|
|
} elsif ($pubkey_file eq 'ssh_host_key.pub') {
|
|
$algo = 'SSH1';
|
|
}
|
|
$host_key{$algo} = $fingerprint;
|
|
} else {
|
|
warn "Can't parse ssh-keygen output: $fingerprint_line";
|
|
}
|
|
}
|
|
unMountImage($MOUNT_POINT, $CONFIG{'FAIL'});
|
|
}
|
|
|
|
#
|
|
# If we're supposed to start the new instance do so - note here we
|
|
# have to unmount the image first.
|
|
#
|
|
if ( $CONFIG{ 'boot' } and !$CONFIG{'FAIL'} )
|
|
{
|
|
#
|
|
# If there is an /etc/xen/auto directory then link in the
|
|
# domain so that it will automatically restart, if it isn't
|
|
# already present.
|
|
#
|
|
# (Will be present if this is overwriting a previous image,
|
|
# for example.)
|
|
#
|
|
if ( -d "/etc/xen/auto" )
|
|
{
|
|
my $cfg =
|
|
$CONFIG{ 'output' } . "/" . $CONFIG{ 'hostname' } .
|
|
$CONFIG{ 'extension' };
|
|
|
|
if ( !-e $cfg )
|
|
{
|
|
logprint("Creating auto-start symlink to: $cfg\n");
|
|
|
|
my $link = "ln -s $cfg /etc/xen/auto/";
|
|
runCommand($link, \%CONFIG);
|
|
}
|
|
}
|
|
|
|
#
|
|
#
|
|
# Start the image
|
|
#
|
|
|
|
# Config file.
|
|
my $cfg =
|
|
$CONFIG{ 'output' } . "/" . $CONFIG{ 'hostname' } .
|
|
$CONFIG{ 'extension' };
|
|
|
|
# Start the DomU
|
|
runCommand("$CONFIG{'xm'} create $cfg", \%CONFIG);
|
|
|
|
logprint("Started new Xen guest: $CONFIG{'hostname'} [$cfg]\n");
|
|
}
|
|
|
|
#
|
|
# Here we print out the status message when finishing.
|
|
#
|
|
# NOTE: We use the $CONFIG{'pid'} here to control whether the
|
|
# message is displayed - since this avoids it from being displayed
|
|
# twice when --boot is used.
|
|
#
|
|
if ( ( defined( $CONFIG{ 'hostname' } ) ) &&
|
|
( -e "/var/log/xen-tools/$CONFIG{'hostname'}.log" ) &&
|
|
( !$CONFIG{ 'pid' } ) )
|
|
{
|
|
print "\n\nLogfile produced at:\n";
|
|
print "\t /var/log/xen-tools/$CONFIG{'hostname'}.log\n";
|
|
}
|
|
|
|
#
|
|
# Did we fail? If so then we should remove the broken installation,
|
|
# unless "--keep" was specified.
|
|
#
|
|
# If we didn't fail, then we assume we succeeded, print a summary
|
|
#
|
|
# $CONFIG{'FAIL'} = 0 - Success
|
|
# $CONFIG{'FAIL'} = 1 - Failed to install, delete the image
|
|
# $CONFIG{'FAIL'} = 2 - Files exist, either .cfg or lvm... etc
|
|
if ( $CONFIG{'FAIL'} == 1 && ( !$CONFIG{ 'keep' } ) )
|
|
{
|
|
|
|
#
|
|
# Run the command
|
|
#
|
|
$CONFIG{ 'verbose' } &&
|
|
logprint("Removing failed install: $CONFIG{'hostname'}\n");
|
|
|
|
if ($CONFIG{ 'hostname' }) {
|
|
my $option = '';
|
|
if ($CONFIG{ 'lvm' }) {
|
|
$option = "--lvm=$CONFIG{'lvm'}"
|
|
} elsif ($CONFIG{ 'evms' }) {
|
|
$option = "--evms=$CONFIG{'evms'}"
|
|
} elsif ($CONFIG{ 'dir' }) {
|
|
$option = "--dir=$CONFIG{'dir'}"
|
|
} elsif ($CONFIG{ 'zpool' }) {
|
|
$option = "--zpool=$CONFIG{'zpool'}"
|
|
}
|
|
|
|
|
|
if ($option) {
|
|
runCommand("xen-delete-image $option --hostname=$CONFIG{'hostname'}", \%CONFIG);
|
|
} else {
|
|
die "Assertion that either --dir, --lvm, --dir or --zpool are given".
|
|
" failed.\nThis is probably a bug, please report it.";
|
|
}
|
|
}
|
|
} elsif ( $CONFIG{'FAIL'} == 0 ) {
|
|
#
|
|
# Assume success
|
|
#
|
|
logprint("\nInstallation Summary\n");
|
|
logprint("---------------------\n");
|
|
logprint("Hostname : $CONFIG{'hostname'}\n");
|
|
logprint("Distribution : $CONFIG{'dist'}\n");
|
|
logprint("MAC Address : $CONFIG{'mac'}\n");
|
|
logprint("IP Address(es) : ");
|
|
if ( $CONFIG{ 'dhcp' } ) {
|
|
logprint("dynamic");
|
|
} elsif( $CONFIG{ 'ip' } ) {
|
|
logprint( $IP_ADDRESSES );
|
|
}
|
|
logprint("\n");
|
|
foreach my $algo (sort keys %host_key) {
|
|
logprint("SSH Fingerprint : $host_key{$algo} ($algo)\n");
|
|
}
|
|
logprint("Root Password : ");
|
|
if ( $PASSWORD ) {
|
|
logprint("$PASSWORD\n");
|
|
} else {
|
|
logprint ("N/A\n");
|
|
}
|
|
logprint("\n");
|
|
}
|
|
|
|
exit $exitcode;
|
|
}
|