initial commit of file from CVS for e-smith-backup on Thu 26 Oct 11:24:24 BST 2023

This commit is contained in:
2023-10-26 11:24:24 +01:00
parent bb6b15a5a9
commit fe41ccadec
81 changed files with 9900 additions and 2 deletions

View File

@@ -0,0 +1,61 @@
package esmith::console::backup_running;
use strict;
use warnings;
use esmith::ConfigDB;
use Locale::gettext;
sub new
{
my $class = shift;
my $self = {};
bless $self, $class;
return $self;
}
sub doit
{
my ($self, $console, $db) = @_;
#-------------------------------------------------------------
# check whether a backup in process and incomplete
#-------------------------------------------------------------
my $restore_db = esmith::ConfigDB->open_ro("/etc/e-smith/restore");
return unless $restore_db;
my $restore_state = $restore_db->get_prop('restore', 'state') || 'idle';
return unless ($restore_state eq 'running');
my ($rc, $choice) = $console->message_page
(
title => gettext("Inconsistent system state"),
text =>
gettext("********** Inconsistent system state detected ***********") .
"\n\n" .
gettext("The restoration of a system backup was running and incomplete at the time of the last reboot. The system should not be used in this state.") .
"\n\n" .
gettext("Consult the User Guide for further instructions."),
);
($rc, $choice) = $console->yesno_page
(
title => gettext("System will be halted"),
text =>
gettext("The server will now be halted.") .
"\n\n" .
gettext("Consult the User Guide for recovery instructions.") .
"\n\n" .
gettext("Do you wish to halt the system right now?"),
);
return unless ($rc == 0);
system("/usr/bin/tput", "clear");
system("/sbin/e-smith/signal-event", "halt");
# A bit of a hack to avoid the console restarting before the
# reboot takes effect.
sleep(600);
}
1;

View File

