Temporarily use resolv.conf from dom0 when chrooting into domU, to be able to run apt-get and yum inside chroot. This permits to use different nameservers in domU, and does not break install nor offline update from dom0.
513 lines
9.9 KiB
Perl
Executable File
513 lines
9.9 KiB
Perl
Executable File
#!/usr/bin/perl -w
|
|
|
|
=head1 NAME
|
|
|
|
xen-update-image - Update the software installed upon offline Xen images.
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
xen-update-image [options] imageName1 imageName2 .. imageNameN
|
|
|
|
Help Options:
|
|
--help Show this scripts help information.
|
|
--manual Read this scripts manual.
|
|
--version Show the version number and exit.
|
|
|
|
General Options:
|
|
--dir Specify the directory which contains the image(s).
|
|
--lvm Specify the LVM volume group which contains the image(s).
|
|
--evms Specify the EVMS container which contains the image(s).
|
|
|
|
=cut
|
|
|
|
|
|
|
|
=head1 OPTIONS
|
|
|
|
=over 8
|
|
|
|
=item B<--dir>
|
|
Specify the directory which contains the image(s).
|
|
|
|
=item B<--evms>
|
|
Specify the EVMS container which contains the image(s).
|
|
|
|
=item B<--help>
|
|
Show the script help.
|
|
|
|
=item B<--lvm>
|
|
Specify the LVM volume group which contains the image(s).
|
|
|
|
=item B<--manual>
|
|
Read the manual.
|
|
|
|
=item B<--version>
|
|
Show the version number and exit.
|
|
|
|
=back
|
|
|
|
=cut
|
|
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
xen-update-image is a simple script which allows you to update
|
|
a Xen image of Debian which has been created with xen-create-image.
|
|
|
|
It does this by mounting the image inside a temporary directory
|
|
then running:
|
|
|
|
apt-get update
|
|
|
|
apt-get upgrade
|
|
|
|
NOTE If the image is already running within Xen this will cause
|
|
corruption otherwise it will allow you to update your image without
|
|
booting it.
|
|
|
|
=cut
|
|
|
|
|
|
=head1 EXAMPLES
|
|
|
|
The following assumes there are two images which are not currently
|
|
running. The images are called 'test.my.flat', and 'x11.my.flat'.
|
|
|
|
Updating both images can be accomplished by executing:
|
|
|
|
xen-update-images --dir=/home/xen test.my.flat x11.my.flat
|
|
|
|
=cut
|
|
|
|
|
|
=head1 AUTHOR
|
|
|
|
|
|
Steve
|
|
--
|
|
http://www.steve.org.uk/
|
|
|
|
=cut
|
|
|
|
|
|
=head1 LICENSE
|
|
|
|
Copyright (c) 2005-2009 by Steve Kemp. All rights reserved.
|
|
|
|
This module is free software;
|
|
you can redistribute it and/or modify it under
|
|
the same terms as Perl itself.
|
|
The LICENSE file contains the full text of the license.
|
|
|
|
=cut
|
|
|
|
|
|
use strict;
|
|
use English;
|
|
use File::Temp qw/ tempdir /;
|
|
use File::Copy qw/ mv cp /;
|
|
use Getopt::Long;
|
|
use Pod::Usage;
|
|
|
|
|
|
#
|
|
# Configuration options, initially read from the configuration file
|
|
# but may be overridden by the command line.
|
|
#
|
|
# Command line flags *always* take precedence over the configuration file.
|
|
#
|
|
my %CONFIG;
|
|
|
|
#
|
|
# Release number.
|
|
#
|
|
my $RELEASE = '4.2beta1';
|
|
|
|
|
|
#
|
|
# Read configuration file if it exists.
|
|
#
|
|
if ( -e "/etc/xen-tools/xen-tools.conf" )
|
|
{
|
|
readConfigurationFile("/etc/xen-tools/xen-tools.conf");
|
|
}
|
|
|
|
|
|
#
|
|
# Parse command line arguments, these override the values from the
|
|
# configuration file.
|
|
#
|
|
parseCommandLineArguments();
|
|
|
|
|
|
#
|
|
# Test that our arguments are sane.
|
|
#
|
|
checkArguments();
|
|
|
|
|
|
#
|
|
# Abort if non-root user.
|
|
#
|
|
if ( $EFFECTIVE_USER_ID != 0 )
|
|
{
|
|
print <<E_O_ROOT;
|
|
|
|
This script is not running with root privileges.
|
|
|
|
root privileges are required to successfully mount the disk image(s).
|
|
|
|
E_O_ROOT
|
|
|
|
exit;
|
|
}
|
|
|
|
|
|
|
|
#
|
|
# Loop over the supplied arguments, and attempt to update each image.
|
|
#
|
|
while ( my $name = shift )
|
|
{
|
|
if ( !xenRunning($name) )
|
|
{
|
|
updateXenImage($name);
|
|
}
|
|
else
|
|
{
|
|
print "Skipping xen guest '$name' - it appears to be running.\n";
|
|
}
|
|
}
|
|
|
|
|
|
#
|
|
# All done.
|
|
#
|
|
exit;
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Mount the primary disk image, so that we're ready to update it.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub updateXenImage
|
|
{
|
|
my ($name) = (@_);
|
|
|
|
#
|
|
# Create a temporary directory, and prepare to mount the
|
|
# image there.
|
|
#
|
|
my $tmp = tempdir( CLEANUP => 1 );
|
|
my $img = '';
|
|
my $mount_cmd = '';
|
|
|
|
#
|
|
# If we're dealing with loopback images find the main one,
|
|
# and mount it.
|
|
#
|
|
if ( $CONFIG{ 'dir' } )
|
|
{
|
|
|
|
# The loopback image.
|
|
$img = $CONFIG{ 'dir' } . "/domains/" . $name . "/disk.img";
|
|
|
|
if ( !-e $img )
|
|
{
|
|
print "Disk image '$img' for host '$name' not found\n";
|
|
return;
|
|
}
|
|
|
|
$mount_cmd = "mount -t auto -o loop $img $tmp";
|
|
}
|
|
elsif ( $CONFIG{ 'lvm' } )
|
|
{
|
|
|
|
# The LVM volume
|
|
$img = "/dev/" . $CONFIG{ 'lvm' } . "/$name-disk";
|
|
|
|
# make sure it exists.
|
|
if ( !-e $img )
|
|
{
|
|
print "Logical volume '$img' for host '$name' not found\n";
|
|
return;
|
|
}
|
|
|
|
$mount_cmd = "mount -t auto $img $tmp";
|
|
}
|
|
elsif ( $CONFIG{ 'evms' } )
|
|
{
|
|
|
|
# The EVMS volume -- note, unlike LVM, you don't need the
|
|
# $CONFIG{'evms'} to see it and mount the
|
|
# volume. $CONFIG{'evms'} is only used for manipulating the
|
|
# underlying object. Still, I don't want to mess with the
|
|
# parse code and make it confusing - otherwise --evms takes an
|
|
# argument everywhere but here, which will confuse users. The
|
|
# better solution is to make it so that --evms can take a
|
|
# following container, but doesn't require it. For the
|
|
# moment, it is better to leave it as it is, take a container,
|
|
# and then ignore it.
|
|
|
|
# The best way to do it is to just read it out of the
|
|
# configuration file, tell the user what you got and where you
|
|
# got it from, and not bother the user with picking --dir or
|
|
# --lvm or --evms at all, but infer it from the config file's
|
|
# disk = parameter. xen-delete-image might work the same way,
|
|
# but it could be *slightly* more dangerous in the context of
|
|
# deleting.
|
|
$img = "/dev/evms/$name-disk";
|
|
|
|
# make sure it exists.
|
|
if ( !-e $img )
|
|
{
|
|
print "EVMS volume '$img' for host '$name' not found\n";
|
|
return;
|
|
}
|
|
|
|
$mount_cmd = "mount -t auto $img $tmp";
|
|
}
|
|
else
|
|
{
|
|
die "Can't happen?\n";
|
|
}
|
|
|
|
#
|
|
# Mount the image.
|
|
#
|
|
`$mount_cmd`;
|
|
|
|
#
|
|
# Make sure this is a Debian image.
|
|
#
|
|
if ( ( -e $tmp . "/usr/bin/apt-get" ) &&
|
|
( -x $tmp . "/usr/bin/apt-get" ) )
|
|
{
|
|
#
|
|
# Copy dom0's resolv.conf to domU
|
|
#
|
|
mv("$tmp/etc/resolv.conf", "$tmp/etc/resolv.conf.old") if -f "$tmp/etc/resolv.conf";
|
|
cp("/etc/resolv.conf", "$tmp/etc/resolv.conf");
|
|
|
|
#
|
|
# Now run the update command.
|
|
#
|
|
system("chroot $tmp /usr/bin/apt-get update");
|
|
|
|
|
|
#
|
|
# Now upgrade
|
|
#
|
|
system(
|
|
"DEBIAN_FRONTEND=noninteractive chroot $tmp /usr/bin/apt-get upgrade --yes --force-yes"
|
|
);
|
|
|
|
#
|
|
# Restore domU's resolv.conf if needed
|
|
#
|
|
if (-f "$tmp/etc/resolv.conf") {
|
|
mv("$tmp/etc/resolv.conf.old", "$tmp/etc/resolv.conf");
|
|
} else {
|
|
unlink "$tmp/etc/resolv.conf";
|
|
}
|
|
}
|
|
else
|
|
{
|
|
print "Xen image $name is not a Debian GNU/Linux image. Skipping\n";
|
|
}
|
|
|
|
|
|
#
|
|
# Unmount
|
|
#
|
|
`umount -l $tmp`;
|
|
`umount $tmp 2>/dev/null >/dev/null`;
|
|
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Test to see if the given instance is running.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub xenRunning
|
|
{
|
|
my ($hostname) = (@_);
|
|
|
|
my $running = 0;
|
|
|
|
open( CMD, "xm list 2>/dev/null |" ) or
|
|
die "Failed to run 'xm list $hostname'";
|
|
while (<CMD>)
|
|
{
|
|
my $line = $_;
|
|
$running = 1 if ( $line =~ /\Q$hostname\E/ );
|
|
}
|
|
close(CMD);
|
|
|
|
return ($running);
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Read the configuration file specified.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub readConfigurationFile
|
|
{
|
|
my ($file) = (@_);
|
|
|
|
open( FILE, "<", $file ) or die "Cannot read file '$file' - $!";
|
|
|
|
my $line = "";
|
|
|
|
while ( defined( $line = <FILE> ) )
|
|
{
|
|
chomp $line;
|
|
if ( $line =~ s/\\$// )
|
|
{
|
|
$line .= <FILE>;
|
|
redo unless eof(FILE);
|
|
}
|
|
|
|
# Skip lines beginning with comments
|
|
next if ( $line =~ /^([ \t]*)\#/ );
|
|
|
|
# Skip blank lines
|
|
next if ( length($line) < 1 );
|
|
|
|
# Strip trailing comments.
|
|
if ( $line =~ /(.*)\#(.*)/ )
|
|
{
|
|
$line = $1;
|
|
}
|
|
|
|
# Find variable settings
|
|
if ( $line =~ /([^=]+)=([^\n]+)/ )
|
|
{
|
|
my $key = $1;
|
|
my $val = $2;
|
|
|
|
# Strip leading and trailing whitespace.
|
|
$key =~ s/^\s+//;
|
|
$key =~ s/\s+$//;
|
|
$val =~ s/^\s+//;
|
|
$val =~ s/\s+$//;
|
|
|
|
# command expansion?
|
|
if ( $val =~ /(.*)`([^`]+)`(.*)/ )
|
|
{
|
|
|
|
# store
|
|
my $pre = $1;
|
|
my $cmd = $2;
|
|
my $post = $3;
|
|
|
|
# get output
|
|
my $output = `$cmd`;
|
|
chomp($output);
|
|
|
|
# build up replacement.
|
|
$val = $pre . $output . $post;
|
|
}
|
|
|
|
# Store value.
|
|
$CONFIG{ $key } = $val;
|
|
}
|
|
}
|
|
|
|
close(FILE);
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Parse the arguments specified upon the command line.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub parseCommandLineArguments
|
|
{
|
|
my $HELP = 0;
|
|
my $MANUAL = 0;
|
|
my $VERSION = 0;
|
|
|
|
# Parse options.
|
|
#
|
|
GetOptions( "dir=s", \$CONFIG{ 'dir' }, "lvm=s", \$CONFIG{ 'lvm' },
|
|
"evms=s", \$CONFIG{ 'evms' }, "help", \$HELP,
|
|
"manual", \$MANUAL, "version", \$VERSION
|
|
);
|
|
|
|
pod2usage(1) if $HELP;
|
|
pod2usage( -verbose => 2 ) if $MANUAL;
|
|
|
|
if ($VERSION)
|
|
{
|
|
my $REVISION = '$Revision: 1.29 $';
|
|
|
|
if ( $REVISION =~ /1.([0-9.]+) / )
|
|
{
|
|
$REVISION = $1;
|
|
}
|
|
print "xen-update-image release $RELEASE - CVS: $REVISION\n";
|
|
exit;
|
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
=begin doc
|
|
|
|
Test that the options we received from the command line, or our
|
|
configuration file, make sense.
|
|
|
|
=end doc
|
|
|
|
=cut
|
|
|
|
sub checkArguments
|
|
{
|
|
|
|
#
|
|
# Make sure we got one and only one installation method.
|
|
#
|
|
my $count = 0;
|
|
foreach my $type (qw/dir lvm evms/)
|
|
{
|
|
$count += 1 if defined( $CONFIG{ $type } );
|
|
}
|
|
|
|
#
|
|
# Show a decent error for when either zero or more than one options
|
|
# were selected.
|
|
#
|
|
if ( $count != 1 )
|
|
{
|
|
print "Please select one and only one of the installation methods:\n";
|
|
print " --dir\n";
|
|
print " --evms\n";
|
|
print " --lvm\n";
|
|
exit;
|
|
}
|
|
}
|