@@ -0,0 +1,294 @@
package esmith::console::perform_backup;
use strict;
use warnings;
use esmith::ConfigDB;
use esmith::console;
use esmith::util;
use Locale::gettext;
use esmith::Backup;
use Carp;
use feature qw( say );
use esmith::BlockDevices;
use POSIX qw(:sys_wait_h strftime);
use File::stat;
use Taint::Util;
my $EMPTY = q{};
# lock file.. see bug 9127
my $backup_lock;
sub new
{
my $class = shift;
my $self = {
name => gettext("Perform backup to removable media"),
order => 80,
};
bless $self, $class;
return $self;
}
sub name
{
return $_[0]->{name};
}
sub order
{
return $_[0]->{order};
}
sub backup_size
{
my $self = shift;
}
# subs to set and remove lock on backup
sub SetLock
{
print "Setting backup lock file\n";
$backup_lock = esmith::Backup::set_lock;
return $backup_lock;
}
sub RemoveLock
{
if (defined($backup_lock))
{
print "Removing backup lock file\n";
esmith::Backup::remove_lock($backup_lock);
$backup_lock = undef;
}
}
sub make_backup_callback
{
my ($device, $CompressionLevel) = @_;
return sub {
my $fh = shift;
my @backup_list = esmith::Backup->restore_list;
my @backup_excludes = esmith::Backup->excludes;
my $backup_size = backupSize (@backup_list);
# set lock.. if not, exit
unless (SetLock()) {
die "Error: failed to create lock file.. is a backup already running?";
}
open(OLDSTDOUT, ">&STDOUT");
unless (open(STDOUT, ">$device/smeserver.tgz"))
{
return gettext("Could not create backup file on device").": $!\n";
}
open(OLDSTDERR, ">&STDERR");
my $logger = open(STDERR, "|-");
die "Can't fork: $!\n" unless defined $logger;
unless ($logger)
{
exec qw(/usr/bin/logger -p local1.info -t console_backup);
}
my $status = 0;
my $gzip = open(GZIP, "|-");
return "could not run gzip" unless defined $gzip;
unless ($gzip)
{
close $fh;
exec "gzip", $CompressionLevel;
}
my $pv = open(PV, "|-");
return "could not run pv" unless defined $pv;
unless ($pv)
{
open(STDOUT, ">&GZIP");
close GZIP;
open(STDERR, ">&$fh");
exec qw(pv -i 0.2 -n -s), $backup_size
}
my $tar = fork;
return "could not run tar" unless defined $tar;
unless ($tar)
{
open(STDOUT, ">&PV");
close PV;
close GZIP;
close $fh;
chdir "/";
#Create the archive
my @directories = grep { -e $_ } @backup_list;
my @exclude = map ("--exclude=$_",@backup_excludes);
exec ("/bin/tar cf - @directories @exclude");
}
waitpid($tar, 0);
warn "status from tar was $?\n" if $?;
unless (close PV)
{
$status |= $! ? $! : $?;
warn "status from pv is $status\n" if $status;
}
unless (close GZIP)
{
$status |= $! ? $! : $?;
warn "status from gzip is $status\n" if $status;
}
open(STDOUT, ">&OLDSTDOUT");
open(STDERR, ">&OLDSTDERR");
close(OLDSTDERR);
close(OLDSTDOUT);
RemoveLock();
return $status ? gettext("Backup failed. Look at the log files for more details.") : gettext("Backup successfully created.");
};
}
sub doit
{
my ($self, $console, $db) = @_;
my @backup_list = esmith::Backup->restore_list;
my @backup_excludes = esmith::Backup->excludes;
my $compressionLevel = $db->get_prop('backupconsole', 'CompressionLevel') || '-6';
my $mountpoint = $db->get_prop('backupconsole', 'Mountpoint') || '/mnt/bootstrap-console-backup';
my $allowMounted = $db->get_prop('backupconsole', 'AllowMounted') || 'disabled'; ### For future use
$ENV{PATH} = "/bin:/usr/bin";
$ENV{HOME} = "/root";
my $devices = esmith::BlockDevices->new ('mount' => $mountpoint, 'allowmount' => $allowMounted);
INITIATE_BACKUP:
my ($rc, $choice) = $console->yesno_page
(
title => gettext("Create Backup to removable media"),
defaultno => 1,
text => gettext('Do you wish to create a backup on removable media?')."\n\n".
gettext('Insert removable media before proceeding.')."\n".
gettext('It may take many seconds to scan for media.'),
);
if ($rc != 0)
{
$devices->destroy;
return;
}
### determine which filesystems are valid or not for backups
# check expected backup size
my $backup_size = backupSize (@backup_list);
# validate each filesystem
my ($valid, $invalid) = $devices->checkBackupDrives ($backup_size);
my $text = $EMPTY;
if (${$invalid}[0]) # If there are filesystems that are not valid.
{
$text .= gettext ('These filesystems are not valid:')."\n";
foreach my $drive (sort @{$invalid})
{
$text .= "$drive ".$devices->desc($drive).' '.gettext ('Reason').': '.$devices->reason($drive)."\n";
}
$text .= "\n";
}
unless (${$valid}[0]) # Unless a device is found show error page
{
my $title = gettext('No valid backup device found').' '.gettext('size').' '.esmith::BlockDevices::scaleIt($backup_size);
$text .= "\n$title, ".gettext('please try again');
($rc, $choice) = $console->yesno_page
(
title => $title,
text => $text,
left => gettext('Try again'),
right => gettext('Cancel'),
);
if ($rc == 0) # Try Again
{
goto INITIATE_BACKUP;
}
else
{
$devices->destroy;
return;
}
}
$text .= gettext ('The following are valid for backup').' (';
$text .= gettext ('size').' '.esmith::BlockDevices::scaleIt($backup_size).')';
#ToDo when valid + invalid > 13 then may need to limit the information
my @args = map { $_ => $devices->desc($_) } @{$valid};
# Display the available backup destinations.
($rc, $choice) = $console->menu_page
(
title => gettext('Choose device to use for backup').' '.gettext('size').' '.esmith::BlockDevices::scaleIt($backup_size),
text => $text,
argsref => \@args,
left => gettext('Cancel'),
right => gettext('OK'),
);
goto INITIATE_BACKUP unless ($rc == 0);
untaint $choice;
$devices->mount ($choice); # mount the chosen filesystem
if (@backup_excludes) {
my $backupexclude = join ("\n/", sort @backup_excludes);
($rc, $choice) = $console->yesno_page
(
title => gettext("Some parts are excluded of your backup"),
left => gettext("Next"),
right => gettext("Cancel"),
text => '/' . $backupexclude,
);
return unless $rc == 0;
}
$console->infobox(
title => gettext("Preparing for backup"),
text => gettext("Please stand by while the system is prepared for backup..."),
);
system("/sbin/e-smith/signal-event", "pre-backup");
$console->gauge(make_backup_callback($mountpoint,$compressionLevel), 'title' => gettext('Creating backup file'));
$devices->destroy;
system("/sbin/e-smith/signal-event", 'post-backup');
RemoveLock();
$console->message_page
(
title => gettext('Backup complete'),
text => gettext('Remove backup media.'),
);
return;
}
sub backupSize
{
my $size;
unless (open(DU, "-|"))
{
open(STDERR, ">/dev/null");
exec qw(/usr/bin/du -sb), map { "/$_" } @_;
}
while (<DU>)
{
next unless (/^(\d+)/);
$size += $1;
}
close DU;
return $size;
}
#use esmith::console;
#esmith::console::perform_backup->new->doit(esmith::console->new,
# esmith::ConfigDB->open);
1;

View File

@@ -0,0 +1,340 @@
package esmith::console::perform_restore;
use strict;
use warnings;
use esmith::ConfigDB;
use esmith::console;
use Locale::gettext;
use Carp;
use feature qw( say );
use esmith::BlockDevices;
use Taint::Util;
use esmith::Backup;
use Data::UUID;
my $EMPTY = q{};
my $backup_lock;
sub new
{
my $class = shift;
my $self = {
name => gettext("Restore from removable media"),
order => installOrder(),
bootstrap => 0,
@_,
};
bless $self, $class;
return $self;
}
sub name
{
return $_[0]->{name};
}
sub order
{
return $_[0]->{order};
}
sub SetLock
{
print "Setting backup lock file\n";
$backup_lock = esmith::Backup::set_lock;
return $backup_lock;
}
sub RemoveLock
{
if (defined($backup_lock))
{
print "Removing backup lock file\n";
esmith::Backup::remove_lock($backup_lock);
$backup_lock = undef;
}
}
sub new_restore_id
{
my $ug = Data::UUID->new;
my $uid = $ug->create_str();
return $uid;
}
sub make_restore_callback
{
my ($mountpoint, $restoreid) = @_;
return sub {
my $fh = shift;
# Check no other backups or restores are in progress
unless (SetLock()) {
die "Error: failed to create lock file. Is a backup or restore already running?";
}
open(OLDSTDOUT, ">&STDOUT");
my $status = 0;
chdir "/";
open(OLDSTDERR, ">&STDERR");
my $logger = open(STDERR, "|-");
die "Can't fork: $!\n" unless defined $logger;
unless ($logger)
{
exec qw(/usr/bin/logger -p local1.info -t console_restore);
}
my $tar = open(TAR, "|-");
return "could not run tar" unless defined $tar;
unless($tar)
{
exec "tar xf -";
}
my $gunzip = open(GUNZIP, "|-");
return "could not run gunzip" unless defined $gunzip;
unless($gunzip)
{
open(STDOUT, ">&TAR");
close TAR;
exec "gunzip";
}
my $pv = fork;
return "could not run pv" unless defined $pv;
unless ($pv)
{
open(STDOUT, ">&GUNZIP");
open(STDERR, ">&$fh");
close GUNZIP;
close TAR;
exec "pv -n $mountpoint/smeserver.tgz";
}
waitpid($pv,0);
warn "status from pv was $?\n" if $?;
unless(close GUNZIP)
{
$status |= $! ? $! : $?;
warn "status from gunzip is $status\n" if $status;
}
unless(close TAR)
{
$status |= $! ? $! : $?;
warn "status from tar is $status\n" if $status;
}
open(STDOUT, ">&OLDSTDOUT");
open(STDERR, ">&OLDSTDERR");
close(OLDSTDOUT);
close(OLDSTDERR);
# Unlock and check for success or failure before returning
RemoveLock();
if ($status) {
return gettext("Restore failed. Your system is likely in an inconsistent state - look at the log files for more details.");
} else {
system("touch", "/tmp/restore_$restoreid");
return gettext("Restore successful.");
}
}
}
sub doit
{
my ($self, $console, $db) = @_;
my $compressionLevel = $db->get_prop('backupconsole', 'CompressionLevel') || '-6';
my $mountpoint = $db->get_prop('backupconsole', 'Mountpoint') || '/mnt/bootstrap-console-backup';
my $allowMounted = $db->get_prop('backupconsole', 'AllowMounted') || 'disabled'; ### For future use
my $restoreMaxDepth = $db->get_prop('backupconsole', 'MaxDepth') || 1; ### For future use
my %found;
my $backupcount = 0;
my $backupdrive; # Which filesystem holds the backup
my $backupfile; # full path to the backup
my ($time, $size); # time and size of chosen backup
return if (($db->get_prop('bootstrap-console', 'Run') eq 'no') && $self->{bootstrap} ); # regular reboot
if ($db->get_prop('bootstrap-console', 'Run') eq 'yes') # called from bootstrap console
{
return if ($db->get_value('PasswordSet') eq 'yes'); # too late
}
return if ($db->get_prop('bootstrap-console', 'Restore') eq 'disabled');
my $devices = esmith::BlockDevices->new ('mount' => $mountpoint, 'allowmount' => $allowMounted);
INITIATE_RESTORE:
my ($rc, $choice) = $console->yesno_page
(
title => gettext("Restore From Backup"),
defaultno => 1,
text => gettext('Do you wish to restore from backup?')."\n\n".
gettext('Insert removable media before proceeding.')."\n".
gettext('It may take many seconds to scan for media.'),
); # Buttons are Yes & No
if ($rc != 0) # choice was not Yes
{
$devices->destroy;
return;
}
### determine which filesystems are valid or not for backups
# validate each filesystem
my ($valid, $invalid) = $devices->checkBackupDrives ($EMPTY);
my $text = $EMPTY;
if (${$valid}[0]) # There are filesystems that could hold a backup.
{
$text .= gettext ('These filesystems could hold backups')."\n";
foreach my $drive (sort @{$valid})
{
$text .= "$drive ".$devices->desc($drive)."\n";
$devices->findBackup ($drive, \%found, $restoreMaxDepth, \$backupcount);
}
$text .= "\n";
}
unless ($backupcount) # Unless a valid backup is found show error page
{
if (${$invalid}[0]) # If there are filesystems that are not valid.
{
$text .= gettext ('These filesystems are not valid:')."\n";
foreach my $drive (sort @{$invalid})
{
$text .= "$drive ".$devices->desc($drive).' '.gettext ('Reason').': '.$devices->reason($drive)."\n";
}
$text .= "\n";
}
my $title = gettext('No valid backup device found');
$text .= "\n$title, ".gettext('please try again');
($rc, $choice) = $console->yesno_page
(
title => $title,
text => $text,
left => gettext('Try again'),
right => gettext('Cancel'),
);
if ($rc == 0) # Try Again
{
goto INITIATE_RESTORE;
}
else
{
$devices->destroy;
return;
}
}
# %found contains $backupcount backups.
if ($backupcount == 1)
{
# One backup found, so simple yes/no choice
$backupdrive = $found{1}{device}; # Find the (only) device that a backup was found on
$backupfile = $found{1}{path}; # find the actual backup
$time = gettext('Date') .' '. $found{1}{time};
$size = gettext('Size') .' '. $found{1}{sizeH};
($rc, $choice) = $console->yesno_page
(
title => gettext('Start restore from backup'),
text =>
gettext('Backup found on device').
"\n$backupdrive ".$devices->desc($backupdrive)."\n\n".
gettext('Backup details').
"\n$backupfile $size $time\n\n\n".
gettext('Do you wish to restore from this file?'),
);
goto INITIATE_RESTORE unless ($rc == 0);
$size = $found{1}{size};
}
else # Multiple backups found so display a choice
{
$text = gettext ('Backups found on these devices')."\n";
foreach my $backupfound (sort keys %found)
{
$backupdrive = $found{$backupfound}{device};
if (($backupfound == 1) || ($found{$backupfound}{device} ne $found{$backupfound-1}{device}))
{
$text.= "$backupdrive ".$devices->desc($backupdrive)."\n";
}
}
my @args = map { $_ => "$found{$_}{device} $found{$_}{path} $found{$_}{sizeH} $found{$_}{time}" } sort keys %found;
($rc, $choice) = $console->menu_page
(
title => gettext('Start restore from backup'),
text =>
"$text\n".
gettext ('Please select the backup that you wish to restore from.'),
argsref => \@args,
left => gettext('Cancel'),
right => gettext('OK'),
);
goto INITIATE_RESTORE unless ($rc == 0);
untaint $choice;
$backupdrive = $found{$choice}{device};
$size = $found{$choice}{size};
}
$devices->mount ($backupdrive); # mount the chosen filesystem
sleep(1); # Some mounts take time to become active
# Prepare for restore
$console->infobox(
title => gettext("Preparing for restore"),
text => gettext("Please stand by while the system is prepared for restore..."),
);
system("/sbin/e-smith/signal-event", "pre-restore");
# Create restore ID to check success later
my $restoreid = new_restore_id();
# Run restore
$console->gauge(make_restore_callback($mountpoint, $restoreid), 'title' => gettext('Restoring from backup'));
# Restore complete, now clean-up. Only post-upgrade and reboot if the restore was successful.
$devices->destroy;
if (-e "/tmp/restore_$restoreid") {
system("/sbin/e-smith/signal-event", "post-upgrade");
$db->set_prop("bootstrap-console", "Run", "yes");
$db->set_prop("bootstrap-console", "ForceSave", "yes");
$db->set_prop("bootstrap-console", "Restore", "disabled");
unless ( $self->{bootstrap} )
{
($rc, $choice) = $console->yesno_page
(
title => gettext("Restore Complete"),
text => gettext('You must restart your system to finish the restore.')."\n\n".
gettext('Do you want to restart now?'),
);
return unless ($rc == 0);
system("/usr/bin/tput", "clear");
system("/sbin/e-smith/signal-event", "reboot");
# A bit of a hack to avoid the console restarting before the
# reboot takes effect.
sleep(600);
}
}
return;
}
# Determine if this server is a fresh install for restore from backup
# Earlier the expression used was ($db->get_value('PasswordSet') eq 'yes')
# To prevent a restore return a negative number
# To allow a restore choose an appropiate sort order, eg 90
sub installOrder
{
my $order = (`grep :x:5...: /etc/group`) ? -1 : 90;
return $order;
}
#use esmith::console;
#esmith::console::perform_restore->new->doit(esmith::console->new,
# esmith::ConfigDB->open);
1;