5265 lines
182 KiB
Perl
5265 lines
182 KiB
Perl
#!/usr/bin/perl -w
|
|
|
|
#----------------------------------------------------------------------
|
|
# Affa is a rsync based backup program for Linux.
|
|
# It remotely backups Linux or other systems, which have either the rsync
|
|
# program and the sshd service or the rsyncd service installed.
|
|
# Please see http://affa.sf.net for full documentation.
|
|
# Copyright (C) 2004-2012 by Michael Weinberger
|
|
# This program comes with ABSOLUTELY NO WARRANTY; for details type 'affa --warranty'.
|
|
# This is free software, and you are welcome to redistribute it
|
|
# under certain conditions; type 'affa --license' for details.
|
|
#----------------------------------------------------------------------
|
|
|
|
# ToDO
|
|
# re-order the getSourceDirs
|
|
|
|
# Line 820
|
|
#2969; my $needed = int( $report->val( 'Report', 'TotalFileSize' ) / 1024 );
|
|
|
|
# Fix the CLI
|
|
#1629; my $archive = $ARGV[1] eq '' ? $job{'Archive'} : $ARGV[1] ; #Overide archive from the CLI if set
|
|
|
|
# Need to fix the 'selected' rise version here
|
|
# sub riseFromDeath() {
|
|
|
|
# not ExecCmd( @cmd, 0 ) or affaErrorExit("writing list of installed RPMs failed."); # reetp fix this around 1580
|
|
|
|
# Restore will fail if contribs are not installed first. rsycn < 3.3.0 cannot make the directories.
|
|
# This will be fixed in rsync 3.3.0+ and v11
|
|
|
|
# 19-08-24
|
|
# RPM version
|
|
my $VERSION='4.0-2';
|
|
# Script Sub Version j
|
|
|
|
# This will remove the trailing -1 release number for docs
|
|
my $shortVersion = $VERSION;
|
|
$shortVersion =~ s/-[0-9]+//;
|
|
|
|
|
|
# Development code to find which routines take time
|
|
# my $develTimer = 0;
|
|
# use Devel::Timer;
|
|
# my $timer = Devel::Timer->new();
|
|
|
|
|
|
|
|
#----------------------------------------------------------------------
|
|
#
|
|
# A few notes - 21/09/2021
|
|
#
|
|
# This is a huge script and probably needs breaking up
|
|
# It probably has lots that can be cleaned or removed
|
|
# It is starting to become systemd aware, but
|
|
# SME is still not fully systemd manageable and still relies
|
|
# on some old init scripts
|
|
#
|
|
# freedup for SME v10 is in smetest or smecontribs
|
|
#
|
|
# reetp - what if this is backing up a remote linux box but non SME?
|
|
# This has become more and more SME specific and certain config settings
|
|
# may trigger unusual behavior.
|
|
#----------------------------------------------------------------------
|
|
|
|
use strict;
|
|
use Date::Format;
|
|
use Errno;
|
|
use File::Path;
|
|
use File::stat;
|
|
use File::Copy;
|
|
use Digest::MD5;
|
|
use Cwd;
|
|
use Filesys::DiskFree;
|
|
use Getopt::Long;
|
|
use Mail::Send;
|
|
use Time::Local;
|
|
use Sys::Hostname;
|
|
use Config::IniFiles;
|
|
use Proc::ProcessTable;
|
|
|
|
##########################
|
|
#ajoute
|
|
use esmith::Backup;
|
|
use esmith::ConfigDB;
|
|
|
|
# ajout pour V3.2.2-2
|
|
use File::Basename;
|
|
|
|
# Added for 3.3.1
|
|
use esmith::Service;
|
|
|
|
#use Affa::lib qw(ExecCmd setlog lg dbg);
|
|
########use Affa::lib qw(ExecCmd setlog lg dbg);
|
|
#fin ajout
|
|
#########################
|
|
|
|
$| = 1; # flush output
|
|
|
|
#$ARGV[0]
|
|
# prototypes
|
|
sub affaErrorExit($);
|
|
sub affaExit($);
|
|
sub checkArchiveExists($$);
|
|
sub checkConfig();
|
|
sub checkConnection($);
|
|
sub checkConnectionsAll();
|
|
sub checkConnection_silent($$$);
|
|
|
|
# ajout pour V3.2.2-2
|
|
sub checkForDiskLabel($);
|
|
# fin ajout
|
|
|
|
sub checkCrossFS($$);
|
|
sub cleanup();
|
|
sub convertReportV2toV3($$);
|
|
sub cronSetup();
|
|
# sub dbg($); reetp - duplicate
|
|
sub dbg($);
|
|
sub deduplicate($);
|
|
sub deleteJob();
|
|
sub df($);
|
|
sub DiskSpaceWarn();
|
|
sub DiskUsage();
|
|
sub DiskUsageRaw();
|
|
sub ExecCmd( \@$ );
|
|
sub ExecCmdBackupList( \@$ );
|
|
sub execJobCommand($$);
|
|
sub execJobCommandRemote($$);
|
|
sub execPostJobCommand($);
|
|
sub execPreJobCommand($);
|
|
sub findProcessId($$);
|
|
sub fullRestore();
|
|
sub getChildProcess($$);
|
|
sub getConfigFile($);
|
|
sub getConfigFileList();
|
|
sub getIncExc($);
|
|
sub getExcludedString();
|
|
sub getIncludedString();
|
|
sub getJobConfig($);
|
|
sub getJobs();
|
|
sub getLinkdest($$);
|
|
sub getLock($);
|
|
sub getMultivalueKeys();
|
|
sub getProcessState($);
|
|
sub getReportVal($$);
|
|
sub getSourceDirs($);
|
|
sub getSourceDirsString($);
|
|
sub getStatus();
|
|
sub getStatusRaw();
|
|
sub isMounted($$);
|
|
sub jobsnrpe($);
|
|
sub killall();
|
|
sub killJob($);
|
|
sub killProcessGroup($$);
|
|
sub lg($);
|
|
sub listArchives();
|
|
sub listArchivesRaw($);
|
|
sub listJobs();
|
|
sub logTail();
|
|
sub mailTest();
|
|
###### ligne suivante modifiée umount diskusage - bug 9147
|
|
# sub mount($$$); # reetp - which mount?
|
|
sub mount($$$%);
|
|
sub moveArchive();
|
|
sub moveFileorDir($$);
|
|
sub nrpe();
|
|
sub remoteCopy($$$);
|
|
sub removeDir($$);
|
|
sub removeLock();
|
|
sub renameConfigKey($$$);
|
|
sub renameJob();
|
|
sub resumeInterrupted();
|
|
sub revokeKeys($);
|
|
sub sanityCheckConfFiles();
|
|
sub sendErrorMesssage();
|
|
sub sendKeys();
|
|
sub sendStatus();
|
|
sub sendSuccessMesssage();
|
|
sub setLock();
|
|
sub setlog($);
|
|
sub setupSamba();
|
|
sub shiftArchives();
|
|
sub showConfigPathes();
|
|
sub showConfigPathesRaw();
|
|
sub showDefaults();
|
|
sub showHelp($);
|
|
sub showProperty();
|
|
sub showSchedule();
|
|
sub showTextfile($);
|
|
sub showVersion();
|
|
sub SignalHandler();
|
|
sub trim($);
|
|
sub unmount($$);
|
|
sub writeConfigFile($);
|
|
|
|
####################################################################################
|
|
########### ajouts
|
|
sub compareRPMLists($);
|
|
sub imapIndexFilesDelete();
|
|
sub imapIndexFilesDeleteCommand();
|
|
sub installedRPMsList($);
|
|
sub installWatchdog($);
|
|
sub mailTestWatchdogRemote($);
|
|
sub riseFromDeath();
|
|
sub runRiseRsync($$$);
|
|
sub saveMySoul();
|
|
sub undoRise();
|
|
|
|
########## fin ajouts
|
|
###################################################################################
|
|
|
|
###################################################################################
|
|
################ ajouts
|
|
|
|
my $configDB = esmith::ConfigDB->open or die "Error: Couldn't open config db.";
|
|
my $LocalIP = $configDB->get("LocalIP")->value;
|
|
my $DomainName = $configDB->get("DomainName")->value;
|
|
my $SystemName = $configDB->get("SystemName")->value;
|
|
|
|
my $rpmlist = "/home/e-smith/db/affa-rpmlist"; # list of installed RPMs
|
|
my $backupList = "BackupDirectoryList.txt";
|
|
|
|
my @services = ( 'crond', 'smbd', 'qmail', 'qpsmtpd', 'sqpsmtpd' );
|
|
|
|
my $ServicesStopped = 0;
|
|
|
|
my $sshQuiet = "-q";
|
|
|
|
my $ServerBasename = "AFFA.$SystemName.$DomainName-$LocalIP";
|
|
|
|
my $allow_retry = 0;
|
|
my $defaultEmail = 'admin';
|
|
|
|
my $affaTitle = "Affa version $VERSION on $SystemName";
|
|
my $smbconf = '/etc/samba/smb.conf';
|
|
|
|
#############
|
|
# reetp
|
|
# So we could change this to check the samba status as this is ONLY checking
|
|
# For the script existence OR /sbin/e-smith/service smb status | grep Active
|
|
|
|
my $systemD = new esmith::Service();
|
|
|
|
# Service command << not required now
|
|
# $serviceCommand = '/sbin/e-smith/service'
|
|
|
|
my $SambaStartScript = '/usr/lib/systemd/system/smbd.service';
|
|
#my $smbStatus = $configDB->get_prop( 'smb', 'status' ) || 'disabled';
|
|
|
|
$systemD->{'serviceName'} = 'smbd';
|
|
|
|
my $smbdStatus = ( $systemD->is_enabled() ) ? "enabled" : "disabled";
|
|
|
|
# nrpe too
|
|
my $NRPEStartScript = '/usr/lib/systemd/system/nrpe.service';
|
|
#my $nrpeStatus = $configDB->get_prop( 'nrpe', 'status' ) || 'disabled';
|
|
|
|
$systemD->{'serviceName'} = 'nrpe';
|
|
|
|
my $nrpeStatus = ( $systemD->is_enabled() ) ? "enabled" : "disabled";
|
|
|
|
#############
|
|
|
|
# Test binary built in reetpTest repo
|
|
# No idea if it works on v10
|
|
my $dedupBinary = '/usr/bin/freedup';
|
|
|
|
my $cfg; # ini file
|
|
|
|
my $curtime = time(); # now timestamp
|
|
my $thisDay = time2str( "%Y%j", $curtime ); # day of year 1-366
|
|
my $thisWeek = time2str( "%Y%W", $curtime ); # week of year 1-53
|
|
my $thisMonth = time2str( "%Y%m", $curtime ); # month 1-12
|
|
my $thisYear = time2str( "%Y", $curtime ); # 4-digit year
|
|
my $process_id = $$; # my PID
|
|
my $jobname = 'NONE';
|
|
my $Command = '';
|
|
my $interactive = 0;
|
|
my %autoMounted = ();
|
|
my $interrupt = '';
|
|
my $ExecCmdOut = '';
|
|
my @Messages = (); # List of all log messages
|
|
my $logdir = '/var/log/affa'; # Logdir
|
|
my $logfile = "$logdir/affa-GLOBAL-LOGFILE.log"; # Logfile
|
|
my $scriptdir = '/etc/affa/scripts';
|
|
my $lockdir = '/var/lock/affa'; # Process lock
|
|
my $archive = '';
|
|
my $restoreVer = '';
|
|
File::Path::mkpath( $lockdir, 0, 0700 ) if not -d $lockdir;
|
|
|
|
# reetp this was not set anywhere at this point so added it right at the start
|
|
# otherwise when you initially run you get a dir not found
|
|
# it does get set in getDefaultConfig()
|
|
# 'RootDir' => '/var/affa',
|
|
# Maybe should be called earlier?
|
|
my $storagedir = '/var/affa'; # Storage directory
|
|
File::Path::mkpath( $storagedir, 0, 0700 ) if not -d $storagedir;
|
|
|
|
|
|
if ( not $ARGV[0] ) {
|
|
showHelp(1);
|
|
exit;
|
|
}
|
|
|
|
my %opts;
|
|
my $runninglog = "Affa $VERSION: Running $0 @ARGV";
|
|
my $getRes = GetOptions(
|
|
'15' => \$opts{'15'},
|
|
'30' => \$opts{'30'},
|
|
'all' => \$opts{'all'},
|
|
'backup' => \$opts{'run'}, # same as --run
|
|
'check-connections' => \$opts{'check-connections'},
|
|
'cleanup' => \$opts{'cleanup'},
|
|
'csv' => \$opts{'csv'},
|
|
'configcheck' => \$opts{'configcheck'},
|
|
'nrpe' => \$opts{'nrpe'},
|
|
'debug' => \$opts{'debug'},
|
|
'cli-debug' => \$opts{'cli-debug'},
|
|
'delete-job' => \$opts{'delete-job'},
|
|
'disk-usage' => \$opts{'disk-usage'},
|
|
'full-restore' => \$opts{'full-restore'},
|
|
'help' => \$opts{'help'},
|
|
'_jobs' => \$opts{'jobs'},
|
|
'_jobsnrpe' => \$opts{'jobsnrpe'},
|
|
'init-nrpe' => \$opts{'init-nrpe'},
|
|
'_cronupdate' => \$opts{'cronupdate'},
|
|
'killall' => \$opts{'killall'},
|
|
'kill' => \$opts{'kill'},
|
|
'resume=s' => \$opts{'resume'},
|
|
'list-archives' => \$opts{'list-archives'},
|
|
'preserve-newer=s' => \$opts{'preserve-newer'},
|
|
'delete=s' => \$opts{'delete'},
|
|
'_delay=s' => \$opts{'delay'},
|
|
'log-tail' => \$opts{'log-tail'},
|
|
'mailtest' => \$opts{'mailtest'},
|
|
'make-cronjobs' => \$opts{'make-cronjobs'},
|
|
'move-archive' => \$opts{'move-archive'},
|
|
'outfile=s' => \$opts{'outfile'},
|
|
'rename-job' => \$opts{'rename-job'},
|
|
'RetryAfter=s' => \$opts{'RetryAfter'},
|
|
'RetryAttempts=s' => \$opts{'RetryAttempts'},
|
|
'revoke-key' => \$opts{'revoke-key'},
|
|
'run' => \$opts{'run'},
|
|
'send-keys' => \$opts{'send-keys'},
|
|
'send-status' => \$opts{'send-status'},
|
|
'_shorthelp' => \$opts{'shorthelp'},
|
|
'show-config-pathes' => \$opts{'show-config-pathes'},
|
|
'show-property' => \$opts{'show-property'},
|
|
'show-schedule' => \$opts{'show-schedule'},
|
|
'show-default-config' => \$opts{'show-default-config'},
|
|
'status' => \$opts{'status'},
|
|
'resume-interrupted' => \$opts{'resume-interrupted'},
|
|
'version' => \$opts{'version'},
|
|
'warranty' => \$opts{'warranty'},
|
|
'license' => \$opts{'license'},
|
|
'silent' => \$opts{'silent'},
|
|
'watchdog=s' => \$opts{'watchdog'},
|
|
"rise" => \$opts{'rise'},
|
|
"undo-rise" => \$opts{'undo-rise'},
|
|
"sanity-check" => \$opts{'sanityheck'},
|
|
);
|
|
|
|
####################################################################################
|
|
#### ajouté paramètre watchdog=s + rise + undo-rise 2 lignes plus haut
|
|
|
|
my $configfile = "/tmp/affa-config-$curtime-$$";
|
|
unlink($configfile);
|
|
|
|
# Check the config files are OK
|
|
sanityCheckConfFiles();
|
|
|
|
my $cliDebug = 0;
|
|
|
|
if ( $opts{'cli-debug'} ) {
|
|
$cliDebug = 1;
|
|
}
|
|
|
|
# get default config - no job specificed
|
|
my %job = getJobConfig('');
|
|
|
|
if ( $opts{'nrpe'} ) {
|
|
exit nrpe();
|
|
}
|
|
|
|
if ( $opts{'cronupdate'} ) {
|
|
$interactive = 1;
|
|
cronSetup();
|
|
exit 0;
|
|
}
|
|
|
|
if ( $opts{'version'} ) {
|
|
showVersion();
|
|
exit 0;
|
|
}
|
|
if ( $opts{'license'} ) {
|
|
showTextfile("/usr/share/doc/smeserver-affa-$shortVersion/LICENSE");
|
|
exit 0;
|
|
}
|
|
if ( $opts{'warranty'} ) {
|
|
showTextfile("/usr/share/doc/smeserver-affa-$shortVersion/WARRANTY");
|
|
exit 0;
|
|
}
|
|
if ( $opts{'help'} ) {
|
|
showHelp(0);
|
|
exit 0;
|
|
}
|
|
if ( $opts{'jobs'} ) {
|
|
listJobs();
|
|
exit 0;
|
|
}
|
|
if ( $opts{'init-nrpe'} ) {
|
|
jobsnrpe(1);
|
|
exit 0;
|
|
}
|
|
if ( $opts{'jobsnrpe'} ) {
|
|
exit jobsnrpe(0);
|
|
}
|
|
if ( $opts{'shorthelp'} ) {
|
|
showHelp(1);
|
|
exit 0;
|
|
}
|
|
|
|
#lg( $runninglog ); $runninglog='';
|
|
|
|
if ( $opts{'list-archives'} ) {
|
|
print listArchives();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'show-config-pathes'} ) {
|
|
undef $jobname;
|
|
showConfigPathes();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'show-default-config'} ) {
|
|
undef $jobname;
|
|
showDefaults();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'log-tail'} ) {
|
|
undef $jobname;
|
|
logTail();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'configcheck'} ) {
|
|
undef $jobname;
|
|
checkConfig();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'send-keys'} ) {
|
|
$jobname = 'send keys';
|
|
sendKeys();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'disk-usage'} ) {
|
|
undef $jobname;
|
|
print DiskUsage();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'status'} ) {
|
|
undef $jobname;
|
|
print getStatus();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'send-status'} ) {
|
|
undef $jobname;
|
|
sendStatus();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'mailtest'} ) {
|
|
mailTest();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'full-restore'} ) {
|
|
$jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Full-Restore Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
# Don't untaint as it removes the digit
|
|
# regex to check [0-9] ?
|
|
$restoreVer = $ARGV[1] || 'scheduled.0';
|
|
fullRestore();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'resume-interrupted'} ) {
|
|
resumeInterrupted();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'cleanup'} ) {
|
|
cleanup();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'delete-job'} ) {
|
|
deleteJob();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'rename-job'} ) {
|
|
renameJob();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'move-archive'} ) {
|
|
moveArchive();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'revoke-key'} ) {
|
|
$jobname = 'revoke key';
|
|
revokeKeys( $ARGV[0] || '' );
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'check-connections'} ) {
|
|
$jobname = 'check-connections';
|
|
checkConnectionsAll();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'kill'} ) {
|
|
killJob( $ARGV[0] || '' );
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'killall'} ) {
|
|
killall();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'show-schedule'} ) {
|
|
showSchedule();
|
|
affaExit('Done.');
|
|
}
|
|
elsif ( $opts{'show-property'} ) {
|
|
showProperty();
|
|
affaExit('Done.');
|
|
}
|
|
|
|
####################################################################################
|
|
#### début des modifs: ajout de
|
|
elsif ( $opts{'rise'} ) {
|
|
#$jobname = 'rising from archive';
|
|
$jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Rise Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
# Don't untaint as it removes the digit
|
|
# regex to check [0-9] ?
|
|
$restoreVer = $ARGV[1] || 'scheduled.0';
|
|
riseFromDeath();
|
|
affaExit('Done.');
|
|
}
|
|
|
|
elsif ( $opts{'undo-rise'} ) {
|
|
$jobname = 'undo-rise';
|
|
$jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
undoRise();
|
|
affaExit('Done.');
|
|
}
|
|
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
if ( $opts{'make-cronjobs'} ) {
|
|
$jobname = 'make cronjobs';
|
|
cronSetup();
|
|
affaExit('Done.');
|
|
}
|
|
|
|
if ( not $opts{'run'} ) {
|
|
print "Run affa --help for help.\n";
|
|
affaErrorExit("Unkown option.");
|
|
}
|
|
|
|
# With all other options tested we now run a backup job
|
|
|
|
my $StartTime = time();
|
|
$jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
|
|
$Command = defined $ARGV[1] ? lc( $ARGV[1] ) : 'scheduled';
|
|
$Command =~ /([a-z]*)/i;
|
|
$Command = $1; # untaint
|
|
if ( not $Command =~ /^(scheduled|daily|weekly|monthly|yearly)$/ ) {
|
|
affaErrorExit("Unknown command '$Command'");
|
|
}
|
|
|
|
%job = getJobConfig($jobname);
|
|
setlog("$jobname.log");
|
|
{ my $txt = "# Affa $VERSION #"; lg( '#' x length($txt) ); lg($txt); lg( '#' x length($txt) ) }
|
|
lg("Starting job $jobname $Command ($job{'remoteHostName'})");
|
|
lg( "Description: " . $job{'Description'} ) if defined $job{'Description'};
|
|
lg("Bandwidth limit: $job{'BandwidthLimit'} KBytes/sec") if $job{'BandwidthLimit'};
|
|
|
|
# check whether the job is already running
|
|
$Command eq "scheduled"
|
|
and getLock($jobname)
|
|
and affaErrorExit( "Lock found. Another job (pid=" . getLock($jobname) . ") is still running." );
|
|
|
|
setLock() if $Command eq "scheduled";
|
|
$SIG{'TERM'} = 'SignalHandler';
|
|
$SIG{'INT'} = 'SignalHandler';
|
|
|
|
if ( $opts{'delay'} ) {
|
|
lg( "Delayed run. Starting at " . Date::Format::time2str( "%T", time() + $opts{'delay'} ) );
|
|
sleep( $opts{'delay'} );
|
|
}
|
|
|
|
if ( $opts{'RetryAfter'} ) {
|
|
lg( "Waiting $opts{'RetryAfter'} seconds. Continuing at "
|
|
. Date::Format::time2str( "%T", time() + $opts{'RetryAfter'} ) );
|
|
sleep( $opts{'RetryAfter'} );
|
|
$job{'chattyOnSuccess'}++ if not $job{'chattyOnSuccess'} and $job{'RetryNotification'} eq 'yes';
|
|
}
|
|
$allow_retry = 1;
|
|
checkConnection($jobname); # and exit on error
|
|
|
|
# mount root dir
|
|
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
|
|
###### ligne suivante modifiée umount diskusage - bug 9147
|
|
# mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'} );
|
|
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
|
|
}
|
|
|
|
affaErrorExit("RootDir $job{'RootDir'} does not exist") unless -d $job{'RootDir'};
|
|
File::Path::mkpath( "$job{'RootDir'}/$jobname", 0, 0700 ) unless -d "$job{'RootDir'}/$jobname";
|
|
|
|
my $ddf = "$job{'RootDir'}/$jobname/.doneDates";
|
|
if ( not -f $ddf ) {
|
|
open( DF, ">$ddf" );
|
|
print DF "[doneDates]\n";
|
|
print DF "daily=-1\n";
|
|
print DF "weekly=-1\n";
|
|
print DF "monthly=-1\n";
|
|
print DF "yearly=-1\n";
|
|
close(DF);
|
|
}
|
|
$job{'_doneDates'} = Config::IniFiles->new( -file => "$job{'RootDir'}/$jobname/.doneDates", -nocase => 0 );
|
|
|
|
# run daily, weekly, monthly or yearly if not already done;
|
|
if ( $Command eq "scheduled" and -f "$job{'RootDir'}/$jobname/scheduled.0/.AFFA4-REPORT" ) {
|
|
$0 =~ /(.*)/; # untaint
|
|
my @cmd = ( $1, '--run', $jobname, 'yearly' );
|
|
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'yearly' ) ne $thisYear and $job{'yearlyKeep'} > 0 );
|
|
$cmd[3] = 'monthly';
|
|
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'monthly' ) ne $thisMonth and $job{'monthlyKeep'} > 0 );
|
|
$cmd[3] = 'weekly';
|
|
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'weekly' ) ne $thisWeek and $job{'weeklyKeep'} > 0 );
|
|
$cmd[3] = 'daily';
|
|
ExecCmd( @cmd, 1 ) if ( $job{'_doneDates'}->val( 'doneDates', 'daily' ) ne $thisDay and $job{'dailyKeep'} > 0 );
|
|
}
|
|
|
|
### hier geht's wirklich los ###
|
|
execPreJobCommand($jobname);
|
|
|
|
####################################################################################
|
|
#### debut des modifs
|
|
installWatchdog( $opts{'watchdog'} || 0 );
|
|
|
|
if ( $job{'SMEServer'} ne 'no' ) {
|
|
# Here only for SME machines
|
|
my $prebackup = 'SME/signal-pre-backup';
|
|
if ( $job{'remoteHostName'} eq 'localhost' ) {
|
|
execJobCommand( $jobname, $prebackup );
|
|
}
|
|
else {
|
|
execJobCommandRemote( $jobname, $prebackup );
|
|
}
|
|
}
|
|
|
|
installedRPMsList(0) if $job{'SMEServer'} ne 'no' and $job{'remoteHostName'} ne 'localhost';
|
|
#### ligne ci-dessus: suppression de "!$ESX and " entre if et $job
|
|
#### fin modifs
|
|
####################################################################################
|
|
|
|
# reetp here we start the business end by retrieving the list of directories that need backing up
|
|
# Note if we are not 'scheduled' we drop right through to after shiftArchives below
|
|
|
|
my $linkDest = '';
|
|
my @cmd;
|
|
my $status = 0;
|
|
my $rsyncOutput = '';
|
|
|
|
if ( $Command eq 'scheduled' ) {
|
|
my $exclude = getExcludedString();
|
|
my $include = getIncludedString();
|
|
$linkDest = getLinkdest( $jobname, 0 );
|
|
dbg("Using link destination $linkDest") if $linkDest;
|
|
|
|
# my @cmd; moved up
|
|
#my $status = 0;
|
|
#my $rsyncOutput = '';
|
|
|
|
File::Path::mkpath( "$job{'RootDir'}/$jobname/scheduled.running",
|
|
0, 0700 )
|
|
unless -d "$job{'RootDir'}/$jobname/scheduled.running";
|
|
|
|
if ( $job{'_rsyncd'} ) {
|
|
|
|
# e.g. Windows Server with rsyncd installed
|
|
# reetp pretend we didnt see this part - Windows needs Cygwin/Rysncd to work
|
|
# This will likely not work and may get removed
|
|
|
|
my @SourceDirs = getSourceDirs($jobname);
|
|
my $source = '';
|
|
foreach my $src (@SourceDirs) {
|
|
$src = "/$src" if not $src =~ /^\//;
|
|
$src =~ s/'/'\\''/g; # escape single quotes
|
|
$source
|
|
.= ( $job{'rsyncdUser'} ? $job{'rsyncdUser'} . '@' : '' )
|
|
. $job{'remoteHostName'} . "::'"
|
|
. $job{'rsyncdModule'}
|
|
. $src . "' ";
|
|
}
|
|
@cmd = (
|
|
$job{'_rsyncLocal'},
|
|
"--archive",
|
|
"--hard-links",
|
|
"--stats",
|
|
"--delete-during",
|
|
"--ignore-errors",
|
|
"--delete-excluded",
|
|
"--relative",
|
|
"--partial",
|
|
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
|
|
$job{'rsync--modify-window'} > 0
|
|
? "--modify-window=$job{'rsync--modify-window'}"
|
|
: '',
|
|
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
|
|
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
|
|
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
|
|
"--numeric-ids",
|
|
( $linkDest
|
|
? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'"
|
|
: ''
|
|
),
|
|
$include, $exclude,
|
|
$job{'rsyncOptions'},
|
|
$source,
|
|
"$job{'RootDir'}/$jobname/scheduled.running/"
|
|
);
|
|
}
|
|
elsif ( $job{'SMEServer'} ne 'no' ) {
|
|
|
|
# SME specific
|
|
# reetp - remote or local? Remote should not be just '/'
|
|
# We fix Include/Exclude in the getSourceDir subs below
|
|
# (Except we don't fix the excludes yet)
|
|
|
|
#my $SourceDirs = getSourceDirsString($jobname) || '/';
|
|
my ( $local, $remote ) = getSourceDirsString($jobname);
|
|
|
|
# $remote is the destination - it could be local
|
|
my $SourceDirs = $remote;
|
|
|
|
####################################################################################
|
|
#### debut des modifs: ajout du "if........." ditto V2
|
|
|
|
# escape single quotes
|
|
#$SourceDirs =~ s/'/'\\''/g if $job{'remoteHostName'} ne 'localhost';
|
|
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
# reetp here is where we set up the backup command
|
|
# The Include/Exclude parts may be problematic UNLESS they are a local box
|
|
# Removed 'default' ssh options for localhost
|
|
|
|
@cmd = (
|
|
$job{'_rsyncLocal'},
|
|
"--archive",
|
|
"--hard-links",
|
|
"--stats",
|
|
"--delete-during",
|
|
"--ignore-errors",
|
|
"--delete-excluded",
|
|
"--relative",
|
|
"--partial",
|
|
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
|
|
$job{'rsync--modify-window'} > 0
|
|
? "--modify-window=$job{'rsync--modify-window'}"
|
|
: '',
|
|
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
|
|
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
|
|
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
|
|
"--numeric-ids",
|
|
"--rsync-path=\"$job{'_rsyncRemote'}\"",
|
|
$job{'remoteHostName'} ne 'localhost'
|
|
? "--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\""
|
|
: '',
|
|
( $linkDest
|
|
? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'"
|
|
: ''
|
|
),
|
|
|
|
# Remove old include/exclude
|
|
#$job{'remoteHostName'} eq 'localhost' ? "$include" : '',
|
|
#$job{'remoteHostName'} eq 'localhost' ? "$exclude" : '',
|
|
# For now just add exclude
|
|
$exclude,
|
|
$job{'rsyncOptions'},
|
|
"$SourceDirs",
|
|
"$job{'RootDir'}/$jobname/scheduled.running/"
|
|
);
|
|
####################################################################################
|
|
#### debut des modifs faites dans le bas de la commande @cmd precedente
|
|
# beginning of the modifications made at the bottom of the previous @cmd command
|
|
#
|
|
#
|
|
# $job{'remoteHostName'} eq 'localhost' ? "$SourceDirs" : $job{'remoteUser'}.'@'.$job{'remoteHostName'}.":'$SourceDirs'",
|
|
#
|
|
##### fin des modifs
|
|
####################################################################################
|
|
|
|
}
|
|
else {
|
|
# Generic linux
|
|
my $SourceDirs = getSourceDirsString($jobname) || '/';
|
|
|
|
# my ($local, $remote) = getSourceDirsString($jobname);
|
|
|
|
@cmd = (
|
|
$job{'_rsyncLocal'},
|
|
"--archive",
|
|
"--hard-links",
|
|
"--stats",
|
|
"--delete-during",
|
|
"--ignore-errors",
|
|
"--delete-excluded",
|
|
"--relative",
|
|
"--partial",
|
|
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
|
|
$job{'rsync--modify-window'} > 0
|
|
? "--modify-window=$job{'rsync--modify-window'}"
|
|
: '',
|
|
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
|
|
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
|
|
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
|
|
"--numeric-ids",
|
|
"--rsync-path=\"$job{'_rsyncRemote'}\"",
|
|
$job{'remoteHostName'} ne 'localhost'
|
|
? "--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\""
|
|
: '',
|
|
( $linkDest
|
|
? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'"
|
|
: ''
|
|
),
|
|
|
|
$job{'remoteHostName'} eq 'localhost' ? "$include" : '',
|
|
$job{'remoteHostName'} eq 'localhost' ? "$exclude" : '',
|
|
|
|
$job{'rsyncOptions'},
|
|
"$SourceDirs",
|
|
"$job{'RootDir'}/$jobname/scheduled.running/"
|
|
);
|
|
}
|
|
|
|
#else {
|
|
# What if we are not 'scheduled??
|
|
#affaErrorExit("Backup Command not equal 'scheduled'");
|
|
#}
|
|
|
|
# And here is where we actually start to backup
|
|
lg("Running Backup $jobname rsync...");
|
|
|
|
if ( $cliDebug == 1 ) {
|
|
|
|
# reetp remove empty array keys
|
|
@cmd = grep { $_ ne '' } @cmd;
|
|
printf "ExecCMD = @cmd\n";
|
|
}
|
|
|
|
$status = ExecCmd( @cmd, 0 );
|
|
$rsyncOutput = $ExecCmdOut;
|
|
|
|
if ( $cliDebug == 1 ) {
|
|
printf($rsyncOutput);
|
|
}
|
|
|
|
# if nothing was transferred, scheduled.running does not exist
|
|
if ( not -d "$job{'RootDir'}/$jobname/scheduled.running" ) {
|
|
lg("Error: No data transferred. Check include/exclude settings.");
|
|
$status += 1000;
|
|
}
|
|
|
|
if ( $status eq '0' or $status eq '23' or $status eq '24' ) {
|
|
|
|
# write report file
|
|
my $reportfile
|
|
= "$job{'RootDir'}/$jobname/scheduled.running/.AFFA4-REPORT";
|
|
lg("writing $reportfile");
|
|
my $rpt = Config::IniFiles->new();
|
|
( my $used, my $avail ) = df( $job{'RootDir'} );
|
|
$rpt->AddSection('Report');
|
|
$rpt->newval( 'Report', 'Date',
|
|
Date::Format::time2str( "%Y%m%d%H%M", time() ) );
|
|
$rpt->newval( 'Report', 'ExecutionTime', time() - $StartTime );
|
|
|
|
foreach my $s ( split( /[\n]/, $rsyncOutput ) ) {
|
|
|
|
#next if ( $s =~ /(^rsync)|(^sent)|(^total size is)|(^File list)|(^[^:]*$)/ );
|
|
#next if ( $s =~ /(^rsync)|(^sent)|(^total size is)|(^File list)|(?=:(?:\s|$))/ );
|
|
#next if ( $s =~ /(^Number)|(^Total)|(^Literal)|(^Matched)|(^File list)|(?=:(?:\s|$))/ );
|
|
# next - doesn't like next
|
|
if (( $s
|
|
=~ /(^Number of)|(^Total file)|(^Total transferred)|(^Literal)|(^Matched)|(^Total bytes)/
|
|
)
|
|
&& ( $s =~ /(?=:(?:\s|$))/ )
|
|
) {
|
|
my @p = split( /:/, $s );
|
|
( my $key = $p[0] ) =~ s/\b(\w)/\U$1/g; # first char uppercase
|
|
$key =~ s/ //g;
|
|
( my $val = trim( $p[1] ) ) =~ s/ .*$//;
|
|
#### ajout AG pour enlever les virgules générées par rsync3
|
|
#### Remove commas generated by Rsync 3
|
|
$val =~ s/,//g;
|
|
##### fin des modifs AG
|
|
$rpt->newval( 'Report', $key, $val );
|
|
}
|
|
}
|
|
|
|
$rpt->newval( 'Report', 'RootDirFilesystemUsed', $used ); # kbytes
|
|
$rpt->newval( 'Report', 'RootDirFilesystemAvail', $avail ); # kbytes
|
|
$rpt->newval( 'Report', 'RootDirFilesystemUsage',
|
|
sprintf( '%.1f', $used / ( $avail + $used ) * 100 ) ); # percent
|
|
$rpt->newval( 'Report', 'ExitStatus', $status );
|
|
$rpt->newval( 'Report', 'Dedup',
|
|
$job{"dedup"} ne 'yes' || not $linkDest ? 'no' : 'yes' );
|
|
$rpt->WriteConfig($reportfile);
|
|
|
|
# save used setup
|
|
my $usfile
|
|
= "$job{'RootDir'}/$jobname/scheduled.running/.$jobname-setup.ini";
|
|
my $js = open( JS, ">$usfile" )
|
|
or lg("Error: Couldn't write $usfile");
|
|
if ($js) {
|
|
print JS "[$jobname]\n";
|
|
print JS "; Created on "
|
|
. trim( Date::Format::ctime( time() ) ) . "\n";
|
|
foreach my $key ( sort { lc($a) cmp lc($b) } keys %job ) {
|
|
next if $key =~ /^_/;
|
|
if ( ref $job{$key} eq 'ARRAY' ) {
|
|
foreach my $r ( sort @{ $job{$key} } ) {
|
|
print JS "$key=$r\n";
|
|
}
|
|
}
|
|
else {
|
|
print JS "$key=$job{$key}\n" if $job{$key};
|
|
}
|
|
}
|
|
close(JS);
|
|
}
|
|
####################################################################################
|
|
#### ajout de modifs
|
|
compareRPMLists("$job{'RootDir'}/$jobname/scheduled.running");
|
|
#### fin ajout modifs
|
|
####################################################################################
|
|
|
|
}
|
|
else {
|
|
affaErrorExit("rsync failed with status $status.");
|
|
}
|
|
}
|
|
|
|
# End of if ( $Command eq 'scheduled' ) {
|
|
|
|
shiftArchives();
|
|
|
|
# We should get here if ne scheduled
|
|
if ( $Command ne 'scheduled' ) {
|
|
my %dd = ( 'daily' => $thisDay, 'weekly' => $thisWeek, 'monthly' => $thisMonth, 'yearly' => $thisYear );
|
|
$job{'_doneDates'}->setval( 'doneDates', $Command, $dd{$Command} );
|
|
$job{'_doneDates'}->RewriteConfig();
|
|
}
|
|
execPostJobCommand($jobname);
|
|
|
|
####################################################################################
|
|
#### debut des modifs
|
|
|
|
if ( $job{'SMEServer'} ne 'no' ) {
|
|
my $postbackup = 'SME/signal-post-backup';
|
|
if ( $job{'remoteHostName'} eq 'localhost' ) {
|
|
execJobCommand( $jobname, $postbackup );
|
|
}
|
|
else {
|
|
execJobCommandRemote( $jobname, $postbackup );
|
|
}
|
|
}
|
|
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
deduplicate($jobname);
|
|
DiskSpaceWarn();
|
|
sendSuccessMesssage();
|
|
affaExit("Completed job '$jobname $Command ($job{'remoteHostName'})'");
|
|
|
|
exit 0;
|
|
|
|
####################################################################################
|
|
|
|
sub sanityCheckConfFiles () {
|
|
my @cfgfiles = getConfigFileList();
|
|
my $error = 0;
|
|
my $count = 0;
|
|
my $line = '';
|
|
foreach my $cfgFile (@cfgfiles) {
|
|
open( INFO, $cfgFile ) or die("Could not open file.");
|
|
$count = 0;
|
|
while ( my $line = <INFO> ) {
|
|
if ( not( $line =~ /\r$/ | $line =~ /\r\n/ | $line =~ /\n$/ ) ) {
|
|
lg("Please check $cfgFile $line and add a LF or CR/LF");
|
|
print "Please check $cfgFile $line and add a LF or CR/LF\n";
|
|
$error = 1;
|
|
last if ++$count == 2;
|
|
}
|
|
}
|
|
}
|
|
close(INFO);
|
|
if ($error) {
|
|
print "Sanity file check failed. Exiting - see your logs";
|
|
affaErrorExit(
|
|
"Your config file MUST end with a line LF or CR/LF - check your log for details"
|
|
);
|
|
} else {
|
|
return;
|
|
}
|
|
}
|
|
|
|
|
|
sub getDefaultConfig() {
|
|
my %job = (
|
|
'AutomountDevice' => '',
|
|
'AutomountOptions' => '',
|
|
'AutomountPoint' => '',
|
|
'AutoUnmount' => '',
|
|
'BandwidthLimit' => 0,
|
|
'chattyOnSuccess' => 0,
|
|
'ConnectionCheckTimeout' => 120,
|
|
'dailyKeep' => 7,
|
|
'Debug' => 'no',
|
|
'dedup' => 'no',
|
|
'dedupKill' => 'no',
|
|
'Description' => '',
|
|
'DiskSpaceWarn' => 'strict', # strict | normal | risky | none
|
|
'_doneDates' => 0,
|
|
'EmailAddress' => ['root'], # multivalue
|
|
'Exclude' => [], # multivalue
|
|
'Include' => [], # multivalue
|
|
'killAt' => '',
|
|
'localNice' => 0,
|
|
'localNiceBinary' => 'nice',
|
|
'localRsyncBinary' => 'rsync',
|
|
'localSSHBinary' => 'ssh',
|
|
'_lockfile' => '',
|
|
'_LockIsSet' => 0,
|
|
'monthlyKeep' => 12,
|
|
'NRPEtrigger' => 24,
|
|
'postJobCommand' => [],
|
|
'postJobCommandRemote' => [],
|
|
'preJobCommand' => [],
|
|
'preJobCommandRemote' => [],
|
|
'RemoteAuthorizedKeysFile' => '.ssh/authorized_keys2', # relative to remoteUser Homedir
|
|
'remoteHostName' => '',
|
|
'remoteNice' => 0,
|
|
'remoteNiceBinary' => '/bin/nice',
|
|
'remoteRsyncBinary' => '/usr/bin/rsync',
|
|
'remoteUser' => 'root',
|
|
'resumeKilledAt' => '',
|
|
'resumeAfterBoot' => 'yes',
|
|
'RetryAfter' => 600,
|
|
'RetryAttempts' => 4,
|
|
'RetryNotification' => 'yes',
|
|
'RootDir' => '/var/affa',
|
|
'rsyncCompress' => 'yes',
|
|
'_rsyncd' => 0,
|
|
'rsyncdMode' => 'no',
|
|
'rsyncdModule' => 'AFFA',
|
|
'rsyncdPassword' => '',
|
|
'rsyncdUser' => 'affa',
|
|
'rsync--inplace' => 'yes',
|
|
'_rsyncLocal' => '/usr/bin/rsync',
|
|
'rsync--modify-window' => 0,
|
|
'rsyncOptions' => '-v ',
|
|
'_rsyncRemote' => '/usr/bin/rsync',
|
|
'rsyncTimeout' => 900,
|
|
'SambaShare' => 'no',
|
|
'SambaValidUser' => ['affa'], # multivalue
|
|
'scheduledKeep' => 1,
|
|
'_sshOpts' => '',
|
|
'sshPort' => 22,
|
|
'status' => 'enabled',
|
|
'TimeSchedule' => ['2230'], # multivalue
|
|
'weeklyKeep' => 4,
|
|
'yearlyKeep' => 2,
|
|
'SMEServer' => 'no',
|
|
'RPMCheck' => 'no',
|
|
'Watchdog' => 'no',
|
|
);
|
|
###########################################################################
|
|
#### ajoute 'SMEServer'=>'no', 3 lignes plus haut
|
|
#### ajoute 'RPMCheck'=>'no', 3 lignes plus haut
|
|
#### ajoute 'Watchdog'=>'no', 3 lignes plus haut
|
|
###########################################################################
|
|
|
|
return %job;
|
|
}
|
|
|
|
sub getMultivalueKeys() {
|
|
my %multi = (
|
|
'EmailAddress' => 'string',
|
|
'Exclude' => 'yes',
|
|
'Include' => 'yes',
|
|
'SambaValidUser' => 'string',
|
|
'TimeSchedule' => 'yes',
|
|
'preJobCommand' => 'yes',
|
|
'preJobCommandRemote' => 'yes',
|
|
'postJobCommand' => 'yes',
|
|
'postJobCommandRemote' => 'yes',
|
|
);
|
|
return %multi;
|
|
}
|
|
|
|
sub showDefaults() {
|
|
%job = getJobConfig('');
|
|
foreach my $key ( sort { lc($a) cmp lc($b) } keys %job ) {
|
|
next if $key =~ /^_/;
|
|
if ( ref $job{$key} eq 'ARRAY' ) {
|
|
foreach my $r ( sort @{ $job{$key} } ) {
|
|
print "$key=$r\n";
|
|
}
|
|
}
|
|
else {
|
|
print "$key=$job{$key}\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
sub getJobConfig( $ ) {
|
|
my $jobname = shift || '';
|
|
my %job = getDefaultConfig();
|
|
|
|
if ( not -f $configfile ) {
|
|
my @cfgfiles = getConfigFileList(); # only valid ones
|
|
my @cmd = ( 'echo', '-n', '>', $configfile, ';', 'chmod', '400', $configfile, ';', 'cat' );
|
|
# mod my @cmd = ( 'echo', '-n', '>', $configfile, ';', 'chmod', '400', $configfile, ';');
|
|
foreach my $s (@cfgfiles) {
|
|
# mod push(@cmd, ('cat', $s,'>>',$configfile,';', 'echo','-n' ,'>>',$configfile, ';' ));
|
|
push( @cmd, '"'.$s.'"' );
|
|
}
|
|
push( @cmd, '>' );
|
|
push( @cmd, $configfile );
|
|
ExecCmd( @cmd, 0 );
|
|
$cfg = Config::IniFiles->new( -file => $configfile, -default => 'GlobalAffaConfig', -nocase => 0 );
|
|
}
|
|
|
|
my %multi = getMultivalueKeys();
|
|
|
|
# configured global Defaults
|
|
my @p = $cfg->Parameters('GlobalAffaConfig');
|
|
foreach my $k (@p) {
|
|
if ( $multi{$k} ) {
|
|
my @m = $cfg->val( $jobname, $k );
|
|
$job{$k} = [@m];
|
|
$job{"_$k"} = join( ',', @m ) if $multi{$k} eq 'string';
|
|
}
|
|
else {
|
|
$job{$k} = $cfg->val( $jobname, $k );
|
|
}
|
|
}
|
|
|
|
# configured Job - reetp needs a fix
|
|
@p = $cfg->Parameters($jobname);
|
|
foreach my $k (@p) {
|
|
if ( not defined $job{$k} ) {
|
|
my @val = $cfg->val( $jobname, $k );
|
|
my $txt = "Unknown parameter '$k=$val[0]' in configuration of job '$jobname'";
|
|
print "$txt\n";
|
|
affaExit($txt);
|
|
}
|
|
if ( $multi{$k} ) {
|
|
my @m = $cfg->val( $jobname, $k );
|
|
######### following 1 line added by Arnaud to solve bug#9449: blank characters into file job.conf##
|
|
map( {
|
|
s/^\s+|\s+$//g;
|
|
$_
|
|
} @m );
|
|
$job{$k} = [@m];
|
|
$job{"_$k"} = join( ',', @m ) if $multi{$k} eq 'string';
|
|
}
|
|
else {
|
|
$job{$k} = $cfg->val( $jobname, $k );
|
|
lg("** Error in job config $jobname: Multivalues are not allowed for key $k") if $job{$k} =~ /\n/;
|
|
$job{$k} =~ s/\n.*//;
|
|
######### following 1 line added by Arnaud to solve bug#9449: blank characters into file job.conf##
|
|
$job{$k} =~ s/^\s+|\s+$//g;
|
|
}
|
|
}
|
|
|
|
# globalStatus overides job status
|
|
$job{'status'} = $job{'globalStatus'} if ( ( $job{'globalStatus'} || '' ) =~ /^(enabled|disabled)$/ );
|
|
|
|
# check and set save keep settings if needed
|
|
$job{'scheduledKeep'} = 1 if ( $job{'scheduledKeep'} < 1 );
|
|
$job{'dailyKeep'} = 0 if ( $job{'dailyKeep'} < 0 );
|
|
$job{'weeklyKeep'} = 0 if ( $job{'weeklyKeep'} < 0 );
|
|
$job{'monthlyKeep'} = 0 if ( $job{'monthlyKeep'} < 0 );
|
|
$job{'yearlyKeep'} = 0 if ( $job{'yearlyKeep'} < 0 );
|
|
|
|
$job{'_rsyncLocal'} = $job{'localRsyncBinary'};
|
|
$job{'_rsyncLocal'} = "$job{'localNiceBinary'} --adjustment=$job{'localNice'} $job{'localRsyncBinary'}"
|
|
if $job{'localNice'};
|
|
$job{'_rsyncRemote'} = $job{'remoteRsyncBinary'};
|
|
$job{'_rsyncRemote'} = "$job{'remoteNiceBinary'} --adjustment=$job{'remoteNice'} $job{'remoteRsyncBinary'}"
|
|
if $job{'remoteNice'};
|
|
$job{'_rsyncd'} = $job{'rsyncdMode'} eq 'yes' ? 1 : 0;
|
|
$ENV{'RSYNC_PASSWORD'} = $job{'rsyncdPassword'};
|
|
$job{'rsyncdPassword'} = $job{'rsyncdPassword'} ? '<not shown>' : '';
|
|
$job{'Debug'} = 'yes' if $opts{'debug'};
|
|
$job{'Archive'} = $job{'Archive'} ? $job{'Archive'} : 'scheduled.0';
|
|
####################################################################################
|
|
#### ajout modifs
|
|
$sshQuiet = $job{'Debug'} eq 'yes' ? '' : '-q';
|
|
#### fin modifs
|
|
####################################################################################
|
|
# get Done Dates
|
|
if ($jobname) {
|
|
# reetp add -i id_rsa_affa here
|
|
$job{'_sshOpts'} = "-p $job{'sshPort'} -i /root/.ssh/id_rsa_affa -o CheckHostIP=no -o StrictHostKeyChecking=no -o HostKeyAlias=$jobname -o UserKnownHostsFile=/root/.ssh/knownhosts-$jobname" . ( $job{'Debug'} ne 'yes' ? ' -q' : '' );
|
|
$job{'_lockfile'} = "$lockdir/$jobname";
|
|
}
|
|
return %job;
|
|
}
|
|
|
|
sub checkConfig() {
|
|
my $errcnt = 0;
|
|
my $casecnt = 0;
|
|
my %multi = getMultivalueKeys();
|
|
my %defaults = getDefaultConfig();
|
|
my %defaults_lc = map { lc $_ => $_ } keys %defaults;
|
|
foreach my $k ( keys %defaults ) {
|
|
$defaults_lc{ lc $k } = $k;
|
|
}
|
|
my @cfiles = getConfigFileList();
|
|
foreach my $f (@cfiles) {
|
|
my $cfg = Config::IniFiles->new( -file => $f, -nocase => 0 );
|
|
foreach my $s ( $cfg->Sections() ) {
|
|
$s =~ /([a-z0-9_\.-]*)/i;
|
|
print "Checking job section $s ($f)\n";
|
|
if ( $s ne $1 ) {
|
|
print "*** ERROR: Illegal characters in job name\n";
|
|
$errcnt++;
|
|
}
|
|
my @parms = $cfg->Parameters($s);
|
|
foreach my $p (@parms) {
|
|
my @v = $cfg->val( $s, $p );
|
|
my $val = $v[0];
|
|
if ( $p =~ /(sendStatus|globalStatus)/ ) {
|
|
if ( $s ne 'GlobalAffaConfig' ) {
|
|
print "*** ERROR: Key $p only allowed in GlobalAffaConfig section\n";
|
|
$errcnt++;
|
|
}
|
|
elsif ( $p eq 'globalStatus' and not $val =~ /(enabled|disabled|jobs)/ ) {
|
|
print "*** ERROR: Bad value: $p=$val\n";
|
|
$errcnt++;
|
|
}
|
|
elsif ( $p eq 'sendStatus' and not $val =~ /(daily|weekly|monthly|never)/ ) {
|
|
print "*** ERROR: Bad value: $p=$val\n";
|
|
$errcnt++;
|
|
}
|
|
next;
|
|
}
|
|
if ( $p =~ /^_/ or not defined $defaults{$p} and not defined $defaults_lc{ lc $p } ) {
|
|
print "*** ERROR: Unknown key: $p\n";
|
|
$errcnt++;
|
|
next;
|
|
}
|
|
if ( not defined $defaults{$p} and defined $defaults_lc{ lc $p } ) {
|
|
my $err = renameConfigKey( $s, $p, $defaults_lc{ lc $p } );
|
|
print "*** CASE ERROR: $p -> $defaults_lc{lc $p} FIXED.\n";
|
|
$p = $defaults_lc{ lc $p };
|
|
$casecnt++;
|
|
}
|
|
if ( scalar @v > 1 and not $multi{$p} ) {
|
|
print "*** ERROR: Multivalues are not allowed for key $p\n";
|
|
$errcnt++;
|
|
next;
|
|
}
|
|
if ( $p eq 'killAt' or $p eq 'resumeKilledAt' ) {
|
|
if ( not $val =~ /^\d\d\d\d$/ or $val > 2359 ) {
|
|
print "*** ERROR: Bad value: $p=$val\n";
|
|
$errcnt++;
|
|
}
|
|
}
|
|
if ( $p eq 'TimeSchedule' ) {
|
|
foreach my $t (@v) {
|
|
if ( not $t =~ /^\d\d\d\d$/ or $t > 2359 ) {
|
|
print "*** ERROR: Bad value: $p=$t\n";
|
|
$errcnt++;
|
|
}
|
|
}
|
|
}
|
|
if ( $p eq 'RootDir' ) {
|
|
if ( not -d $val ) {
|
|
print "*** ERROR: Directory does not exist: $p=$val\n";
|
|
$errcnt++;
|
|
}
|
|
}
|
|
if ( $p eq 'DiskSpaceWarn' ) {
|
|
if ( not $val =~ /^(strict|normal|risky|none)$/ ) {
|
|
print "*** ERROR: Bad value: $p=$val\n";
|
|
$errcnt++;
|
|
}
|
|
}
|
|
if ( $p =~ /JobCommand/ ) {
|
|
foreach my $s (@v) {
|
|
if ( not -x "/etc/affa/scripts/$s" ) {
|
|
print "*** ERROR: No executable script found: $p=$s\n";
|
|
$errcnt++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
$cfg = Config::IniFiles->new( -file => $f, -nocase => 0 ) if $casecnt;
|
|
if ( $s ne 'GlobalAffaConfig' ) {
|
|
if ( not $cfg->val( $s, 'remoteHostName' ) ) {
|
|
print "*** ERROR: Key remoteHostName is mandatory\n";
|
|
$errcnt++;
|
|
next;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
if ( not $errcnt and not $casecnt ) {
|
|
print "Configuration is ok.\n";
|
|
}
|
|
else {
|
|
print $casecnt ? "Fixed $casecnt case errors\n" : '';
|
|
print $errcnt ? "Configuration has $errcnt errors\n" : '';
|
|
}
|
|
}
|
|
|
|
sub getJobs() {
|
|
my @joblist;
|
|
foreach my $job ( $cfg->Sections() ) {
|
|
next if $job eq 'GlobalAffaConfig';
|
|
push( @joblist, $job );
|
|
}
|
|
return @joblist;
|
|
}
|
|
|
|
sub getConfigFileList() {
|
|
dbg('Fetching list of all config files');
|
|
|
|
my @dirList = qw (/etc/affa/ /etc/affa/conf.d/);
|
|
my @ls = ();
|
|
my @list = ();
|
|
|
|
foreach my $dir (@dirList) {
|
|
if ( -d $dir ) {
|
|
my @cmd = (
|
|
'find', "$dir", '-maxdepth 1', '-type', 'f', '-name', '"*.conf"'
|
|
);
|
|
ExecCmd( @cmd, 0 );
|
|
@ls = split( /[\r\n]/, $ExecCmdOut );
|
|
@list = (@list, @ls);
|
|
}
|
|
}
|
|
|
|
my @ret = ();
|
|
|
|
foreach my $s (@list) {
|
|
my $c = Config::IniFiles->new( -file => $s );
|
|
if ( not $c ) {
|
|
foreach my $e (@Config::IniFiles::errors) {
|
|
$e =~ s/[\r\n][\t ]*/ /g;
|
|
my $txt = "CONFIG ERROR: $e";
|
|
print "$txt\n";
|
|
lg($txt);
|
|
}
|
|
my $txt = "CONFIG ERROR: File '$s' ignored!";
|
|
print "$txt\n";
|
|
lg($txt);
|
|
}
|
|
else {
|
|
push( @ret, $s );
|
|
}
|
|
}
|
|
return sort @ret;
|
|
}
|
|
|
|
sub rewriteConfigVal($$$) {
|
|
( my $jobname, my $key, my $newval ) = @_;
|
|
my $err = 1;
|
|
my $c = openOrgConfig($jobname);
|
|
if ($c) {
|
|
$c->setval( $jobname, $key, $newval );
|
|
writeConfigFile($c);
|
|
$err = 0;
|
|
}
|
|
return $err;
|
|
}
|
|
|
|
sub renameConfigKey($$$) {
|
|
( my $jobname, my $oldkey, my $newkey ) = @_;
|
|
my $err = 1;
|
|
my $c = openOrgConfig($jobname);
|
|
if ($c) {
|
|
my @val = $c->val( $jobname, $oldkey );
|
|
$c->delval( $jobname, $oldkey );
|
|
my @nv = $c->val( $jobname, $newkey );
|
|
if ( scalar @nv ) {
|
|
push( @val, @nv );
|
|
}
|
|
$c->newval( $jobname, $newkey, @val );
|
|
writeConfigFile($c);
|
|
$err = 0;
|
|
}
|
|
return $err;
|
|
}
|
|
|
|
sub openOrgConfig($) {
|
|
my $jobname = shift;
|
|
my $cfg;
|
|
my @list = getConfigFileList();
|
|
foreach my $s (@list) {
|
|
$cfg = Config::IniFiles->new( -file => $s );
|
|
if ( $cfg->SectionExists($jobname) ) {
|
|
last;
|
|
}
|
|
undef $cfg;
|
|
}
|
|
return $cfg;
|
|
}
|
|
|
|
sub setLock() {
|
|
open( LOCK, ">$job{'_lockfile'}" ) or die "Error: Couldn't create lockfile $job{'_lockfile'}\n";
|
|
print LOCK "$process_id\n";
|
|
close(LOCK) or warn "Error: Couldn't close lockfile $job{'_lockfile'}\n";
|
|
$job{'_LockIsSet'} = 1;
|
|
}
|
|
|
|
sub removeLock() {
|
|
unlink( $job{'_lockfile'} ) if ( -f $job{'_lockfile'} && $job{'_LockIsSet'} );
|
|
$job{'_LockIsSet'} = 0;
|
|
}
|
|
|
|
sub getLock($) {
|
|
my $jobname = shift;
|
|
%job = getJobConfig($jobname);
|
|
my $lockpid = 0;
|
|
if ( open( LOCK, "<$job{'_lockfile'}" ) ) {
|
|
$lockpid = <LOCK>;
|
|
chomp($lockpid);
|
|
close(LOCK);
|
|
}
|
|
if ($lockpid) {
|
|
if ( -f "/proc/$lockpid/stat" ) {
|
|
if ( open( STAT, "</proc/$lockpid/stat" ) ) {
|
|
my $stat = <STAT>;
|
|
chomp($stat);
|
|
close(STAT);
|
|
if ( not( $stat =~ /^$lockpid \(affa\)/ ) ) {
|
|
$lockpid = 0;
|
|
unlink $job{'_lockfile'};
|
|
lg("Orphaned lock found and removed.");
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
$lockpid = 0;
|
|
unlink $job{'_lockfile'};
|
|
lg("Orphaned lock found and removed.");
|
|
}
|
|
}
|
|
return $lockpid;
|
|
}
|
|
|
|
# returns process id if dedup is running
|
|
sub findProcessId($$) {
|
|
( my $treepid, my $pattern ) = @_;
|
|
my %pt;
|
|
my $ret = '';
|
|
getChildProcess( $treepid, \%pt );
|
|
foreach my $p ( keys %pt ) {
|
|
if ( $pt{$p} =~ /$pattern/ ) {
|
|
$ret = $p;
|
|
last;
|
|
}
|
|
}
|
|
return $ret;
|
|
}
|
|
|
|
sub getChildProcess($$) {
|
|
( my $parent, my $pt ) = @_;
|
|
my $proc_table = Proc::ProcessTable->new();
|
|
foreach my $proc ( @{ $proc_table->table() } ) {
|
|
if ( $proc->ppid == $parent ) {
|
|
my $cmd = '';
|
|
if ( open( CMD, "</proc/" . $proc->pid . "/cmdline" ) ) {
|
|
$cmd = <CMD> || '';
|
|
close(CMD);
|
|
$cmd =~ s/\0/ /g;
|
|
}
|
|
$pt->{ $proc->pid } = $cmd;
|
|
getChildProcess( $proc->pid, $pt );
|
|
}
|
|
}
|
|
return $pt;
|
|
}
|
|
|
|
sub getProcessState($) {
|
|
my $jobname = shift;
|
|
my $lockpid = getLock($jobname);
|
|
my $state = '';
|
|
if ($lockpid) {
|
|
my $job = getJobConfig($jobname);
|
|
if ( findProcessId( $lockpid, "$job{'localRsyncBinary'}.*scheduled.running" ) ) {
|
|
$state = "running rsync";
|
|
}
|
|
elsif ( findProcessId( $lockpid, $dedupBinary ) ) {
|
|
$state = "deduplicating";
|
|
}
|
|
else {
|
|
$state = 'waiting';
|
|
}
|
|
}
|
|
else {
|
|
my $job = getJobConfig($jobname);
|
|
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
|
|
if ( -d "$job{'RootDir'}/$jobname/scheduled.running" ) {
|
|
$state = 'rsync interrupted';
|
|
}
|
|
elsif ( -f $rptfile ) {
|
|
my $rpt = Config::IniFiles->new( -file => $rptfile );
|
|
if ( $rpt->exists( 'Report', 'Dedup' )
|
|
&& $rpt->val( 'Report', 'Dedup' ) eq 'yes'
|
|
&& !$rpt->exists( 'Report', 'DedupDate' ) ) {
|
|
$state = 'dedup interrupted';
|
|
}
|
|
}
|
|
}
|
|
return $state;
|
|
}
|
|
|
|
sub checkConnection($) {
|
|
my $jobname = shift;
|
|
return checkConnection_silent( $jobname, 0, 0 );
|
|
}
|
|
|
|
sub checkConnection_silent($$$) {
|
|
|
|
####################################################################################
|
|
#### debut modifs
|
|
return 0 if ( $job{'remoteHostName'} eq 'localhost' );
|
|
|
|
#### fin modifs
|
|
####################################################################################
|
|
|
|
my ( $jobname, $viapi, $silent ) = @_;
|
|
my %job = getJobConfig($jobname);
|
|
my $status = 0;
|
|
my @cmd;
|
|
if ( $job{'_rsyncd'} ) {
|
|
lg( "Checking rsyncd connection to " . $job{'remoteHostName'} );
|
|
@cmd = (
|
|
$job{'_rsyncLocal'}, '-dq',
|
|
( $job{'rsyncdUser'} ? $job{'rsyncdUser'} . '@' : '' )
|
|
. $job{'remoteHostName'} . "::'"
|
|
. $job{'rsyncdModule'} . "'"
|
|
);
|
|
not ExecCmd( @cmd, 0 )
|
|
or affaErrorExit( "Rsyncd connection to "
|
|
. $job{'remoteHostName'}
|
|
. " failed. Did you set the rsyncdUser, rsyncdPassword and rsyncdModule properties correctly?" );
|
|
}
|
|
else {
|
|
lg( "Checking SSH connection to " . $job{'remoteUser'} . '@' . $job{'remoteHostName'} );
|
|
@cmd = ( '/usr/bin/ssh', '-o', "ConnectTimeout=$job{'ConnectionCheckTimeout'}", '-o', 'PasswordAuthentication=no', $job{'_sshOpts'}, $job{'remoteUser'} . '@' . $job{'remoteHostName'}, 'echo OK'
|
|
);
|
|
ExecCmd( @cmd, 0 );
|
|
chomp($ExecCmdOut);
|
|
if ( $ExecCmdOut ne "OK" ) {
|
|
$status = -1;
|
|
if ( !$silent ) {
|
|
affaErrorExit(
|
|
"SSH connection to " . $job{'remoteHostName'} . " failed. Did you send the public key?" );
|
|
}
|
|
}
|
|
}
|
|
return $status;
|
|
}
|
|
|
|
####################################################################################
|
|
#### début ajout modifs
|
|
sub installedRPMsList($) {
|
|
return if $job{'SMEServer'} eq 'no' or $job{'RPMCheck'} ne 'yes';
|
|
my $forceLocal = shift(@_);
|
|
|
|
# requires bash shell on remote host
|
|
my @cmd;
|
|
if ( $job{'remoteHostName'} eq 'localhost' or $forceLocal ) {
|
|
lg("writing list of installed RPMs on localhost ($rpmlist)");
|
|
@cmd = ( "/sbin/e-smith/affa-rpmlist.sh", ">$rpmlist" );
|
|
}
|
|
else {
|
|
lg( "writing list of installed RPMs on " . $job{'remoteHostName'} . " ($rpmlist)" );
|
|
|
|
#### ligne suivante: rajouté $jobname
|
|
remoteCopy( $jobname, "/sbin/e-smith/affa-rpmlist.sh", "/sbin/e-smith/affa-rpmlist.sh" );
|
|
@cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jobname", '-o',
|
|
"UserKnownHostsFile=/root/.ssh/knownhosts-$jobname", $sshQuiet, $job{'remoteHostName'}, "'/sbin/e-smith/affa-rpmlist.sh>$rpmlist'"
|
|
);
|
|
}
|
|
# not ExecCmd( @cmd, 0 ) or affaErrorExit("writing list of installed RPMs failed.");
|
|
my $listStatus = ExecCmd (@cmd, 0);
|
|
if ($listStatus ne 0) {
|
|
affaErrorExit("Status = $listStatus - Error writing list of installed RPMs");
|
|
}
|
|
|
|
}
|
|
|
|
sub compareRPMLists($) {
|
|
return if $job{'SMEServer'} eq 'no' or $job{'remoteHostName'} eq 'localhost' or $job{'RPMCheck'} ne 'yes';
|
|
my $localRPMList = shift;
|
|
lg("Comparing installed RPMs on backup und remote host");
|
|
my $RPMPath = "home/e-smith/db/affa-rpmlist";
|
|
my %remoteRPM;
|
|
lg ("Reading remote RPM list Local = $localRPMList RPMPath = $RPMPath");
|
|
my $newRPMPath = "$localRPMList/$RPMPath";
|
|
lg ("New path = $newRPMPath");
|
|
open( RP, "$newRPMPath" ) or affaErrorExit("Couldn't open $localRPMList/$RPMPath");
|
|
while (<RP>) {
|
|
my @z = split( " ", $_ );
|
|
$remoteRPM{ $z[0] } = $z[1];
|
|
}
|
|
close(RP);
|
|
installedRPMsList(1);
|
|
my %localRPM;
|
|
dbg("Reading local RPM list");
|
|
open( RP, "</$RPMPath" ) or lg("Error: Couldn't open /$RPMPath");
|
|
while (<RP>) {
|
|
my @z = split( " ", $_ );
|
|
$localRPM{ $z[0] } = $z[1];
|
|
}
|
|
close(RP);
|
|
|
|
# vergleichen
|
|
my ( @missing, @VersionMismatch );
|
|
my $md5 = Digest::MD5->new;
|
|
foreach my $p ( keys %remoteRPM ) {
|
|
if ( not $localRPM{$p} ) {
|
|
push( @missing, "$p-" . $remoteRPM{$p} );
|
|
$md5->add( "$p-" . $remoteRPM{$p} );
|
|
}
|
|
elsif ( $localRPM{$p} ne $remoteRPM{$p} ) {
|
|
push( @VersionMismatch, "$p-" . $remoteRPM{$p} );
|
|
$md5->add( "$p-" . $remoteRPM{$p} );
|
|
}
|
|
}
|
|
my $RPMFilename = "$job{'RootDir'}/$jobname/rpms-missing.txt";
|
|
my $MD5Filename = "$job{'RootDir'}/$jobname/.md5-rpms-missing-" . $md5->hexdigest;
|
|
dbg("RPMFilename=$RPMFilename");
|
|
dbg("MD5Filename=$MD5Filename");
|
|
if ( not -f $MD5Filename ) {
|
|
open( RP, ">$RPMFilename" ) or affaErrorExit("Couldn't open $RPMFilename for writing.");
|
|
|
|
my $out = '';
|
|
foreach my $k (@missing) {
|
|
$out .= "$k\n";
|
|
}
|
|
if ($out) {
|
|
print RP "* \n";
|
|
print RP "* The following packages are installed on " . $job{'remoteHostName'} . ",\n";
|
|
print RP "* but they are missing on this backup host:\n";
|
|
print RP "* \n";
|
|
print RP $out;
|
|
}
|
|
|
|
$out = '';
|
|
foreach my $k (@VersionMismatch) {
|
|
$out .= "$k\n";
|
|
}
|
|
if ($out) {
|
|
print RP "\n* \n";
|
|
print RP "* The following packages are installed on both,\n";
|
|
print RP "* the source " . $job{'remoteHostName'} . " and on this backup host,\n";
|
|
print RP "* but the version does not match:\n";
|
|
print RP "* \n";
|
|
print RP $out;
|
|
}
|
|
|
|
close(RP);
|
|
|
|
if ( $job{'RPMCheck'} eq 'yes' and not -f $MD5Filename and $job{'_EmailAddress'} ) {
|
|
my $msg = new Mail::Send;
|
|
$msg->subject(
|
|
"Missing RPMs on $SystemName.$DomainName ($LocalIP) compared with " . $job{'remoteHostName'} );
|
|
$msg->to( $job{'_EmailAddress'} );
|
|
$msg->set( "From", "\"Affa Backup Server\" <noreply\@$SystemName.$DomainName>" );
|
|
my $fh = $msg->open;
|
|
open( RP, "<$RPMFilename" ) or affaErrorExit("Couldn't open $RPMFilename.");
|
|
#### attention dans AffaV2, c'est $job{'EmailAdresses'} donc modifié aussi dans
|
|
#### la déclaration des variables et paramètres
|
|
while (<RP>) {
|
|
print $fh $_;
|
|
}
|
|
close(RP);
|
|
$fh->close;
|
|
lg( "RPM check message sent to " . $job{'_EmailAddress'} );
|
|
}
|
|
|
|
unlink( glob("$job{'RootDir'}/$jobname/.md5-rpms-missing-*") );
|
|
open( RP, ">$MD5Filename" ) or affaErrorExit("Couldn't open $MD5Filename for writing.");
|
|
print RP "md5sum of content of file $RPMFilename\n";
|
|
close(RP);
|
|
}
|
|
}
|
|
|
|
###### fin ajout modifs
|
|
####################################################################################
|
|
|
|
# get directories and files to backup from db
|
|
# Add standard dirs if SME.
|
|
sub getSourceDirs($) {
|
|
my $jobname = shift; # From ($)
|
|
my %job = getJobConfig($jobname);
|
|
my @result = ();
|
|
my @remoteDirectories = ();
|
|
my @remoteDirList = ();
|
|
my @remoteDirs = ();
|
|
my @localDirList = ();
|
|
my @localDirs = ();
|
|
my $remoteDir = '';
|
|
my $backupListScript = "SME/backupList.sh";
|
|
my $remoteHost = $job{'remoteHostName'};
|
|
|
|
####################################################
|
|
# This is to rise a specific backup
|
|
|
|
if ( $opts{'rise'} || $opts{'full-restore'} ) {
|
|
$archive = $restoreVer eq '' ? $job{'Archive'} : $restoreVer; #Overide archive from the CLI if set
|
|
printf ("getSourceDirs Archive $archive\n");
|
|
}
|
|
|
|
# For non SME
|
|
my $exclude = getExcludedString();
|
|
my $include = getIncludedString()
|
|
; #--include="/etc/sudoers" --include="/media/tmp" --include="/usr/share/nextcloud"
|
|
|
|
####################################################################################
|
|
#### debut des modifs: recopie a partir de la version 2rc5
|
|
|
|
if ( $job{'SMEServer'} ne 'no' ) {
|
|
|
|
# reetp for reference here we use the esmith standard backup routine
|
|
# to get the standard dirs for backup
|
|
|
|
# Includes & Excludes
|
|
my @include = getIncExc("Include");
|
|
my @exclude = getIncExc("Exclude");
|
|
|
|
# For a job run we have to create the local and remote directory lists
|
|
# For restore we only need to read the lists that are stored
|
|
# for rise ??
|
|
|
|
# Only continue here if both are 0 ie this is a backup operation
|
|
if ( !( ( $opts{'full-restore'} ) || ( $opts{'rise'} ) ) ) {
|
|
|
|
# If we are SME remote host - not localhost
|
|
if ( $remoteHost ne 'localhost' ) {
|
|
|
|
# SME Remote routine
|
|
lg("remote copy backupList.pl to target");
|
|
remoteCopy( $jobname, "/etc/affa/scripts/SME/backupList.pl",
|
|
"/tmp/backupList.pl" );
|
|
@remoteDirectories
|
|
= execJobCommandRemote( $jobname, $backupListScript );
|
|
|
|
@remoteDirList = split( ',', $remoteDirectories[0] );
|
|
|
|
# We have the remote directories in @remoteDirs
|
|
# We have the additional includes in @includes
|
|
|
|
# For full restore we will have to save both src and destination dirs
|
|
foreach my $dir (@remoteDirList) {
|
|
$dir = "/$dir" if not $dir =~ /^\//;
|
|
chomp $dir;
|
|
trim($dir);
|
|
push( @localDirs, $dir );
|
|
my $remoteHostDir
|
|
= $job{'remoteUser'} . '@' . "$remoteHost:$dir";
|
|
push( @remoteDirs, $remoteHostDir );
|
|
}
|
|
|
|
foreach my $dir (@include) {
|
|
$dir = "/$dir" if not $dir =~ /^\//;
|
|
chomp($dir);
|
|
push( @localDirs, $dir );
|
|
my $remoteHostDir
|
|
= $job{'remoteUser'} . '@' . "$remoteHost:$dir";
|
|
push( @remoteDirs, $remoteHostDir );
|
|
}
|
|
}
|
|
else {
|
|
# SME local routine - use SME Backup to discover dirs
|
|
# Slightly duplicated as it should produce identical local and remote strings
|
|
my $b = new esmith::Backup
|
|
or die "Error: Couldn't create Backup object\n";
|
|
foreach my $k ( $b->restore_list ) {
|
|
$k = "/$k" if not $k =~ /^\//;
|
|
chomp $k;
|
|
trim($k);
|
|
push( @localDirs, $k );
|
|
push( @remoteDirs, $k );
|
|
|
|
}
|
|
push( @localDirs, "/etc/affa" );
|
|
|
|
foreach my $dir (@include) {
|
|
$dir = "/$dir" if not $dir =~ /^\//;
|
|
chomp($dir);
|
|
trim($dir);
|
|
push( @localDirs, $dir );
|
|
push( @remoteDirs, $dir );
|
|
}
|
|
push( @remoteDirs, "/etc/affa" );
|
|
}
|
|
|
|
# Remove duplicates
|
|
# This is done in the calling sub getSourceDirs but we need to do it before
|
|
@remoteDirs = sort ( keys { map { $_ => 1 } @remoteDirs } );
|
|
@localDirs = sort ( keys { map { $_ => 1 } @localDirs } );
|
|
|
|
# Write @SrcDirList to file
|
|
open my $fd, '>',
|
|
"$job{'RootDir'}/$jobname/scheduled.running/src-$backupList"
|
|
or die
|
|
"Cannot open $job{'RootDir'}/$jobname/scheduled.running/src-$backupList : $!";
|
|
print $fd ( join ',', @localDirs );
|
|
close $fd;
|
|
|
|
# Write @remoteDirList to file
|
|
open my $fh, '>',
|
|
"$job{'RootDir'}/$jobname/scheduled.running/dest-$backupList"
|
|
or die
|
|
"Cannot open $job{'RootDir'}/$jobname/scheduled.running/dest-$backupList : $!";
|
|
print $fh ( join ',', @remoteDirs );
|
|
close $fh;
|
|
}
|
|
|
|
# This is restore and rise(?)
|
|
else {
|
|
# read @localDirList and @remoteDirList from file
|
|
# Which dir does it restore - from Argv above in full-restore
|
|
open my $fd, '<',
|
|
"$job{'RootDir'}/$jobname/$archive/src-$backupList"
|
|
or die
|
|
"Cannot open $job{'RootDir'}/$jobname/$archive/src-$backupList : $!";
|
|
chomp( @localDirList = <$fd> );
|
|
@localDirs = split( ',', $localDirList[0] );
|
|
close $fd;
|
|
|
|
open my $fh, '<',
|
|
"$job{'RootDir'}/$jobname/$archive/dest-$backupList"
|
|
or die
|
|
"Cannot open $job{'RootDir'}/$jobname/$archive/dest-$backupList : $!";
|
|
chomp( @remoteDirList = <$fh> );
|
|
@remoteDirs = split( ',', $remoteDirList[0] );
|
|
close $fh;
|
|
|
|
# Check if the two arrays match
|
|
my $err = 0;
|
|
my @splitRemote = ();
|
|
foreach my $localDir (@localDirs) {
|
|
my $answer = grep /\Q$localDir\E$/, @remoteDirs;
|
|
if ($answer) {
|
|
#print "Local $localDir found in remote \n";
|
|
}
|
|
else {
|
|
$err = 1;
|
|
lg( "Local directory $localDir not found in remote. Check the backup directory lists"
|
|
);
|
|
}
|
|
}
|
|
foreach my $remote (@remoteDirs) {
|
|
@splitRemote = split( ':', $remote );
|
|
my $remoteDir = $splitRemote[1];
|
|
my $answer = grep /\Q$remoteDir\E$/, @localDirs;
|
|
if ($answer) {
|
|
# print "Remote $remote found in local \n";
|
|
}
|
|
else {
|
|
$err = 1;
|
|
lg( "Remote directory $remoteDir not found in local. Check the backup directory lists"
|
|
);
|
|
}
|
|
}
|
|
# Exit on any errors
|
|
if ($err == 1) {
|
|
affaErrorExit ("Check the global log for details, Check directory lists match");
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
# Else this is not SME
|
|
# In which case you need routines for localhost & remote host
|
|
else {
|
|
# Oh dear we are standard linux and I have no solution yet
|
|
# $job{'SMEServer'} ne no
|
|
print "This is not a SME server - no configuration available";
|
|
affaExit("Standard Linux - we should not be here");
|
|
}
|
|
|
|
return ( \@localDirs, \@remoteDirs );
|
|
}
|
|
|
|
sub getSourceDirsString($) {
|
|
my $jobname = shift;
|
|
my ($local, $remote) = getSourceDirs($jobname);
|
|
my @localDirs = @$local;
|
|
my @remoteDirs = @$remote;
|
|
my $localresult = '';
|
|
my $remoteresult = '';
|
|
|
|
# @SourceDirs = keys {map {$_ => 1} @SourceDirs};
|
|
foreach my $k (@localDirs) {
|
|
$localresult .= '"' . $k . '" ';
|
|
}
|
|
foreach my $k (@remoteDirs) {
|
|
$remoteresult .= '"' . $k . '" ';
|
|
}
|
|
trim ($localresult);
|
|
trim ($remoteresult);
|
|
return ($localresult, $remoteresult);
|
|
}
|
|
|
|
# Get Include & Exclude directories for SME
|
|
sub getIncExc ($) {
|
|
my $IncExc = shift;
|
|
my @result = ();
|
|
foreach my $i ( @{ $job{$IncExc} } ) {
|
|
$i =~ s/^\///;
|
|
$i =~ s/\/$//;
|
|
$i =~ s/"/\\"/g;
|
|
$i = "/$i";
|
|
chomp ($i);
|
|
trim ($i);
|
|
push (@result, $i) # Have to leave the / there now;
|
|
#print $i;
|
|
}
|
|
return @result;
|
|
}
|
|
|
|
# get files to include
|
|
sub getIncludedString() {
|
|
my $result = '';
|
|
foreach my $k ( @{ $job{'Include'} } ) {
|
|
$k =~ s/^\///;
|
|
$k =~ s/\/$//;
|
|
$k =~ s/"/\\"/g;
|
|
$result .= '--include="/' . $k . '" ' if $k and not $k =~ /^\//;
|
|
}
|
|
chomp($result);
|
|
return trim($result);
|
|
}
|
|
|
|
# get directories and files to exclude
|
|
sub getExcludedString() {
|
|
my $result = '';
|
|
foreach my $k ( @{ $job{'Exclude'} } ) {
|
|
$k =~ s/^\///;
|
|
$k =~ s/\/$//;
|
|
$k =~ s/"/\\"/g;
|
|
$result .= '--exclude="/' . $k . '" ' if $k;
|
|
}
|
|
chomp($result);
|
|
return trim($result);
|
|
}
|
|
|
|
|
|
sub getLinkdest($$) {
|
|
( my $jobname, my $prev ) = @_;
|
|
my %job = getJobConfig($jobname);
|
|
my %timestamps;
|
|
my @st;
|
|
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
|
|
my $ar;
|
|
while ( defined( $ar = readdir(DIR) ) ) {
|
|
next if not $ar =~ /^(scheduled|daily|weekly|monthly|yearly)\.[0-9]+$/;
|
|
my $d = getReportVal( $jobname, $ar );
|
|
if ( $d =~ /^\d{12}$/ ) {
|
|
$timestamps{$ar} = $d;
|
|
}
|
|
}
|
|
foreach my $k ( sort { $timestamps{$b} cmp $timestamps{$a} } keys %timestamps ) {
|
|
push( @st, $k );
|
|
}
|
|
return $prev ? ( $st[1] || '' ) : ( $st[0] || '' );
|
|
}
|
|
|
|
sub shiftArchives() {
|
|
lg("Shifting backup archives...");
|
|
my $nothingDone = "Nothing to be done.";
|
|
my $JobDir = "$job{'RootDir'}/$jobname";
|
|
my $basename = "$JobDir/$Command";
|
|
if ( -d "$basename.0" and ( $Command ne 'scheduled' or -f "$JobDir/scheduled.running/.AFFA4-REPORT" ) ) {
|
|
my $keep = $job{ $Command . 'Keep' } - 1;
|
|
if ( -d "$basename.$keep" ) {
|
|
File::Path::mkpath( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 0, 0700 )
|
|
unless -d "$job{'RootDir'}/$jobname/.AFFA-TRASH";
|
|
$nothingDone = '';
|
|
moveFileorDir( "$basename.$keep", "$job{'RootDir'}/$jobname/.AFFA-TRASH/$Command.$keep-$curtime-$$" );
|
|
}
|
|
for ( my $i = $keep; $i > 0; $i-- ) {
|
|
if ( -d "$basename." . ( $i - 1 ) ) {
|
|
$nothingDone = '';
|
|
moveFileorDir( "$basename." . ( $i - 1 ), "$basename.$i" );
|
|
chmod( 0700, "$basename.$i" );
|
|
}
|
|
}
|
|
}
|
|
if ( $Command eq 'scheduled' and not -d "$JobDir/scheduled.0" and -f "$JobDir/scheduled.running/.AFFA4-REPORT" ) {
|
|
moveFileorDir( "$JobDir/scheduled.running", "$JobDir/scheduled.0" );
|
|
$nothingDone = '';
|
|
}
|
|
if ( $Command eq 'yearly' ) {
|
|
my $src = "$JobDir/monthly." . ( $job{'monthlyKeep'} - 1 );
|
|
$src = "$JobDir/weekly." . ( $job{'weeklyKeep'} - 1 ) if ( not -d $src ) && !$job{'monthlyKeep'};
|
|
$src = "$JobDir/daily." . ( $job{'dailyKeep'} - 1 )
|
|
if ( not -d $src ) && !$job{'monthlyKeep'} && !$job{'weeklyKeep'};
|
|
$src = "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 )
|
|
if ( not -d $src ) && !$job{'monthlyKeep'} && !$job{'weeklyKeep'} && !$job{'dailyKeep'};
|
|
if ( -d $src ) {
|
|
moveFileorDir( $src, "$basename.0" );
|
|
chmod( 0700, "$basename.0" );
|
|
$nothingDone = '';
|
|
}
|
|
}
|
|
elsif ( $Command eq 'monthly' ) {
|
|
my $src = "$JobDir/weekly." . ( $job{'weeklyKeep'} - 1 );
|
|
$src = "$JobDir/daily." . ( $job{'dailyKeep'} - 1 ) if ( not -d $src ) && !$job{'weeklyKeep'};
|
|
$src = "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 )
|
|
if ( not -d $src ) && !$job{'weeklyKeep'} && !$job{'scheduledKeep'};
|
|
if ( -d $src ) {
|
|
moveFileorDir( $src, "$basename.0" );
|
|
chmod( 0700, "$basename.0" );
|
|
$nothingDone = '';
|
|
}
|
|
}
|
|
elsif ( $Command eq 'weekly' ) {
|
|
my $src = "$JobDir/daily." . ( $job{'dailyKeep'} - 1 );
|
|
$src = "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 ) if ( not -d $src ) && !$job{'dailyKeep'};
|
|
if ( -d $src ) {
|
|
moveFileorDir( $src, "$basename.0" );
|
|
chmod( 0700, "$basename.0" );
|
|
$nothingDone = '';
|
|
}
|
|
}
|
|
elsif ( $Command eq 'daily' and -d "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 ) ) {
|
|
moveFileorDir( "$JobDir/scheduled." . ( $job{'scheduledKeep'} - 1 ), "$basename.0" );
|
|
chmod( 0700, "$basename.0" );
|
|
$nothingDone = '';
|
|
}
|
|
removeDir( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 1 );
|
|
lg($nothingDone) if $nothingDone;
|
|
}
|
|
|
|
####################################################################################
|
|
#### début des modifs: copié à partir de AffaV2
|
|
|
|
sub installWatchdog($) {
|
|
return if $job{'Watchdog'} ne 'yes' or $job{'SMEServer'} ne 'yes' or $job{'remoteHostName'} eq 'localhost';
|
|
#### suppression de "$ESX or " entre le if et $job{'Watchdog'}
|
|
|
|
my $t = shift;
|
|
my $nextScheduled = ( $t || '86400' ) + 600;
|
|
lg( "Installing watchdog on " . $job{'remoteHostName'} . " with dt=$nextScheduled seconds" );
|
|
|
|
my $scheduled = Date::Format::ctime( time() );
|
|
chomp($scheduled);
|
|
my $WDName = "affa-watchdog-$jobname-$LocalIP";
|
|
my $trigger = Date::Format::time2str( "%Y%m%d%H%M", time() + $nextScheduled );
|
|
open( WD, "</usr/lib/affa/watchdog.template" ) or warn "Error: Couldn't open /usr/lib/affa/watchdog.template\n";
|
|
open( WDS, ">/tmp/$$.$WDName" ) or warn "Error: Couldn't open /tmp/$$.$WDName for writing\n";
|
|
dbg("Watchdog parameters:");
|
|
while (<WD>) {
|
|
#### modifs 3 lignes plus bas: dans AffaV2, c'est $job{'EmailAddresses'}
|
|
$_ =~ s/^use constant _TRIGGER=>.*$/use constant _TRIGGER=>$trigger;/;
|
|
$_ =~ s/^use constant _JOBNAME=>.*$/use constant _JOBNAME=>'$jobname';/;
|
|
$_ =~ s/^use constant _EMAIL=>.*$/use constant _EMAIL=>\'$job{'_EmailAddress'}\';/;
|
|
$_ =~ s/^use constant _BACKUPHOST=>.*$/use constant _BACKUPHOST=>'$SystemName.$DomainName ($LocalIP)';/;
|
|
$_ =~ s/^use constant _SCHEDULED=>.*$/use constant _SCHEDULED=>'$scheduled';/;
|
|
$_ =~ s/^use constant _WDSCRIPT=>.*$/use constant _WDSCRIPT=>'$WDName';/;
|
|
print WDS $_;
|
|
if ( $_ =~ /^use constant/ ) {
|
|
( my $p = $_ ) =~ s/^use constant(.*);/$1/;
|
|
chomp($p);
|
|
dbg( " " . trim($p) );
|
|
}
|
|
}
|
|
close(WDS);
|
|
close(WD);
|
|
chmod( 0700, "/tmp/$$.$WDName" );
|
|
my @cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jobname", '-o', "UserKnownHostsFile=/root/.ssh/knownhosts-$jobname", $job{'remoteHostName'}, "/bin/rm", "-f", "/etc/cron.hourly/$WDName-reminder"
|
|
);
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't delete /etc/cron.hourly/$WDName-reminder on remote host.");
|
|
##### ligne suivante: ajouté $jobname
|
|
remoteCopy( $jobname, "/tmp/$$.$WDName", "/etc/cron.hourly/$WDName" );
|
|
unlink("/tmp/$$.$WDName");
|
|
}
|
|
|
|
sub mailTestWatchdogRemote($) {
|
|
#### ligne suivante ajoutée
|
|
my $jobname = shift;
|
|
my %job = getJobConfig($jobname);
|
|
####################################################################################
|
|
checkConnection($jobname);
|
|
my $WDName = "affa-watchdog-mailtest-$jobname-$LocalIP";
|
|
open( WD, "</usr/lib/affa/watchdog-mailtest.template" )
|
|
or warn "Error: Couldn't open /usr/lib/affa/watchdog.template\n";
|
|
open( WDS, ">/usr/lib/affa/$WDName" ) or warn "Error: Couldn't open /usr/lib/affa/$WDName for writing\n";
|
|
dbg("Watchdog parameters:");
|
|
while (<WD>) {
|
|
#### modifs 2 lignes plus bas: dans AffaV2, c'est $job{'EmailAddresses'}
|
|
$_ =~ s/^use constant _JOBNAME=>.*$/use constant _JOBNAME=>'$jobname';/;
|
|
$_ =~ s/^use constant _EMAIL=>.*$/use constant _EMAIL=>\'$job{'_EmailAddress'}\';/;
|
|
$_ =~ s/^use constant _BACKUPHOST=>.*$/use constant _BACKUPHOST=>'$SystemName.$DomainName ($LocalIP)';/;
|
|
$_ =~ s/^use constant _WDSCRIPT=>.*$/use constant _WDSCRIPT=>'$WDName';/;
|
|
print WDS $_;
|
|
}
|
|
close(WDS);
|
|
close(WD);
|
|
chmod( 0700, "/usr/lib/affa/$WDName" );
|
|
|
|
#### ligne suivante: ajouté $jobname
|
|
remoteCopy( $jobname, "/usr/lib/affa/$WDName", "/tmp/" );
|
|
my @cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jobname", '-o', "UserKnownHostsFile=/root/.ssh/knownhosts-$jobname", $sshQuiet, $job{'remoteHostName'}, "/tmp/$WDName"
|
|
);
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't run /usr/lib/affa/$WDName on remote host.");
|
|
}
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
sub execJobCommandRemote($$) {
|
|
( my $jobname, my $scrname ) = @_;
|
|
|
|
####################################################################################
|
|
#### début des modifs: en 'remoteHostName'=localhost, il n'y a pas de execJobCommandRemote --> renvoi vers execJobCommand
|
|
|
|
if ( $job{'remoteHostName'} eq 'localhost' ) {
|
|
lg("Executing script $scrname on $job{'remoteHostName'}");
|
|
execJobCommand( $jobname, $scrname );
|
|
}
|
|
|
|
else {
|
|
#### fin des modifs ////// "}" rajouté à la fin de la fonction à cause du "else"
|
|
my $script = "$scriptdir/$scrname";
|
|
my $remoteScript = "/tmp/affa-$jobname-$$-$curtime";
|
|
if ( not -x $script ) {
|
|
my $txt = "Error: Script $scrname not found or not executable.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
lg("Copying script $scrname to $job{'remoteHostName'}");
|
|
my @cmd = (
|
|
$job{'_rsyncLocal'},
|
|
"--archive",
|
|
$job{'rsync--modify-window'} > 0 ? "--modify-window=$job{'rsync--modify-window'}" : '',
|
|
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
|
|
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
|
|
"--rsync-path=\"$job{'_rsyncRemote'}\"",
|
|
"--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\"",
|
|
$job{'rsyncOptions'},
|
|
$script,
|
|
( $job{'remoteUser'} ? $job{'remoteUser'} . '@' : '' ) . $job{'remoteHostName'} . ":$remoteScript",
|
|
);
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Copying $remoteScript to $job{'remoteHostName'} failed.");
|
|
lg("Executing script $scrname on $job{'remoteHostName'}");
|
|
@cmd = (
|
|
"$job{'localSSHBinary'} $job{'_sshOpts'}",
|
|
( $job{'remoteUser'} ? $job{'remoteUser'} . '@' : '' ) . $job{'remoteHostName'},
|
|
$remoteScript, $job{'remoteHostName'}, $jobname,
|
|
);
|
|
# Retrieve the remote backup directories
|
|
if ($scrname eq "SME/backupList.sh") {
|
|
# not ExecCmdBackupList( @cmd, 0 ) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
|
|
my @list = ExecCmdBackupList( @cmd, 0);
|
|
#my $result = $list [0];
|
|
my $string = $list [1];
|
|
not ($list[0]) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
|
|
return $string;
|
|
#not ExecCmdBackupList( @cmd, 0 ) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
|
|
} else {
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Executing script $scrname on $job{'remoteHostName'} failed.");
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
sub execJobCommand($$) {
|
|
( my $jobname, my $scrname ) = @_;
|
|
my $script = "$scriptdir/$scrname";
|
|
if ( not -x $script ) {
|
|
my $txt = "Error: Script $scrname not found or not executable.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
lg("Executing script $scrname");
|
|
$allow_retry = 0;
|
|
my @cmd = ();
|
|
affaErrorExit("Script '$script' not found or not executable") if not -x $script;
|
|
if ($job{'remoteHostName'} eq 'localhost') {
|
|
@cmd = ( $script );
|
|
} else {
|
|
@cmd = ( $script, $job{'remoteHostName'}, $jobname, "'$job{'localSSHBinary'} $job{'_sshOpts'}'" );
|
|
}
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Executing script $scrname failed.");
|
|
}
|
|
|
|
sub execPreJobCommand($) {
|
|
return if $Command ne "scheduled";
|
|
my $jobname = shift;
|
|
my %job = getJobConfig($jobname);
|
|
my %ps = map { $_ => 0 } @{ $job{"preJobCommand"} };
|
|
%ps = ( %ps, map { $_ => 1 } @{ $job{"preJobCommandRemote"} } );
|
|
foreach my $p ( sort keys %ps ) {
|
|
if ( $ps{$p} ) {
|
|
execJobCommandRemote( $jobname, $p );
|
|
}
|
|
else {
|
|
execJobCommand( $jobname, $p );
|
|
}
|
|
}
|
|
}
|
|
|
|
sub execPostJobCommand($) {
|
|
return if $Command ne "scheduled";
|
|
my $jobname = shift;
|
|
my %job = getJobConfig($jobname);
|
|
my %ps = map { $_ => 0 } @{ $job{"postJobCommand"} };
|
|
%ps = ( %ps, map { $_ => 1 } @{ $job{"postJobCommandRemote"} } );
|
|
foreach my $p ( sort keys %ps ) {
|
|
if ( $ps{$p} ) {
|
|
execJobCommandRemote( $jobname, $p );
|
|
}
|
|
else {
|
|
execJobCommand( $jobname, $p );
|
|
}
|
|
}
|
|
}
|
|
|
|
sub deduplicate($) {
|
|
return if $Command ne "scheduled";
|
|
my $jobname = shift;
|
|
my %job = getJobConfig($jobname);
|
|
return if $job{"dedup"} ne 'yes';
|
|
lg('Starting deduplicating');
|
|
if ( not -x $dedupBinary ) {
|
|
lg("Executable $dedupBinary not found. Skipping deduplicating");
|
|
return -1;
|
|
}
|
|
my $ar1 = getLinkdest( $jobname, 0 );
|
|
my $ar2 = getLinkdest( $jobname, 1 );
|
|
if ( $ar1 ne 'scheduled.0' || not $ar2 ) {
|
|
lg('No archives found to deduplicate');
|
|
return -1;
|
|
}
|
|
lg("Deduplicating $ar1 and $ar2 archives.");
|
|
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
|
|
my $rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
|
|
my $exectime = time();
|
|
my @cmd = (
|
|
$dedupBinary, '-upg',
|
|
"$job{'RootDir'}/$jobname/$ar1",
|
|
"$job{'RootDir'}/$jobname/$ar2",
|
|
'2>&1', '|', '/bin/egrep', '"(size of replaced files was [0-9]* bytes|[0-9]+ replaced by links)\.$"'
|
|
);
|
|
my $stat = ExecCmd( @cmd, 0 );
|
|
$exectime = time() - $exectime;
|
|
$ExecCmdOut =~ s/\n//;
|
|
$ExecCmdOut =~ /([0-9]+) files of ([0-9]+) replaced by links.*?replaced files was ([0-9]+) bytes/;
|
|
my $replacedFiles = defined $1 ? $1 : -1;
|
|
my $totalFiles = defined $2 ? $2 : -1;
|
|
my $savedBytes = defined $3 ? $3 : -1;
|
|
|
|
if ( $stat == 0
|
|
&& $replacedFiles >= 0
|
|
&& $totalFiles >= 0
|
|
&& $savedBytes >= 0
|
|
&& !$rpt->exists( 'Report', 'DedupDate' ) ) {
|
|
$rpt->newval( 'Report', 'DedupTotalFiles', $totalFiles );
|
|
$rpt->newval( 'Report', 'DedupReplacedFiles', $replacedFiles );
|
|
$rpt->newval( 'Report', 'DedupSavedBytes', $savedBytes );
|
|
$rpt->newval( 'Report', 'DedupExectime', $exectime );
|
|
$rpt->newval( 'Report', 'DedupDate', Date::Format::time2str( "%Y%m%d%H%M", time() ) );
|
|
$rpt->RewriteConfig();
|
|
lg( 'Deduplicating completed. Yield is ' . sizeUnit($savedBytes) . ' Bytes.' );
|
|
}
|
|
else {
|
|
lg('Deduplicating failed with bad status');
|
|
}
|
|
}
|
|
|
|
sub listJobs() {
|
|
$interactive = 1;
|
|
foreach my $job ( $cfg->Sections() ) {
|
|
next if $job eq 'GlobalAffaConfig';
|
|
print "$job\n";
|
|
}
|
|
}
|
|
|
|
sub jobsnrpe($) {
|
|
my $init = shift;
|
|
$interactive = 1;
|
|
my $af;
|
|
my $nf;
|
|
if ( -f "/etc/nagios/nrpe.cfg" ) {
|
|
$af = "/etc/nagios/affa-nrpe.cfg";
|
|
$nf = "/etc/nagios/nrpe.cfg";
|
|
}
|
|
elsif ( -f "/etc/icinga/nrpe.cfg" ) {
|
|
$af = "/etc/icinga/affa-nrpe.cfg";
|
|
$nf = "/etc/icinga/nrpe.cfg";
|
|
}
|
|
else {
|
|
print "# NRPE is not installed on the Affa server\n";
|
|
return -1;
|
|
}
|
|
if ( $nrpeStatus ne 'enabled' ) {
|
|
lg("NRPE service is not running.") ;
|
|
return;
|
|
}
|
|
open( FO, ">$af" );
|
|
print FO "command[check_affa]=sudo /usr/sbin/affa --nrpe\n";
|
|
print FO "command[affa_jobsnrpe]=sudo /usr/sbin/affa --_jobsnrpe\n";
|
|
print FO "command[affa_diskusagenrpe]=sudo /usr/sbin/affa --disk-usage --csv\n";
|
|
foreach my $jobname ( $cfg->Sections() ) {
|
|
next if $jobname eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jobname);
|
|
next if ( $job{'NRPEtrigger'} < 0 || $job{'status'} ne 'enabled' );
|
|
print "$jobname\n" if not $init;
|
|
print FO "command[check_affa_$jobname]=sudo /usr/sbin/affa --nrpe $jobname\n";
|
|
}
|
|
print "#OK\n" if not $init;
|
|
close(FO);
|
|
system(
|
|
"grep -v 'include=$af' $nf > $nf-$$ && echo include=$af>>$nf-$$ && mv -f $nf-$$ $nf && /etc/init.d/nrpe restart"
|
|
);
|
|
}
|
|
|
|
sub listArchives() {
|
|
$interactive = 1;
|
|
if ( not $ARGV[0] ) {
|
|
foreach my $job ( sort $cfg->Sections() ) {
|
|
next if $job eq 'GlobalAffaConfig';
|
|
push( @ARGV, $job );
|
|
}
|
|
}
|
|
|
|
my $out = '';
|
|
foreach my $jobname (@ARGV) {
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
#$timer->mark('startlistArchivesRaw');
|
|
my @csv = listArchivesRaw($jobname);
|
|
#$timer->mark('endListArchives->Raw');
|
|
|
|
#$timer->mark('startgetJobConfig');
|
|
my %job = getJobConfig($jobname);
|
|
#$timer->mark('endgetJobConfig');
|
|
|
|
if ( $opts{'csv'} ) {
|
|
$out = join( "\n", @csv ) . "\n";
|
|
}
|
|
else {
|
|
$out .= $out ? "\n" : "$affaTitle\n";
|
|
shift(@csv);
|
|
my $h;
|
|
|
|
##### début modifs
|
|
# ($h = sprintf "+-%076s-+\n", '-') =~ s/0/-/g;
|
|
( $h = sprintf "+-%0110s-+\n", '-' ) =~ s/0/-/g;
|
|
|
|
$out .= $h;
|
|
|
|
# $out .= sprintf( "| Job: %-71s |\n", $jobname );
|
|
# $out .= sprintf( "| Description: %-63s |\n", $job{'Description'} ) if $job{'Description'};
|
|
# $out .= sprintf( "| Directory: %-65s |\n", $job{'RootDir'}."/$jobname/" );
|
|
# $out .= sprintf( "| Hostname: %-66s |\n", $job{'remoteHostName'} );
|
|
|
|
$out .= sprintf( "| Job: %-105s |\n", $jobname );
|
|
$out .= sprintf( "| Description: %-97s |\n", $job{'Description'} ) if $job{'Description'};
|
|
$out .= sprintf( "| Directory: %-99s |\n", $job{'RootDir'} . "/$jobname/" );
|
|
$out .= sprintf( "| Hostname: %-100s |\n", $job{'remoteHostName'} );
|
|
|
|
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
|
|
$out .= sprintf( "| AutomountDevice: %-93s |\n", $job{'AutomountDevice'} );
|
|
$out .= sprintf( "| AutomountPoint: %-94s |\n", $job{'AutomountPoint'} );
|
|
$out .= sprintf( "| AutoUnmount: %-97s |\n", $job{'AutoUnmount'} );
|
|
}
|
|
my $etxt = "Email:";
|
|
foreach my $s ( @{ $job{'EmailAddress'} } ) {
|
|
|
|
# $out .= sprintf( "| %6s %-69s |\n", $etxt, trim($s) );
|
|
$out .= sprintf( "| %6s %-103s |\n", $etxt, trim($s) );
|
|
|
|
$etxt = '';
|
|
}
|
|
|
|
# ligne d'origine ($h = sprintf "+%05s+%022s+%08s+%08s+%07s+%07s+%07s+%07s+\n", '-','-','-','-','-','-','-','-') =~ s/0/-/g;
|
|
# ligne du patch affa2 ($h = sprintf "+-%05s-+-%021s-+-%09s-+-%040s-+-%06s-+-%06s-+\n", '-','-','-','-','-','-') =~ s/0/-/g;
|
|
# ligne modif AG
|
|
# ($h = sprintf "+%05s+%022s+%08s+%08s+%07s+%07s+%07s+%07s+%25s+\n", '-','-','-','-','-','-','-','-','-') =~ s/0/-/g;
|
|
# fin modif
|
|
my $out2 = '';
|
|
my $lastArchive = '';
|
|
my $tag = 0;
|
|
foreach my $k (@csv) {
|
|
my @c = split( ";", $k );
|
|
my $valid = $c[7] ne "yes" ? 0 : 1;
|
|
my $date = ( $valid and $c[2] ne '197001010000' ) ? formatHTime( $c[2] ) : "Incomplete!";
|
|
|
|
my $NumberOfFiles = ( $valid and $c[3] ) ? countUnit( $c[3] ) : '-';
|
|
|
|
# ligne du patch affa2 my $NumberOfFiles = ($valid and $c[3]) ? ($c[3]) : '-';
|
|
# ligne du patch affa2 $NumberOfFiles =~ s/,//g;
|
|
|
|
my $TotalFileSize = ( $valid and $c[4] ) ? sizeUnit( $c[4] ) : '-';
|
|
my $TotalBytesSent = ( $valid and $c[8] >= 0 ) ? sizeUnit( $c[8] ) : '-';
|
|
my $TotalBytesReceived = ( $valid and $c[9] >= 0 ) ? sizeUnit( $c[9] ) : '-';
|
|
my $DiskUsage =
|
|
( $valid and $c[5] and $c[6] )
|
|
? sizeUnit( $c[6] * 1024 ) . "/" . int( $c[6] / ( $c[5] + $c[6] ) * 100 ) . "%"
|
|
: '-';
|
|
my $idx = sprintf '%s%2d', uc( substr( $c[0], 0, 1 ) ), $c[1];
|
|
my $ExecTime = !$valid || $c[10] eq '-' ? '-' : timeUnit( $c[10] );
|
|
if ( $c[1] >= $job{ $c[0] . "Keep" } ) {
|
|
$idx = "*$idx";
|
|
$tag++;
|
|
}
|
|
else {
|
|
$idx = " $idx";
|
|
}
|
|
if ( $lastArchive ne $c[0] ) {
|
|
$lastArchive = $c[0];
|
|
$out2 .= $h;
|
|
}
|
|
my $DedupSavedBytes = '-';
|
|
my $DedupExectime = '-';
|
|
|
|
if ( defined $c[14] && $c[14] ne '' ) {
|
|
$DedupSavedBytes = sizeUnit( $c[13] );
|
|
$DedupExectime = timeUnit( $c[14] );
|
|
}
|
|
if ( $idx =~ /S.0/ && getProcessState($jobname) =~ /deduplicating/ ) {
|
|
$DedupSavedBytes = 'busy';
|
|
$DedupExectime = 'busy';
|
|
}
|
|
|
|
# lignes d'origine $out2 .= sprintf( "|%-4s | %-19s | %6s | %6s | %5s | %5s | %5s | %5s |\n",
|
|
# $idx,$date,$ExecTime,$DedupExectime,$DedupSavedBytes,
|
|
# $NumberOfFiles,$TotalFileSize,$TotalBytesReceived );
|
|
|
|
# ligne du patch Affa2 $out2 .= sprintf( "| %-5s | %-21s | %9s | %40s | %6s | %6s |\n", $idx,$date,$ExecTime,$NumberOfFiles,$TotalFileSize,$TotalBytesReceived );
|
|
|
|
# lignes modifiée AG
|
|
$out2 .= sprintf(
|
|
"|%-5s | %-42s | %9s | %6s | %5s | %5s | %5s | %5s | %5s |\n",
|
|
$idx, $date, $ExecTime, $DedupExectime, $DedupSavedBytes,
|
|
$NumberOfFiles, $TotalFileSize, $TotalBytesSent, $TotalBytesReceived
|
|
);
|
|
|
|
}
|
|
if ($lastArchive) {
|
|
$out .= sprintf "| %-76s |\n", "Archives with an index greater than the Keep value are tagged with '*'"
|
|
if $tag;
|
|
|
|
# ligne du patch Affa2 $out .= sprintf "| %-102s |\n", "Archives with an index greater than the Keep value are tagged with '*'" if $tag;
|
|
|
|
$out .= $h;
|
|
|
|
# ligne d'origine $out .= sprintf "| %-3s | %-20s | %6s | %6s | %5s | %5s | %5s | %5s |\n", 'Run','Completion date', 'buTime', 'ddTime', 'ddYld', 'Files', 'Size', 'Recvd';
|
|
# ligne du patch Affa2 $out .= sprintf "| %-5s | %-21s | %9s | %-40s | %6s | %6s |\n", "Run","Completion date", "Exec Time", "Files", "Size", "Recvd";
|
|
# ligne modifiée AG ci-dessous
|
|
$out .= sprintf "| %-5s | %-41s | %9s | %6s | %5s | %5s | %5s | %5s | %5s |\n", 'Run',
|
|
'Completion date', 'buTime', 'ddTime', 'ddYld', 'Files', 'Size', 'Sent', 'Recvd';
|
|
|
|
}
|
|
else {
|
|
# ($h = sprintf "+-%076s-+\n", '-') =~ s/0/-/g;
|
|
# ligne du patch Affa2 ci dessous
|
|
( $h = sprintf "+-%0110s-+\n", '-' ) =~ s/0/-/g;
|
|
}
|
|
$out2 .= $h;
|
|
$out = $out . $out2;
|
|
}
|
|
}
|
|
#$timer->mark('END');
|
|
#$timer->report();
|
|
return $out;
|
|
}
|
|
|
|
sub listArchivesRaw($) {
|
|
my $jobname = shift @_ || '';
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
|
|
#$timer->mark('startautomountlistArchivesRaw');
|
|
# automount root dir - fixing bug 9147
|
|
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
|
|
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
|
|
}
|
|
#$timer->mark('endautomountlistArchivesRaw');
|
|
affaErrorExit("RootDir $job{'RootDir'} does not exist") unless -d $job{'RootDir'};
|
|
|
|
my @out = (
|
|
'Archive:Count;Date;Files;Size;RootDirFilesystemAvail;RootDirFilesystemUsed;valid;TotalBytesReceived;ExecutionTime;DedupTotalFiles;DedupReplacedFiles;DedupSavedBytes;DedupExectime;DedupDate'
|
|
);
|
|
my %js;
|
|
my %js2;
|
|
my @jobdirs;
|
|
#$timer->mark('start job-RootDir-jobName');
|
|
foreach my $k (<$job{'RootDir'}/$jobname/*>) {
|
|
push( @jobdirs, $k ) if $k =~ /\/(scheduled|daily|weekly|monthly|yearly)\.[0-9]/;
|
|
}
|
|
foreach my $record ( sort @jobdirs ) {
|
|
( my $k = $record ) =~ s/.*\///;
|
|
$k =~ s/scheduled/A-scheduled/;
|
|
$k =~ s/daily/B-daily/;
|
|
$k =~ s/weekly/C-weekly/;
|
|
$k =~ s/monthly/D-monthly/;
|
|
$k =~ s/yearly/E-yearly/;
|
|
( my $a, my $b ) = split( /\./, $k );
|
|
$js{ $a . sprintf( "-%05d", $b ) } = "$record/.AFFA4-REPORT";
|
|
$js2{ $a . sprintf( "-%05d", $b ) } = "$record/.AFFA-REPORT";
|
|
}
|
|
#$timer->mark('end job-RootDir-jobName');
|
|
|
|
foreach my $k ( reverse sort keys %js ) {
|
|
#$timer->mark('start for');
|
|
( my $a, my $b, my $c ) = split( /-/, $k );
|
|
my $Date = 0;
|
|
my $NumberOfFiles = 0;
|
|
my $TotalFileSize = 0;
|
|
my $TotalBytesSent = 0;
|
|
my $TotalBytesReceived = 0;
|
|
my $RootDirFilesystemAvail = 0;
|
|
my $valid = "no";
|
|
my $RootDirFilesystemUsed = 0;
|
|
my $ExecutionTime = 0;
|
|
my $DedupTotalFiles = '';
|
|
my $DedupReplacedFiles = '';
|
|
my $DedupSavedBytes = '';
|
|
my $DedupExectime = '';
|
|
my $DedupDate = '';
|
|
|
|
if ( -f $js{$k} or -f $js2{$k} ) {
|
|
#$timer->mark('start if');
|
|
if ( not -f $js{$k} ) {
|
|
convertReportV2toV3( $js{$k}, $js2{$k} );
|
|
}
|
|
#$timer->mark('go Ini');
|
|
my $rpt = Config::IniFiles->new( -file => $js{$k} );
|
|
#$timer->mark('returned ini');
|
|
$Date = $rpt->val( 'Report', 'Date' );
|
|
$NumberOfFiles = $rpt->val( 'Report', 'NumberOfFiles' );
|
|
|
|
$TotalFileSize = $rpt->val( 'Report', 'TotalFileSize' );
|
|
$TotalBytesSent = $rpt->val( 'Report', 'TotalBytesSent' );
|
|
$TotalBytesReceived = $rpt->val( 'Report', 'TotalBytesReceived' );
|
|
|
|
# ligne du patch affa2 (my $TotalFileSize=$v->prop('TotalFileSize')||0) =~ s/(\D*)*/$1/g;
|
|
# ligne du patch affa2 (my $TotalBytesReceived=$v->prop('TotalBytesReceived')||0) =~ s/(\D*)*/$1/g;
|
|
|
|
$RootDirFilesystemAvail = $rpt->val( 'Report', 'RootDirFilesystemAvail' );
|
|
$RootDirFilesystemUsed = $rpt->val( 'Report', 'RootDirFilesystemUsed' );
|
|
$ExecutionTime = $rpt->val( 'Report', 'ExecutionTime' );
|
|
$valid = "yes";
|
|
#$timer->mark('start DedupExec');
|
|
if ( $rpt->exists( 'Report', 'DedupExectime' ) ) {
|
|
$DedupTotalFiles = $rpt->val( 'Report', 'DedupTotalFiles' );
|
|
$DedupReplacedFiles = $rpt->val( 'Report', 'DedupReplacedFiles' );
|
|
$DedupSavedBytes = $rpt->val( 'Report', 'DedupSavedBytes' );
|
|
$DedupExectime = $rpt->val( 'Report', 'DedupExectime' );
|
|
$DedupDate = $Date;
|
|
$DedupDate = $rpt->val( 'Report', 'DedupDate' ) if $rpt->exists( 'Report', 'DedupDate' );
|
|
}
|
|
#$timer->mark('end DedupExec');
|
|
}
|
|
|
|
push( @out,
|
|
"$b;$c" . ";"
|
|
. $Date . ";"
|
|
. $NumberOfFiles . ";"
|
|
. $TotalFileSize . ";"
|
|
. $RootDirFilesystemAvail . ";"
|
|
. $RootDirFilesystemUsed . ";"
|
|
. $valid . ";"
|
|
. $TotalBytesSent . ";"
|
|
. $TotalBytesReceived . ";"
|
|
. $ExecutionTime . ";"
|
|
. $DedupTotalFiles . ";"
|
|
. $DedupReplacedFiles . ";"
|
|
. $DedupSavedBytes . ";"
|
|
. $DedupExectime . ";"
|
|
. $DedupDate );
|
|
}
|
|
|
|
# auto-unmount - fixing bug 9147
|
|
my $dev = $job{'AutomountDevice'};
|
|
my $mountPoint = $job{'AutomountPoint'};
|
|
unmount( $dev, $mountPoint ) if $dev and $mountPoint;
|
|
|
|
#$timer->mark('returning ListArchivesRaw');
|
|
return @out;
|
|
}
|
|
|
|
sub nrpe() {
|
|
my $exitstat = 0;
|
|
my @failed;
|
|
my $jobcnt = 0;
|
|
my $jobckcnt = 0;
|
|
my $STATUS = 'OK';
|
|
my $total_exectime = 0;
|
|
my $total_size = 0;
|
|
if ( not $ARGV[0] ) {
|
|
|
|
foreach my $job ( sort $cfg->Sections() ) {
|
|
next if $job eq 'GlobalAffaConfig';
|
|
push( @ARGV, $job );
|
|
}
|
|
}
|
|
my $lastrun;
|
|
my $done = 0;
|
|
my $state;
|
|
foreach my $jobname (@ARGV) {
|
|
my %job = getJobConfig($jobname);
|
|
|
|
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
|
|
$rptfile = -f $rptfile ? $rptfile : $job{'RootDir'} . "/$jobname/daily.0/.AFFA4-REPORT";
|
|
|
|
my $rpt;
|
|
$rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
|
|
|
|
my $last = $rpt ? $rpt->val( 'Report', 'Date' ) : '19700101000000';
|
|
$lastrun = formatHTime($last);
|
|
$state = getProcessState($jobname);
|
|
my $exectime = $rpt ? $rpt->val( 'Report', 'ExecutionTime' ) : 0;
|
|
my $size = $rpt ? $rpt->val( 'Report', 'TotalFileSize' ) : 0;
|
|
|
|
$exectime = $exectime ? $exectime : 0;
|
|
next if $job{'NRPEtrigger'} <= 0;
|
|
$jobcnt++;
|
|
next if $job{'status'} ne 'enabled';
|
|
$jobckcnt++;
|
|
$total_exectime += $exectime if $exectime =~ /\d/;
|
|
$total_size += $size if $size =~ /\d/;
|
|
$last = hTime2Timestamp($last);
|
|
if ( !$state && $last + 3600 * $job{'NRPEtrigger'} < $curtime || $state =~ /interrupted/ ) {
|
|
push( @failed, $jobname );
|
|
$STATUS = 'CRITICAL';
|
|
$exitstat = 2;
|
|
}
|
|
$done++;
|
|
}
|
|
return 0 if !$done;
|
|
|
|
print "$STATUS: ";
|
|
if ( scalar @ARGV == 1 ) {
|
|
print $state? $state : $lastrun;
|
|
}
|
|
else {
|
|
if ( $STATUS eq 'CRITICAL' ) {
|
|
print "Failed jobs: " . join( ' ', @failed ) . ' ';
|
|
}
|
|
else {
|
|
print "$jobckcnt of $jobcnt jobs are enabled. ";
|
|
}
|
|
}
|
|
print '|';
|
|
my $totaltxt = '';
|
|
if ( scalar @ARGV > 1 ) {
|
|
printf( "Jobs failed=%d;;;; ", scalar @failed );
|
|
printf( "Jobs total=%d;;;; ", $jobcnt );
|
|
printf( "Jobs checked=%d;;;; ", $jobckcnt );
|
|
$totaltxt = "Total ";
|
|
}
|
|
printf( "$totaltxt" . "Execution Time=%0.1fmin;;;; ", $total_exectime / 60 );
|
|
printf( "$totaltxt" . "Size=%0.3fGByte;;;; ", $total_size / 1024 / 1024 / 1024 );
|
|
print "\n";
|
|
return $exitstat;
|
|
}
|
|
|
|
sub getStatus() {
|
|
$interactive = 1;
|
|
my @csv = getStatusRaw();
|
|
my $out = "$affaTitle\n";
|
|
if ( $opts{'csv'} ) {
|
|
$out = join( "\n", @csv ) . "\n";
|
|
}
|
|
else {
|
|
shift(@csv);
|
|
my $jobcolw = 2;
|
|
foreach my $k (@csv) {
|
|
my @c = split( ";", $k );
|
|
$jobcolw = length( $c[0] ) if $jobcolw < length( $c[0] );
|
|
}
|
|
$jobcolw = 3 if ( $jobcolw < 3 );
|
|
|
|
# $jobcolw=12 if( $jobcolw>12 );
|
|
#ligne suivante modifiée AG
|
|
$jobcolw = 38 if ( $jobcolw > 38 );
|
|
|
|
( my $h = sprintf "+%0" . ( $jobcolw + 2 ) . "s+%05s+%07s+%08s+%07s+%07s+%07s+%016s+\n",
|
|
'-', '-', '-', '-', '-', '-', '-', '-'
|
|
) =~ s/0/-/g;
|
|
$out .= $h;
|
|
$out .= sprintf "| %-" . $jobcolw . "s | %3s | %5s | %6s | %5s | %5s | %5s | %14s |\n", 'Job', 'ENA', 'Last',
|
|
'Time', 'Next', 'Size', 'ddYld', 'N of S,D,W,M,Y';
|
|
$out .= $h;
|
|
my $eo = '';
|
|
my $do = '';
|
|
my $disabled = 0;
|
|
foreach my $k ( sort @csv ) {
|
|
( my $thisjob,
|
|
my $status,
|
|
my $lastrun,
|
|
my $netxrun,
|
|
my $TotalFileSize,
|
|
my $avail,
|
|
my $used,
|
|
my $nof,
|
|
my $lock,
|
|
my $state,
|
|
my $BackupTime,
|
|
my $DedupTotalFiles,
|
|
my $DedupReplacedFiles,
|
|
my $DedupSavedBytes,
|
|
my $DedupExectime,
|
|
my $DedupDate
|
|
) = split( ";", $k );
|
|
if ( $DedupExectime ne '' ) {
|
|
$DedupSavedBytes = sizeUnit($DedupSavedBytes);
|
|
$lastrun = $DedupDate if defined $DedupDate;
|
|
}
|
|
else {
|
|
$DedupSavedBytes = '-';
|
|
$DedupExectime = 0;
|
|
}
|
|
$lastrun = $status eq "yes" ? ( $lastrun eq '19700101000000' ? 'never' : 'ERROR' ) : '-'
|
|
if $curtime - hTime2Timestamp($lastrun) > 86400;
|
|
$lastrun =~ s/\d{8}(\d\d)(\d\d)/$1:$2/;
|
|
$thisjob = length($thisjob) > $jobcolw ? substr( $thisjob, 0, $jobcolw - 2 ) . ".." : $thisjob;
|
|
$TotalFileSize = $TotalFileSize ? sizeUnit($TotalFileSize) : '-';
|
|
my $line = '';
|
|
my $TotalExecTime = '-';
|
|
if ( $BackupTime ne '' ) {
|
|
$TotalExecTime = timeUnit( $BackupTime + $DedupExectime );
|
|
}
|
|
if ( !$lock && not $state =~ 'interrupted' ) {
|
|
$line = sprintf "| %-" . $jobcolw . "s | %3s | %5s | %6s | %5s | %5s | %5s | %14s |\n",
|
|
$thisjob, $status, $lastrun, $TotalExecTime, $netxrun, $TotalFileSize, $DedupSavedBytes, $nof;
|
|
}
|
|
else {
|
|
my $ps = $lock ? "(pid $lock)" : '';
|
|
$line = sprintf "| %-" . $jobcolw . "s | %3s | %-38s | %14s |\n", $thisjob, $status, "$state $ps", $nof;
|
|
}
|
|
if ( $status eq "no" && not $state ) {
|
|
$do .= $line;
|
|
$disabled++;
|
|
}
|
|
else {
|
|
$eo .= $line;
|
|
}
|
|
}
|
|
if ( $opts{'all'} ) {
|
|
$do = $h . $do if $do && $eo;
|
|
$out .= $eo . $do . $h;
|
|
}
|
|
else {
|
|
$out .= $eo . $h;
|
|
$out .= sprintf "%d disabled jobs not listed. Use --all to display.\n", $disabled if $disabled;
|
|
}
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
sub getStatusRaw() {
|
|
my $txt;
|
|
my $lock;
|
|
my $lockpid = 0;
|
|
my @out =
|
|
"Job;Enabled;Last;Next;Size;RootDirFilesystemAvail;RootDirFilesystemUsed;NumberOfArchives;lockpid;ProcessState;BackupTime;DedupTotalFiles;DedupReplacedFiles;DedupSavedBytes;DedupExectime;DedupDate";
|
|
my @sections = $cfg->Sections();
|
|
foreach my $jobname ( reverse sort @sections ) {
|
|
next if $jobname eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jobname);
|
|
|
|
# automount root dir - fixing bug 9147
|
|
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
|
|
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
|
|
}
|
|
affaErrorExit("RootDir $job{'RootDir'} does not exist") unless -d $job{'RootDir'};
|
|
|
|
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
|
|
my $rpt;
|
|
$rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
|
|
|
|
# lockpid
|
|
my $lock = getLock($jobname);
|
|
|
|
# process state
|
|
my $state = getProcessState($jobname);
|
|
|
|
# status
|
|
my $status = $job{'status'} eq "enabled" ? "yes" : "no";
|
|
|
|
# Number of archives
|
|
my $total = 0;
|
|
my %acnt = ( 'scheduled' => 0, 'daily' => 0, 'weekly' => 0, 'monthly' => 0, 'yearly' => 0 );
|
|
my $jobpath = $job{'RootDir'} . "/$jobname";
|
|
foreach my $k (<$jobpath/*>) {
|
|
$k =~ s/.*\/(.*?)\.[0-9]$/$1/;
|
|
$acnt{$k}++;
|
|
$total++;
|
|
}
|
|
|
|
# Last and next run
|
|
my $nowTime = Date::Format::time2str( "%H%M", time() );
|
|
my @s;
|
|
my @u = @{ $job{'TimeSchedule'} };
|
|
foreach my $z ( sort @u ) {
|
|
$z = trim($z);
|
|
push( @s, $z ) if ( length($z) == 4 and $z == sprintf( "%04d", int($z) ) );
|
|
}
|
|
@u = sort { $a <=> $b } @s;
|
|
push( @u, $u[0] );
|
|
( my $netxrun = $u[0] ) =~ s/(..)(..)/$1:$2/;
|
|
for ( my $i = 0; $i < @u; $i++ ) {
|
|
if ( $nowTime < $u[$i] ) {
|
|
( $netxrun = $u[$i] ) =~ s/(\d\d)(\d\d)/$1:$2/;
|
|
last;
|
|
}
|
|
}
|
|
|
|
my $lastrun = $rpt ? $rpt->val( 'Report', 'Date' ) : '19700101000000';
|
|
|
|
# Size
|
|
( my $TotalFileSize = $rpt ? $rpt->val( 'Report', 'TotalFileSize' ) : 0 ) =~ s/.*?(\d*).*/$1/;
|
|
|
|
# ligne du patch affa2 (my $TotalFileSize=$props{'TotalFileSize'}||0) =~ s/(\D*)*/$1/g;
|
|
# ligne modifiée AG (my $TotalFileSize=$rpt ? $rpt->val('Report','TotalFileSize'):0) =~ s/(\D*)*/$1/g;
|
|
|
|
$TotalFileSize = int($TotalFileSize);
|
|
|
|
# Disk usage
|
|
( my $used = $rpt ? $rpt->val( 'Report', 'RootDirFilesystemUsed' ) : 0 ) =~ s/ .*//;
|
|
( my $avail = $rpt ? $rpt->val( 'Report', 'RootDirFilesystemAvail' ) : 0 ) =~ s/ .*//;
|
|
|
|
# Backup execution time
|
|
my $BackupTime = $rpt ? $rpt->val( 'Report', 'ExecutionTime' ) : '';
|
|
|
|
my $ExecutionTime = 0;
|
|
my $DedupTotalFiles = '';
|
|
my $DedupReplacedFiles = '';
|
|
my $DedupSavedBytes = '';
|
|
my $DedupExectime = '';
|
|
my $DedupDate = '';
|
|
if ( $rpt && $rpt->exists( 'Report', 'DedupExectime' ) ) {
|
|
$DedupTotalFiles = $rpt->val( 'Report', 'DedupTotalFiles' );
|
|
$DedupReplacedFiles = $rpt->val( 'Report', 'DedupReplacedFiles' );
|
|
$DedupSavedBytes = $rpt->val( 'Report', 'DedupSavedBytes' );
|
|
$DedupExectime = $rpt->val( 'Report', 'DedupExectime' );
|
|
$DedupDate = $lastrun;
|
|
$DedupDate = $rpt->val( 'Report', 'DedupDate' ) if $rpt->exists( 'Report', 'DedupDate' );
|
|
}
|
|
|
|
my $nof = sprintf "%2d,%2d,%2d,%2d,%2d", $acnt{'scheduled'}, $acnt{'daily'}, $acnt{'weekly'}, $acnt{'monthly'},
|
|
$acnt{'yearly'};
|
|
push( @out,
|
|
"$jobname;$status;$lastrun;$netxrun;$TotalFileSize;$avail;$used;$nof;$lock;$state;$BackupTime;$DedupTotalFiles;$DedupReplacedFiles;$DedupSavedBytes;$DedupExectime;$DedupDate"
|
|
);
|
|
}
|
|
|
|
# auto-unmount - fixing bug 9147
|
|
my $dev = $job{'AutomountDevice'};
|
|
my $mountPoint = $job{'AutomountPoint'};
|
|
unmount( $dev, $mountPoint ) if $dev and $mountPoint;
|
|
|
|
return @out;
|
|
}
|
|
|
|
sub convertReportV2toV3($$) {
|
|
( my $fn, my $f ) = @_;
|
|
open( DF, ">$fn" );
|
|
print DF "; converted from .AFFA-REPORT (Affa V2)\n";
|
|
lg("converting report file $f to $fn");
|
|
print DF "[Report]\n";
|
|
open( AS, "<$f" );
|
|
while (<AS>) {
|
|
next if /^[ \t]*#/ or not /: /;
|
|
chomp($_);
|
|
$_ =~ /(.*?):(.*)/;
|
|
my $prop = trim($1);
|
|
next if $prop =~ /^File/;
|
|
my $val = trim($2);
|
|
next if not $val =~ /^[\-0-9]/ and not $prop =~ /^Date/;
|
|
$val =~ /([\-0-9.]+)(.*)/;
|
|
$val = $1;
|
|
my @ps = split( " ", $prop );
|
|
$prop = '';
|
|
|
|
foreach my $k (@ps) {
|
|
$prop .= ucfirst($k);
|
|
}
|
|
print DF "$prop=$val\n";
|
|
}
|
|
close(AS);
|
|
close(DF);
|
|
( my $d = $f ) =~ s/\.AFFA-REPORT//;
|
|
system("touch -r $f $d");
|
|
}
|
|
|
|
sub df($) {
|
|
my $dir = shift;
|
|
my $df = new Filesys::DiskFree;
|
|
$df->df();
|
|
return ( int( $df->used($dir) / 1024 ), int( $df->avail($dir) / 1024 ) );
|
|
}
|
|
|
|
sub DiskUsage() {
|
|
my @csv = DiskUsageRaw();
|
|
my $out = "$affaTitle\n";
|
|
if ( $opts{'csv'} ) {
|
|
$out = join( "\n", @csv ) . "\n";
|
|
}
|
|
else {
|
|
( my $h = sprintf "+-%04s-+-%06s-+-%06s-+-%050s-+\n", '-', '-', '-', '-' ) =~ s/0/-/g;
|
|
$out .= $h;
|
|
$out .= sprintf "| %4s | %6s | %6s | %-50s |\n", "Use%", "Used", "Avail", "Root Dir";
|
|
$out .= $h;
|
|
shift(@csv);
|
|
foreach my $k (@csv) {
|
|
my @c = split( ";", $k );
|
|
$c[0] =~ s/"//g;
|
|
$c[0] =~ s/.*(.{47})$/...$1/ if length( $c[0] ) > 50;
|
|
if ( $c[1] eq '-' or $c[2] eq '-' ) {
|
|
$out .= sprintf "| %4s | %6s | %6s | %-50s |\n", '-', '-', '-', $c[0];
|
|
}
|
|
else {
|
|
$out .= sprintf "| %4s | %6s | %6s | %-50s |\n", int( $c[2] / ( $c[1] + $c[2] ) * 100 ) . "%", # Use%
|
|
sizeUnit( $c[2] * 1024 ), # Used
|
|
sizeUnit( $c[1] * 1024 ), # Avail
|
|
$c[0];
|
|
}
|
|
}
|
|
$out .= $h;
|
|
}
|
|
return $out;
|
|
}
|
|
|
|
sub DiskUsageRaw() {
|
|
my @out = ('RootDir;RootDirFilesystemAvail;RootDirFilesystemUsed');
|
|
my %done;
|
|
foreach my $jobname ( reverse sort $cfg->Sections() ) {
|
|
next if $jobname eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jobname);
|
|
my $RootDir = $job{'RootDir'};
|
|
next if $done{$RootDir};
|
|
$done{$RootDir} = 1;
|
|
my $used = '-';
|
|
my $avail = '-';
|
|
my $dev = $job{'AutomountDevice'};
|
|
my $mountPoint = $job{'AutomountPoint'};
|
|
my $mountOptions = $job{'AutomountOptions'};
|
|
|
|
if ( $RootDir && $mountPoint && $RootDir =~ /$mountPoint/ ) {
|
|
###### ligne suivante modifiée umount diskusage - bug 9147
|
|
# mount($dev,$mountPoint, $mountOptions) if $dev and $mountPoint;
|
|
mount( $dev, $mountPoint, $mountOptions, %job ) if $dev and $mountPoint;
|
|
( $used, $avail ) = df($RootDir) if isMounted( $dev, $mountPoint );
|
|
unmount( $dev, $mountPoint ) if $dev and $mountPoint;
|
|
}
|
|
elsif ( $RootDir && -x $RootDir ) {
|
|
( $used, $avail ) = df($RootDir);
|
|
}
|
|
push( @out, "\"$RootDir\";$avail;$used" );
|
|
}
|
|
return @out;
|
|
}
|
|
|
|
# ajout pour V3.2.2-2
|
|
sub checkForDiskLabel($) {
|
|
my $dev = $_[0];
|
|
my $result = $dev;
|
|
if ( $dev =~ "/by-label/" ) {
|
|
$result = "/dev/" . basename( readlink($dev) );
|
|
lg("Mapping disk label to device: $dev links to $result\n");
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
# fin ajout
|
|
|
|
sub isMounted($$) {
|
|
( my $dev, my $AutomountPoint ) = @_;
|
|
$AutomountPoint =~ s/\/$//;
|
|
|
|
# ajout pour V3.2.2-2
|
|
$dev = checkForDiskLabel($dev);
|
|
|
|
# fin ajout
|
|
$dev =~ s/\/$//;
|
|
my $txt = "Check mounted: $dev $AutomountPoint";
|
|
my $df = new Filesys::DiskFree;
|
|
my $result = 0;
|
|
$df->df();
|
|
( my $df_AutomountPoint = $df->device($AutomountPoint) ) =~ s/\/$//;
|
|
if ( $df_AutomountPoint =~ "^/dev/mapper/" && not( $dev =~ "^/dev/mapper" ) ) {
|
|
|
|
# convert /dev/mapper/VG-LV to /dev/VG/LV
|
|
( my $d = $df_AutomountPoint ) =~ s;^/dev/mapper/(.*)-(.*)$;/dev/$1/$2;;
|
|
$result = 1 if ( $d eq $dev );
|
|
}
|
|
$result |= $df_AutomountPoint eq $dev;
|
|
dbg( "$txt. Result: " . ( $result ? 'yes' : 'no ' ) );
|
|
return $result;
|
|
}
|
|
|
|
###### 2 lignes suivantes modifiées umount diskusage - bug 9147
|
|
# sub mount($$$) {
|
|
# (my $dev, my $AutomountPoint, my $options) = @_;
|
|
sub mount($$$%) {
|
|
( my $dev, my $AutomountPoint, my $options, my %job ) = @_;
|
|
$AutomountPoint =~ s/\/$//;
|
|
return if isMounted( $dev, $AutomountPoint );
|
|
File::Path::mkpath( $AutomountPoint, 0, 0700 ) if not -d $AutomountPoint;
|
|
lg("Mounting $dev to $AutomountPoint");
|
|
my @cmd = ( '/bin/mount', $options, $dev, $AutomountPoint );
|
|
if ( ExecCmd( @cmd, 0 ) ) {
|
|
my $s = "Couldn't mount $dev $AutomountPoint";
|
|
if ($Command) {
|
|
affaErrorExit($s);
|
|
}
|
|
else {
|
|
lg($s);
|
|
}
|
|
}
|
|
$autoMounted{$AutomountPoint} = $dev if $job{'AutoUnmount'} eq 'yes';
|
|
|
|
}
|
|
|
|
sub unmount($$) {
|
|
( my $dev, my $AutomountPoint ) = @_;
|
|
$AutomountPoint =~ s/\/$//;
|
|
return
|
|
if not $autoMounted{$AutomountPoint}
|
|
or $autoMounted{$AutomountPoint} ne $dev
|
|
or not isMounted( $dev, $AutomountPoint );
|
|
my @cmd = ( '/bin/umount', '-l', $AutomountPoint );
|
|
lg("Unmounting $AutomountPoint");
|
|
not ExecCmd( @cmd, 0 ) or lg("Couldn't unmount $AutomountPoint");
|
|
}
|
|
|
|
sub unmountAll() {
|
|
while ( ( my $AutomountPoint, my $dev ) = each(%autoMounted) ) {
|
|
unmount( $dev, $AutomountPoint );
|
|
}
|
|
}
|
|
|
|
sub checkCrossFS($$) {
|
|
( my $fs1, my $fs2 ) = @_;
|
|
my $fn = ".affa.checkCrossFS.$$";
|
|
unlink("$fs1/$fn") if -e "$fs1/$fn";
|
|
unlink("$fs2/$fn") if -e "$fs2/$fn";
|
|
open( OUT, ">$fs1/$fn" );
|
|
print OUT "test";
|
|
close(OUT);
|
|
link( "$fs1/$fn", "$fs2/$fn" );
|
|
my $res = -e "$fs2/$fn" ? 0 : 1;
|
|
unlink("$fs1/$fn") if -e "$fs1/$fn";
|
|
unlink("$fs2/$fn") if -e "$fs2/$fn";
|
|
return $res;
|
|
}
|
|
|
|
sub DiskSpaceWarn() {
|
|
return if $Command ne 'scheduled' or $job{'DiskSpaceWarn'} eq 'none';
|
|
lg("Checking disk space.");
|
|
( my $used, my $avail ) = df( $job{'RootDir'} );
|
|
my $report = Config::IniFiles->new( -file => "$job{'RootDir'}/$jobname/scheduled.0/.AFFA4-REPORT" );
|
|
my $needed = int( $report->val( 'Report', 'TotalFileSize' ) / 1024 );
|
|
$needed = int( $needed * 0.5 ) if $job{'DiskSpaceWarn'} eq 'normal';
|
|
$needed = int( $needed * 0.1 ) if $job{'DiskSpaceWarn'} eq 'risky';
|
|
if ( $avail < $needed ) {
|
|
my $msg = new Mail::Send;
|
|
$msg->subject("Warning: Affa server $SystemName running out of disk space!");
|
|
$msg->to( $job{'_EmailAddress'} );
|
|
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
|
|
my $s;
|
|
my $fh = $msg->open;
|
|
print $fh "This message was sent by job '$jobname'.\n";
|
|
$s = "Configured threshold type: $job{'DiskSpaceWarn'}\n";
|
|
print $fh $s;
|
|
lg($s);
|
|
$s = "Disk space left: " . sizeUnit( int( $avail * 1024 ) ) . "\n";
|
|
print $fh $s;
|
|
lg($s);
|
|
$s = "Used disk space: " . sizeUnit( int( $used * 1024 ) ) . "\n";
|
|
print $fh $s;
|
|
lg($s);
|
|
$s = "Disk size: " . sizeUnit( int( ( $avail + $used ) * 1024 ) ) . "\n";
|
|
print $fh $s;
|
|
lg($s);
|
|
close($fh);
|
|
lg( "Running out of disk space message sent to " . $job{'_EmailAddress'} );
|
|
}
|
|
}
|
|
|
|
sub getReportVal($$) {
|
|
( my $jobname, my $archive ) = @_;
|
|
my $rptfile = $job{'RootDir'} . "/$jobname/$archive/.AFFA4-REPORT";
|
|
my $rpt = Config::IniFiles->new( -file => $rptfile ) if ( -f $rptfile );
|
|
my $val = '';
|
|
if ( $rpt && $rpt->exists( 'Report', 'Date' ) ) {
|
|
$val = $rpt->val( 'Report', 'Date' );
|
|
}
|
|
return $val;
|
|
}
|
|
|
|
sub checkArchiveExists($$) {
|
|
( my $jobname, my $archive ) = @_;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
if ( not -f "$job{'RootDir'}/$jobname/$archive/.AFFA4-REPORT" ) {
|
|
$interactive = 0;
|
|
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
|
|
my $ar;
|
|
my %va;
|
|
while ( defined( $ar = readdir(DIR) ) ) {
|
|
next if not $ar =~ /^(scheduled|daily|weekly|monthly|yearly)\.[0-9]+$/;
|
|
my $d = getReportVal( $jobname, $ar );
|
|
if ($d) {
|
|
$va{$ar} = $d;
|
|
}
|
|
}
|
|
my $txt = "Error: Archive $archive not found.";
|
|
print "$txt\n";
|
|
lg($txt);
|
|
$txt = "The following Affa v4 archives exist:";
|
|
print "$txt\n";
|
|
lg($txt);
|
|
foreach my $k ( sort { $va{$a} cmp $va{$b} } keys %va ) {
|
|
$txt = sprintf " %-12s: %s", $k, formatHTime( $va{$k} );
|
|
print "$txt\n";
|
|
lg($txt);
|
|
}
|
|
print "Note there may be Affa 3 archives available but Affa v4 cannot restore them\n";
|
|
affaErrorExit(".");
|
|
}
|
|
}
|
|
|
|
# remove archives which have indices greater than the Keep value
|
|
sub cleanup() {
|
|
my $jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $txt;
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
$txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
|
|
my %archives;
|
|
my $cnt = 0;
|
|
if ($dir) {
|
|
my $af;
|
|
while ( defined( $af = readdir(DIR) ) ) {
|
|
next if not $af =~ /^(scheduled|daily|weekly|monthly|yearly)\.[0-9]+$/;
|
|
( my $k, my $b ) = split( /\./, $af );
|
|
next if ( $b < $job{ $k . "Keep" } );
|
|
$k =~ s/scheduled/A/;
|
|
$k =~ s/daily/B/;
|
|
$k =~ s/weekly/C/;
|
|
$k =~ s/monthly/D/;
|
|
$k =~ s/yearly/E/;
|
|
$archives{ "$k" . sprintf( "%05d", $b ) } = $af;
|
|
$cnt++;
|
|
}
|
|
}
|
|
if ($cnt) {
|
|
print "\nWARNING: The following $cnt archives will be deleted!\n";
|
|
foreach my $k ( reverse sort keys %archives ) {
|
|
print "$archives{$k}\n";
|
|
}
|
|
my $input = '';
|
|
print "Type 'proceed' to confirm or <ENTER> to cancel: ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
if ( $input ne 'proceed' ) {
|
|
affaErrorExit("Terminated by user") if $input ne 'proceed';
|
|
}
|
|
File::Path::mkpath( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 0, 0700 )
|
|
unless -d "$job{'RootDir'}/$jobname/.AFFA-TRASH";
|
|
foreach my $k ( reverse sort keys %archives ) {
|
|
print "deleting archive $archives{$k} in background... ";
|
|
$|++;
|
|
moveFileorDir( "$job{'RootDir'}/$jobname/$archives{$k}",
|
|
"$job{'RootDir'}/$jobname/.AFFA-TRASH/$archives{$k}.$curtime-$$" );
|
|
print " Done.\n";
|
|
}
|
|
removeDir( "$job{'RootDir'}/$jobname/.AFFA-TRASH", 1 );
|
|
}
|
|
}
|
|
|
|
sub moveArchive() {
|
|
$jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $txt;
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
$txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
( my $newdir = $ARGV[1] ) =~ s/\/$//;
|
|
if ( not $newdir ) {
|
|
$txt = "Error: New RootDir not given.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
if ( not $newdir =~ /^\// or $newdir =~ /\.\./ ) {
|
|
$txt = "Error: Full path required for NEWROOTDIR.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
if ( not -d $newdir ) {
|
|
my $input = '';
|
|
while ( not $input =~ /^(yes|no)$/ ) {
|
|
print "Directory $newdir does not exist. Create? (yes|no) ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
}
|
|
if ( $input ne 'yes' ) {
|
|
$interactive = 0;
|
|
affaErrorExit("Terminated by user") if $input ne 'proceed';
|
|
}
|
|
File::Path::mkpath( $newdir, 0, 0700 );
|
|
}
|
|
my $err = 0;
|
|
if ( "$job{'RootDir'}/$jobname" ne "$newdir/$jobname" and -d "$job{'RootDir'}/$jobname" ) {
|
|
if ( checkCrossFS( $job{'RootDir'} . "/$jobname", $newdir ) ) {
|
|
$txt = "Warning: Cannot move across filesystems. Using copy and delete.";
|
|
print("$txt\n");
|
|
lg($txt);
|
|
$txt = "Please wait...";
|
|
print("$txt\n");
|
|
lg($txt);
|
|
my @cmd = (
|
|
"/bin/tar", "--remove-files", "-C", $job{'RootDir'}, "-cf", '-',
|
|
$jobname, "|", "/bin/tar", "-C", $newdir, "-xf",
|
|
'-'
|
|
);
|
|
$err = ExecCmd( @cmd, 0 );
|
|
|
|
if ($err) {
|
|
$txt = "Error: Copying failed.";
|
|
print("$txt\n");
|
|
lg($txt);
|
|
}
|
|
else {
|
|
removeDir( $job{'RootDir'} . "/$jobname", 0 );
|
|
}
|
|
}
|
|
else {
|
|
moveFileorDir( $job{'RootDir'} . "/$jobname", "$newdir/$jobname" );
|
|
}
|
|
}
|
|
if ( not $err ) {
|
|
my $err = rewriteConfigVal( $jobname, 'RootDir', $newdir );
|
|
lg("Changed 'RootDir' value to '$newdir' in config file of job '$jobname'") if ( not $err );
|
|
}
|
|
}
|
|
|
|
sub renameJob() {
|
|
$jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $txt;
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
$txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
my $newname = $ARGV[1] || '';
|
|
$newname =~ s/\//_/g;
|
|
if ( not $newname ) {
|
|
$txt = "Error: No new jobname given.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
if ( $cfg->SectionExists($newname) ) {
|
|
$txt = "Error: A job '$newname' already exists.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
if ( -e "$job{'RootDir'}/$newname" ) {
|
|
$txt = "Error: $job{'RootDir'}/$newname already exists.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
if ( -d "$job{'RootDir'}/$jobname" ) {
|
|
moveFileorDir( $job{'RootDir'} . "/$jobname", $job{'RootDir'} . "/$newname" );
|
|
}
|
|
|
|
# rename ssh HostKeyAlias
|
|
if ( -f "/root/.ssh/knownhosts-$jobname" ) {
|
|
open( LF, "</root/.ssh/knownhosts-$jobname" );
|
|
$_ = <LF>;
|
|
s/^$jobname /$newname /;
|
|
close(LF);
|
|
open( LF, ">/root/.ssh/knownhosts-$newname" );
|
|
print LF;
|
|
close(LF);
|
|
unlink("/root/.ssh/knownhosts-$jobname");
|
|
}
|
|
|
|
# rename Section in config file
|
|
my $cfg = openOrgConfig($jobname);
|
|
$cfg->AddSection($newname);
|
|
my @p = $cfg->Parameters($jobname);
|
|
foreach my $k (@p) {
|
|
$cfg->newval( $newname, $k, $cfg->val( $jobname, $k ) );
|
|
}
|
|
$cfg->DeleteSection($jobname);
|
|
writeConfigFile($cfg);
|
|
|
|
# rename logfile
|
|
moveFileorDir( "$logdir/$jobname.log", "$logdir/$newname.log" );
|
|
|
|
lg("Renamed job '$jobname' to '$newname'");
|
|
$jobname = $newname;
|
|
cronSetup();
|
|
}
|
|
|
|
sub writeConfigFile($) {
|
|
my $cfg = shift;
|
|
$cfg->RewriteConfig();
|
|
|
|
# replace 'here documents' by multiline values
|
|
my $file = $cfg->GetFileName();
|
|
my $key = '';
|
|
open( HD, "<$file" );
|
|
open( MP, ">$file.$$.$curtime" );
|
|
while (<HD>) {
|
|
my $e = trim($_);
|
|
$e =~ s/(.*?)(= <<)(EOT)$//;
|
|
if ( not $key and not $e ) {
|
|
$key = $1;
|
|
next;
|
|
}
|
|
if ( $key and $e ne 'EOT' ) {
|
|
print MP "$key=$e\n";
|
|
next;
|
|
}
|
|
if ( $e eq 'EOT' ) {
|
|
$key = '';
|
|
next;
|
|
}
|
|
print MP "$e\n";
|
|
}
|
|
close(MP);
|
|
close(HD);
|
|
rename( "$file.$$.$curtime", $file );
|
|
|
|
# force config re-read
|
|
unlink($configfile) if $configfile;
|
|
getJobConfig('');
|
|
}
|
|
|
|
# entirely remove a job
|
|
sub deleteJob() {
|
|
my $jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $txt;
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
$txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
my $dir = opendir( DIR, "$job{'RootDir'}/$jobname" );
|
|
my %archives;
|
|
if ($dir) {
|
|
my $af;
|
|
while ( defined( $af = readdir(DIR) ) ) {
|
|
next if not $af =~ /^(scheduled|hourly|daily|weekly|monthly|yearly])\.[0-9]+$/;
|
|
( my $k, my $b ) = split( /\./, $af );
|
|
$k =~ s/scheduled/A/;
|
|
$k =~ s/daily/B/;
|
|
$k =~ s/weekly/C/;
|
|
$k =~ s/monthly/D/;
|
|
$k =~ s/yearly/E/;
|
|
$archives{ "$k" . sprintf( "%05d", $b ) } = $af;
|
|
}
|
|
}
|
|
print "Job '$jobname' has the following archives:\n";
|
|
foreach my $k ( sort keys %archives ) {
|
|
print " $archives{$k}\n";
|
|
}
|
|
print "WARNING: All archives and logfiles of the job '$jobname' will be deleted!\n";
|
|
my $input = '';
|
|
print "Type 'proceed' to confirm or <ENTER> to cancel: ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
if ( $input ne 'proceed' ) {
|
|
affaErrorExit("Terminated by user") if $input ne 'proceed';
|
|
}
|
|
rewriteConfigVal( $jobname, 'status', 'disabled' );
|
|
print("Set job '$jobname' status disabled.\n");
|
|
|
|
####################################################################################
|
|
#### début modifs
|
|
if ( $job{'remoteHostName'} ne 'localhost' ) {
|
|
#### fin modifs (penser à l'accolade fermante après le revokeKeys
|
|
####################################################################################
|
|
revokeKeys($jobname);
|
|
}
|
|
|
|
if ($dir) {
|
|
print "deleting $job{'RootDir'}/$jobname in background...";
|
|
$|++;
|
|
removeDir( "$job{'RootDir'}/$jobname", 1 );
|
|
print " Done.\n";
|
|
}
|
|
cronSetup();
|
|
print "deleting logfile '/var/log/affa/$jobname.log' ... ";
|
|
$|++;
|
|
unlink("/var/log/affa/$jobname.log");
|
|
print " Done.\n";
|
|
}
|
|
|
|
|
|
sub fullRestore() {
|
|
$interactive = 0;
|
|
$SIG{'INT'} = sub { };
|
|
$archive = $restoreVer;
|
|
my $txt = '';
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
#affaExit("Ending Restore");
|
|
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
|
|
##### ligne suivante modifiée umount diskusage - bug 9147
|
|
# mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'} );
|
|
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
|
|
}
|
|
|
|
# check if a job is running
|
|
if ( getLock($jobname) ) {
|
|
print "Job '$jobname' is running. Wait for completion. Then run affa --full-restore again.\n";
|
|
affaErrorExit("affa job 'jobname' is running.");
|
|
}
|
|
|
|
# check if archive exists
|
|
checkArchiveExists( $jobname, $archive ); # and affaErrorExit() if archive does not exist
|
|
$interactive = 1;
|
|
checkConnection($jobname);
|
|
|
|
####################################################################################
|
|
#### debut des modifs
|
|
print "WARNING: After the restore is done, the server $job{'remoteHostName'} will reboot! \n"
|
|
if $job{'SMEServer'} ne 'no';
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
my $input = '';
|
|
print "Type 'proceed' to confirm or <ENTER> to cancel: ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
#Modified the Exit line. Moved Rise subs nr
|
|
if ( $input ne 'proceed' ) {
|
|
$interactive = 0;
|
|
affaErrorExit("Terminated by user") if $input ne 'proceed';
|
|
}
|
|
|
|
####################################################################################
|
|
#### debut des modifs
|
|
|
|
if ( $job{'SMEServer'} ne 'no' ) {
|
|
|
|
#pour debbugage print 'SME-server= ' . $job{'SMEServer'} . "\n";
|
|
my $prerestore = 'SME/signal-pre-restore';
|
|
if ( $job{'remoteHostName'} eq 'localhost' ) {
|
|
execJobCommand( $jobname, $prerestore );
|
|
}
|
|
else {
|
|
execJobCommandRemote( $jobname, $prerestore );
|
|
}
|
|
#pour debbugage print 'pre-restore: $prerestore' . "\n";
|
|
}
|
|
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
# Archive version is set via at the top in full-restore or via CLI
|
|
|
|
# Old
|
|
#my @SourceDirs = getSourceDirsString($jobname);
|
|
|
|
my ($local, $remote) = getSourceDirs($jobname);
|
|
# $remote is the destination - it could be local
|
|
|
|
foreach my $k (@$remote) {
|
|
|
|
my @split = split(':', $k);
|
|
my $source = $split[1];
|
|
my $target = "\"$k\"";
|
|
|
|
my $src = "$job{'RootDir'}/$jobname/$archive$source";
|
|
next if not -e $src;
|
|
$txt = "Restoring Job $job{'remoteHostName'} Target $k ...";
|
|
lg($txt);
|
|
print "$txt\n";
|
|
@cmd = (
|
|
"/usr/bin/rsync",
|
|
"--archive",
|
|
"--stats",
|
|
"--ignore-errors",
|
|
$opts{'preserve-newer'} || 'yes' eq 'no' ? "" : "--update",
|
|
$opts{'delete'} || 'no' eq 'yes' ? "--delete-during" : "",
|
|
"--partial",
|
|
$job{'rsync--inplace'} ne 'no' ? "--inplace" : "",
|
|
"--numeric-ids",
|
|
$job{'remoteHostName'} ne 'localhost' ? "--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\"" : '',
|
|
( -d $src ? "$src/" : "$src" ),
|
|
# reetp fix here
|
|
$job{'remoteHostName'} eq 'localhost' ? "/$k" : $target
|
|
);
|
|
|
|
####################################################################################
|
|
#### debut des modifs faites dans la dernière ligne de la commande @cmd précédente
|
|
#
|
|
# ajout de $job{'remoteHostName'} eq 'localhost' ? ":/$k" :
|
|
#
|
|
#### fin des modifs
|
|
####################################################################################
|
|
ExecCmd( @cmd, 0 );
|
|
}
|
|
|
|
####################################################################################
|
|
#### debut des modifs
|
|
|
|
if ( $job{'SMEServer'} ne 'no' ) {
|
|
|
|
####################################################################################
|
|
#### penser à rajouter le imapIndexFilesDelete(); cf AffaV2
|
|
|
|
#pour debbugage print 'SME-server= ' . $job{'SMEServer'} . "\n";
|
|
my $postupgrade = 'SME/signal-post-upgrade-reboot';
|
|
if ( $job{'remoteHostName'} eq 'localhost' ) {
|
|
execJobCommand( $jobname, $postupgrade );
|
|
}
|
|
else {
|
|
execJobCommandRemote( $jobname, $postupgrade );
|
|
}
|
|
#pour debbugage print 'post-upgrade + reboot: $postupgrade' . "\n";
|
|
}
|
|
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
}
|
|
|
|
####################################################################################
|
|
#### début des modifs: ajout à partir de Affa2
|
|
|
|
sub riseFromDeath() {
|
|
$SIG{'INT'} = sub { };
|
|
$interactive = 1;
|
|
my @cmd;
|
|
my $bootstrap = $configDB->get_prop( "bootstrap-console", "Run" );
|
|
#my $jobname = $ARGV[0] || 'none'; # This will cause an exit below
|
|
my $archive = $restoreVer;
|
|
|
|
if ( $cliDebug == 1 ) {
|
|
print "Argv0: $ARGV[0] Argv1: $ARGV[1] Archive: $archive\n";
|
|
}
|
|
|
|
# check if archive exists
|
|
##### ligne d'origine: checkArchiveExists($job{'RootDir'},$jobname,$archive); remplacée par:
|
|
if ($jobname eq 'none') {
|
|
affaErrorExit("Please specify the job name to rise");
|
|
}
|
|
else {
|
|
####################################################################################
|
|
#### ligne d'origine d'Affa2: getJobConfig( $jobname );
|
|
%job = getJobConfig($jobname);
|
|
####################################################################################
|
|
}
|
|
|
|
if ( $job{'remoteHostName'} eq 'localhost' ) {
|
|
$interactive = 0;
|
|
my $txt;
|
|
$txt = "Error: A rise cannot be done for this server from it's own backup.";
|
|
lg($txt);
|
|
print "$txt\n";
|
|
$txt = "Try option --full-restore instead.";
|
|
lg($txt);
|
|
print "$txt\n";
|
|
affaErrorExit("Cannot rise from my own backup.");
|
|
}
|
|
|
|
$archive = $archive ne '' ? $archive : $job{'Archive'};
|
|
|
|
# This will exit in the subroutine if the archive does not exist
|
|
checkArchiveExists( $jobname, $archive );
|
|
|
|
if ( $job{'AutomountDevice'} and $job{'AutomountPoint'} ) {
|
|
###### ligne suivante modifiée umount diskusage - bug 9147
|
|
# mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'} );
|
|
mount( $job{'AutomountDevice'}, $job{'AutomountPoint'}, $job{'AutomountOptions'}, %job );
|
|
}
|
|
|
|
# check if other affa jobs are running
|
|
##### ligne d'origine supprimée: my %all = $affa->as_hash();
|
|
my $havelocks = 0;
|
|
#### début des modifs // commentés avec un #: script d'origine de AffaV2
|
|
# foreach my $job( reverse sort keys %all )
|
|
# {
|
|
# next if $job eq $ServerBasename;
|
|
# my $v=$all{$job};
|
|
# next if $v->{'type'} ne 'job';
|
|
# my $lockpid=getLock("$lockdir/$job");
|
|
|
|
foreach my $jobname ( sort $cfg->Sections() ) {
|
|
next if $jobname eq $ServerBasename;
|
|
|
|
#### fin des modifs
|
|
|
|
my $lockpid = getLock($jobname);
|
|
if ($lockpid) {
|
|
# my $txt = "Job '$job' is running (pid $lockpid)";
|
|
my $txt = "Job '$jobname' is running (pid $lockpid)";
|
|
print "$txt\n";
|
|
lg($txt);
|
|
$havelocks++;
|
|
$interactive = 0;
|
|
}
|
|
}
|
|
if ( $interactive == 0 ) {
|
|
print "Wait for completion of the running jobs or kill them. Then run affa --rise again.\n";
|
|
affaErrorExit("affa jobs are running.");
|
|
}
|
|
|
|
# reetp test stop
|
|
if ( $cliDebug != 1 ) {
|
|
stopServices();
|
|
}
|
|
|
|
if ( $bootstrap ne 'no' ) {
|
|
print "*************************************************************\n";
|
|
print "* WARNING: *\n";
|
|
print "* Bootstrap-console Run flag is set. *\n";
|
|
print "* It appears as if affa --rise has already run. *\n";
|
|
print "* Skipping backup run of affa server base (this server) *\n";
|
|
print "*************************************************************\n";
|
|
}
|
|
else {
|
|
# reetp test backup
|
|
if ( $cliDebug != 1 ) {
|
|
saveMySoul();
|
|
}
|
|
}
|
|
|
|
####################################################################################
|
|
#### début modifs
|
|
# reetp why get it again?
|
|
%job = getJobConfig($jobname);
|
|
|
|
##### fin modifs
|
|
####################################################################################
|
|
|
|
if ( checkCrossFS( $job{'RootDir'}, "/usr/lib/affa" ) ) {
|
|
print "*************************************************************\n";
|
|
print "* WARNING: *\n";
|
|
print "* The archive is not on the local filesystem, therefore *\n";
|
|
print "* hardlinks cannot be used and all data need to be copied. *\n";
|
|
print "* Depending on the size of the archive this can take a long *\n";
|
|
print "* time. *\n";
|
|
print "*************************************************************\n";
|
|
}
|
|
my $input = '';
|
|
|
|
if ( $cliDebug != 1 ) {
|
|
while ( not $input =~ /^(proceed|quit|exit)$/ ) {
|
|
print "Type 'proceed' to continue or 'quit' to exit: ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
}
|
|
if ( $input ne 'proceed' ) {
|
|
$interactive = 0;
|
|
affaErrorExit("Terminated by user") if $input ne 'proceed';
|
|
}
|
|
}
|
|
print "Signalling pre-restore event\n";
|
|
@cmd = ( "/sbin/e-smith/signal-event", "pre-restore" );
|
|
|
|
# reet test exec
|
|
if ( $cliDebug != 1 ) {
|
|
ExecCmd( @cmd, 0 );
|
|
}
|
|
|
|
$configDB = esmith::ConfigDB->close;
|
|
|
|
##### ligne d'origine AffaV2: runRiseRsync("$job{'RootDir'}/$jobname/$archive/", "/");
|
|
|
|
#my $archiveDir = "$job{'RootDir'}/$jobname/$job{'Archive'}";
|
|
|
|
my $archiveDir = "$job{'RootDir'}/$jobname/$archive";
|
|
|
|
if ( $cliDebug != 1 ) {
|
|
print "Running Rise $jobname ArchiveDir $archiveDir\n";
|
|
runRiseRsync( $jobname, $archiveDir, "/" );
|
|
}
|
|
else {
|
|
print "ArchiveDir $archiveDir\n";
|
|
print "Debug - exiting before rise\n";
|
|
exit;
|
|
}
|
|
|
|
$configDB = esmith::ConfigDB->open or die "Error: Couldn't open config db.";
|
|
|
|
imapIndexFilesDelete();
|
|
|
|
print "Signalling post-upgrade event\n";
|
|
@cmd = ( "/sbin/e-smith/signal-event", "post-upgrade" );
|
|
if ( $cliDebug != 1 ) {
|
|
ExecCmd( @cmd, 0 );
|
|
} else {
|
|
print "Debug - exit without post-upgrade\n";
|
|
exit;
|
|
}
|
|
|
|
####################################################################################
|
|
# We can't do the following unless saveMySoul has been run
|
|
####################################################################################
|
|
|
|
|
|
####################################################################################
|
|
|
|
# preserve ethernet driver configuration for this hardware.
|
|
# This allows us to run the --rise option remotely and connect to the restored server
|
|
|
|
my $srcconfigPath = "/var/affa/$ServerBasename/$archive/home/e-smith/db/configuration";
|
|
my $srcconfig = esmith::ConfigDB->open_ro($srcconfigPath)
|
|
or affaErrorExit("Couldn't open source config db $srcconfigPath");
|
|
$configDB->set_prop( "EthernetDriver1", "type", $srcconfig->get("EthernetDriver1")->value );
|
|
$configDB->set_prop( "EthernetDriver2", "type", $srcconfig->get("EthernetDriver2")->value );
|
|
$configDB->set_prop( "InternalInterface", "Driver", $srcconfig->get_prop( "InternalInterface", "Driver" ) );
|
|
$configDB->set_prop( "InternalInterface", "Name", $srcconfig->get_prop( "InternalInterface", "Name" ) );
|
|
$configDB->set_prop( "InternalInterface", "NICBondingOptions",
|
|
$srcconfig->get_prop( "InternalInterface", "NICBondingOptions" ) );
|
|
$configDB->set_prop( "InternalInterface", "NICBonding",
|
|
$srcconfig->get_prop( "InternalInterface", "NICBonding" ) || 'disabled' );
|
|
|
|
print "Please make sure that the server '" . $job{'remoteHostName'} . "' is not connected to your network.\n";
|
|
print "Otherwise you will not be able to connect to this server after the reboot.\n";
|
|
print "Please reboot this server now.\n";
|
|
}
|
|
|
|
sub runRiseRsync($$$) {
|
|
( my $jobname, my $archive, my $dest ) = @_;
|
|
|
|
# set $dest = /
|
|
|
|
#### ligne d'origine AffaV2: (my $archive,my $dest)=@_;
|
|
|
|
#my $b = new esmith::Backup or die "Error: Couldn't create Backup object\n";
|
|
|
|
#### ligne d'origine AffaV2: my @SourceDirs = $opts{'all'} ? getSourceDirs() : $b->restore_list;
|
|
#my @SourceDirs = $opts{'all'} ? getSourceDirs($jobname) : $b->restore_list;
|
|
|
|
#my @SourceDirs = $opts{'all'} ? getSourceDirs($jobname) : $b->restore_list;
|
|
|
|
#This should be scheduled.0 or the ARGv
|
|
|
|
my ( $local, $remote ) = getSourceDirs($jobname);
|
|
|
|
# $remote is the destination - it could be local
|
|
|
|
# Why? This is questionable
|
|
if ( $jobname eq 'undo-rise' ) {
|
|
push( @$remote, "/etc/affa" );
|
|
}
|
|
|
|
my $txt = 'Running Rise rsync...';
|
|
lg($txt);
|
|
print "$txt\n";
|
|
|
|
#my $src = "$job{'RootDir'}/$jobname/$archive$source";
|
|
# foreach my $src (@SourceDirs) {
|
|
my $linkDest = getLinkdest( $jobname, 0 ); # scheduled.0
|
|
|
|
affaErrorExit("No LinkDest $linkDest") unless $linkDest;
|
|
|
|
dbg("runRiseRsync using link destination $linkDest") if $linkDest;
|
|
|
|
foreach my $url (@$remote) {
|
|
|
|
my @split = split( ':', $url );
|
|
my $src = $split[1];
|
|
#my $target = "\"$k\"";
|
|
|
|
$src =~ s/"/\\"/g;
|
|
chomp($src);
|
|
|
|
my $archivesrc = $archive . $src;
|
|
|
|
# Call it dest and not source to save confusion
|
|
my $dest = "$src";
|
|
|
|
# Check is this is a dir and add a / if it is
|
|
if (-d $archivesrc) {
|
|
$archivesrc = $archive . $src . "/";
|
|
}
|
|
|
|
#my $linkDest = "$job{'RootDir'}/$jobname/$linkDest/$archivesrc";
|
|
|
|
|
|
my @cmd = (
|
|
"/usr/bin/rsync", "--archive",
|
|
"--stats", "--delete-during",
|
|
"--ignore-errors", "--delete-excluded",
|
|
"--partial", "--inplace",
|
|
"--numeric-ids",
|
|
# "--link-dest=$archive$src",
|
|
# We are trying to set say daily.0 instead of scheduled.0 if required
|
|
# ( $linkDest ? "--link-dest='$job{'RootDir'}/$jobname/$linkDest'" : '' ), "$archive$src", "$dest$src"
|
|
# $src is the destination $dest = '/' but no idea if that is useful - just ends with //some/dir
|
|
|
|
# --link-dest
|
|
# Unchanged files are hard linked from DIR to the destination directory
|
|
# rsync -av --link-dest=$PWD/prior_dir host:src_dir/ new_dir/
|
|
|
|
# If no LinkDest we should have already bailed?
|
|
"--link-dest=$archivesrc", "$archivesrc", "$dest"
|
|
);
|
|
|
|
print "Restoring LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
|
|
|
|
if ( $cliDebug == 1 ) {
|
|
print "What does the next line say?\n";
|
|
print "LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
|
|
print join( ',', @cmd );
|
|
print "\n";
|
|
}
|
|
|
|
# reetp - and run or not
|
|
if ( $cliDebug == 1 ) {
|
|
print " Debug - don't exec cmd\n";
|
|
} else {
|
|
lg("LinkDest: $linkDest Src: $archivesrc Dest: $dest\n");
|
|
my $status = ExecCmd( @cmd, 0 );
|
|
|
|
if ( $status == 0 or $status == 23 or $status == 24 ) {
|
|
|
|
# $Affa::lib::ExecCmdOut =~ /(Total file size: [0-9]* bytes)/gm;
|
|
|
|
$ExecCmdOut =~ /(Total file size: [0-9]* bytes)/gm;
|
|
|
|
# print "OK. $1\n"; ##### Bug 9139: valeur de $1 non définie.
|
|
print "OK. Restoring LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
|
|
}
|
|
else {
|
|
print "Failed. Restoring LinkDest: $linkDest Src: $archivesrc Dest: $dest\n";
|
|
}
|
|
}
|
|
}
|
|
if ( $cliDebug == 1 ) {
|
|
print " Debug - exiting\n";
|
|
exit;
|
|
}
|
|
}
|
|
|
|
sub undoRise() {
|
|
# Note that this will undo back to the last run - scheduled.0
|
|
# What if you had tried rise 'day.3' Which you can do?
|
|
# But... you were at 'latest' at the point you started the rise.
|
|
# So this is correct
|
|
|
|
my @cmd;
|
|
$interactive = 1;
|
|
|
|
# search server base backup
|
|
my $dir = opendir( DIR, "/var/affa/" );
|
|
affaErrorExit("Couldn't open directory /var/affa/") if not $dir;
|
|
my $archive;
|
|
my $a = '';
|
|
while ( defined( $archive = readdir(DIR) ) ) {
|
|
next if not $archive =~ /AFFA\.[a-z][a-z0-9\-]*\..*-\d*\.\d*\.\d*\.\d*/;
|
|
$a = $archive;
|
|
}
|
|
affaErrorExit("Server backup found.") if not $a and not -f "$a/scheduled.0";
|
|
stopServices();
|
|
print "\nWARNING: You will loose all data of your current server installation!\n";
|
|
my $input = '';
|
|
while ( not $input =~ /^(proceed|quit|exit)$/ ) {
|
|
print "Type 'proceed' to continue or 'quit' to exit: ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
}
|
|
if ( $input ne 'proceed' ) {
|
|
$interactive = 0;
|
|
affaErrorExit("Terminated by user") if $input ne 'proceed';
|
|
}
|
|
|
|
print "Signalling pre-restore event\n";
|
|
@cmd = ( "/sbin/e-smith/signal-event", "pre-restore" );
|
|
ExecCmd( @cmd, 0 );
|
|
|
|
runRiseRsync( $jobname, "/var/affa/$a/scheduled.0/", "/" );
|
|
|
|
imapIndexFilesDelete();
|
|
|
|
print "Signalling post-upgrade event\n";
|
|
@cmd = ( "/sbin/e-smith/signal-event", "post-upgrade" );
|
|
ExecCmd( @cmd, 0 );
|
|
|
|
print "affa server base restored. Please reboot now.\n";
|
|
}
|
|
|
|
sub resumeInterrupted() {
|
|
my @csv = getStatusRaw();
|
|
my $delay = 15;
|
|
foreach my $line (@csv) {
|
|
my @c = split( ";", $line );
|
|
if ( $c[9] && $c[9] =~ 'interrupted' ) {
|
|
my $jobname = $c[0];
|
|
my %job = getJobConfig($jobname);
|
|
my $txt = "Interrupted Affa job '$jobname' ";
|
|
if ( $job{'resumeAfterBoot'} eq 'yes' ) {
|
|
system("/usr/sbin/affa --run --_delay=$delay $jobname &");
|
|
$txt .= "resumed. Start is delayed by $delay seconds.";
|
|
$delay += 30;
|
|
}
|
|
else {
|
|
$txt .= "was NOT resumed.";
|
|
}
|
|
lg($txt);
|
|
print "$txt\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
sub sendKeys() {
|
|
$interactive = 1;
|
|
@ARGV = getJobs() if not $ARGV[0];
|
|
foreach my $jobname (@ARGV) {
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $kf = "/root/.ssh/id_rsa_affa.pub";
|
|
my $s;
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
print "Job $jobname: " if ($jobname);
|
|
if ( not -f $kf or not -f "/root/.ssh/id_rsa_affa" ) {
|
|
$s = "Generating RSA affa keys...";
|
|
print "$s\n";
|
|
lg($s);
|
|
@cmd = ( "/usr/bin/ssh-keygen", "-t", "rsa", "-N ''", "-b 4096", "-f", "/root/.ssh/id_rsa_affa" );
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't generate RSA keys");
|
|
$s = "Successfully created RSA key pair.";
|
|
print "$s\n";
|
|
lg($s);
|
|
}
|
|
open( PUBK, $kf ) or affaErrorExit("Could not open $kf");
|
|
my $pubk = trim(<PUBK>);
|
|
close(PUBK);
|
|
unlink "/root/.ssh/knownhosts-$jobname";
|
|
my $ak = $job{'RemoteAuthorizedKeysFile'};
|
|
( my $kd = $ak ) =~ s/(.*?)\/.*/$1/;
|
|
my $mkdirstr = $kd ? "mkdir -p $kd && chmod 700 $kd &&" : '';
|
|
my $cmd =
|
|
"/bin/cat $kf | /usr/bin/ssh $job{'_sshOpts'} -q -p $job{'sshPort'} $job{'remoteUser'}\@$job{'remoteHostName'} 'cat - > /tmp/$SystemName.\$\$ && $mkdirstr touch $ak && grep -v \"$pubk\" < $ak >> /tmp/$SystemName.\$\$ ; mv -f /tmp/$SystemName.\$\$ $ak'";
|
|
dbg("Exec Cmd: $cmd");
|
|
my $err = system($cmd);
|
|
$s =
|
|
$err ? "Sending public key to $job{'remoteHostName'} failed." : "Public key sent to $job{'remoteHostName'}";
|
|
print "$s\n";
|
|
lg($s);
|
|
}
|
|
}
|
|
|
|
sub revokeKeys($) {
|
|
my $jobname = shift;
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $kf = "/root/.ssh/id_rsa_affa.pub";
|
|
return if not -f $kf;
|
|
my $s;
|
|
my @cmd;
|
|
if ( not $cfg->SectionExists($jobname) ) { # does job exist?
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
open( PUBK, $kf ) or affaErrorExit("Could not open $kf");
|
|
my $pubk = trim(<PUBK>);
|
|
close(PUBK);
|
|
my $ak = $job{'RemoteAuthorizedKeysFile'};
|
|
my $cmd =
|
|
"/usr/bin/ssh $job{'_sshOpts'} -q -o PasswordAuthentication=no -o StrictHostKeyChecking=yes $job{'remoteUser'}\@$job{'remoteHostName'} 'touch $ak && grep -v \"$pubk\" < $ak > $ak.$SystemName.\$\$ ; mv -f $ak.$SystemName.\$\$ $ak'";
|
|
dbg("Exec Cmd: $cmd");
|
|
my $err = system($cmd);
|
|
|
|
if ($err) {
|
|
$s = "Deleting public key on $job{'remoteHostName'} failed.";
|
|
print "$s\n";
|
|
affaErrorExit($s);
|
|
}
|
|
unlink "/root/.ssh/knownhosts-$jobname";
|
|
$s = "Public key deleted on $job{'remoteHostName'}";
|
|
print "$s\n";
|
|
lg($s);
|
|
}
|
|
|
|
sub checkConnectionsAll() {
|
|
@ARGV = getJobs() if not $ARGV[0];
|
|
foreach my $jb ( sort @ARGV ) {
|
|
next if $jb eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jb);
|
|
my @cmd;
|
|
printf "%-16s : ", $jb;
|
|
if ( $job{'_rsyncd'} ) {
|
|
print "Rsyncd connection ";
|
|
@cmd = ( $job{'_rsyncLocal'}, '-dq', ( $job{'rsyncdUser'} ? $job{'rsyncdUser'} . '@' : '' ) . $job{'remoteHostName'} . "::'" . $job{'rsyncdModule'} . "'"
|
|
);
|
|
print( ( ( ExecCmd( @cmd, 0 ) == 0 ) ? "ok" : "FAILED" ) . ". " );
|
|
}
|
|
if ($jb ne 'localhost') {
|
|
print "SSH connection ";
|
|
@cmd = ( '/usr/bin/ssh', '-p', $job{'sshPort'}, '-i', '/root/.ssh/id_rsa_affa', '-o', "HostKeyAlias=$jb", '-o', 'StrictHostKeyChecking=yes', '-o', "ConnectTimeout=$job{'ConnectionCheckTimeout'}", '-o', 'PasswordAuthentication=no', $job{'_sshOpts'}, $job{'remoteUser'} . '@' . $job{'remoteHostName'}, 'echo OK'
|
|
);
|
|
ExecCmd( @cmd, 0 );
|
|
chomp($ExecCmdOut);
|
|
print( $ExecCmdOut eq "OK" ? "ok\n" : "FAILED\n" );
|
|
} else {
|
|
print "Not tested\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
sub showProperty() {
|
|
exit if not $ARGV[0];
|
|
my $pa = $ARGV[0];
|
|
my $prop = '';
|
|
my $cnt = 0;
|
|
my %def = getDefaultConfig();
|
|
for my $p ( sort keys %def ) {
|
|
next if $p =~ /^_/;
|
|
if ( $p =~ /^$pa/i ) {
|
|
if ( $p =~ /^$pa$/i ) {
|
|
$prop = $p;
|
|
last;
|
|
}
|
|
$prop .= " " if $cnt;
|
|
$prop .= $p;
|
|
$cnt++;
|
|
}
|
|
}
|
|
exit if not $prop;
|
|
if ( $cnt > 1 ) {
|
|
print "$pa is ambiguous: $prop\n";
|
|
exit;
|
|
}
|
|
print "Values of property $prop\n";
|
|
|
|
my $jobcolw = 17;
|
|
my %multi = getMultivalueKeys();
|
|
my @sections = $cfg->Sections();
|
|
foreach my $jobname ( sort @sections ) {
|
|
next if $jobname eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jobname);
|
|
my $jn = length($jobname) > $jobcolw ? substr( $jobname, 0, $jobcolw - 2 ) . ".." : $jobname;
|
|
if ( $multi{$prop} ) {
|
|
my @v = @{ $job{$prop} };
|
|
my $out = '';
|
|
foreach my $p ( sort @v ) {
|
|
if ( $prop =~ /(TimeSchedule|SambaValidUser)/ ) {
|
|
$out .= $out ? " $p" : sprintf( "%-" . $jobcolw . "s: %s", $jn, $p );
|
|
}
|
|
else {
|
|
$out .= sprintf( "%-" . $jobcolw . "s: %s\n", $jn, $p );
|
|
}
|
|
}
|
|
$out =~ s/\n$//;
|
|
print "$out\n" if $out;
|
|
}
|
|
else {
|
|
printf( "%-" . $jobcolw . "s: %s\n", $jn, $job{$prop} );
|
|
}
|
|
}
|
|
}
|
|
|
|
sub showConfigPathes() {
|
|
$interactive = 1;
|
|
my @csv = showConfigPathesRaw();
|
|
my $out = "$affaTitle\n";
|
|
if ( $opts{'csv'} ) {
|
|
print join( "\n", @csv ) . "\n";
|
|
}
|
|
else {
|
|
shift(@csv);
|
|
my $jobcolw = 2;
|
|
foreach my $k (@csv) {
|
|
my @c = split( ";", $k );
|
|
$jobcolw = length( $c[0] ) if $jobcolw < length( $c[0] );
|
|
}
|
|
$jobcolw = 3 if ( $jobcolw < 3 );
|
|
$jobcolw = 17 if ( $jobcolw > 17 );
|
|
foreach my $k ( sort @csv ) {
|
|
( my $job, my $configpath ) = split( ";", $k );
|
|
$job = length($job) > $jobcolw ? substr( $job, 0, $jobcolw - 2 ) . ".." : $job;
|
|
printf( "%-" . $jobcolw . "s: %s\n", $job, $configpath );
|
|
}
|
|
}
|
|
}
|
|
|
|
sub showConfigPathesRaw() {
|
|
my @ret;
|
|
push( @ret, "job;configpath" );
|
|
|
|
if ( not $ARGV[0] ) {
|
|
foreach my $job ( sort $cfg->Sections() ) {
|
|
next if $job eq 'GlobalAffaConfig';
|
|
push( @ARGV, $job );
|
|
}
|
|
}
|
|
|
|
foreach my $j ( sort @ARGV ) {
|
|
my $c = openOrgConfig($j);
|
|
push( @ret, "$j;" . $c->GetFileName() );
|
|
}
|
|
return @ret;
|
|
}
|
|
|
|
sub logTail() {
|
|
my $txt;
|
|
my $jobname = $ARGV[0] || '';
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
my $log;
|
|
if ( not $jobname ) {
|
|
$log = $logfile; # global log
|
|
}
|
|
else {
|
|
$log = "$logdir/$jobname.log";
|
|
}
|
|
if ( not -f $log ) {
|
|
$txt = "Error: Job log file not found.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
open( LF, $log );
|
|
my $lc = 0;
|
|
while ( <LF> && $lc < 50 ) {
|
|
$lc++;
|
|
}
|
|
close(LF);
|
|
print "\nHit Ctrl-C to exit\n";
|
|
sleep(1);
|
|
if ( $lc < 50 ) { #current log file is too short
|
|
my $pl = `ls -r $log-* 2>/dev/null | head -1`;
|
|
system("tail -50 $pl") if $pl; # show last rotated log file
|
|
}
|
|
system("tail -n 50 -f $log");
|
|
}
|
|
|
|
sub showSchedule() {
|
|
my $res = $opts{15} ? 15 : 30;
|
|
my $vt = 240 / 30 * $res;
|
|
my %out;
|
|
my $maxlen = 0;
|
|
my $disabled = 0;
|
|
my @cols;
|
|
for ( my $i = 0, my $h = 12 * 60; $i < 24 * 60; $i += $res, $h += $res ) {
|
|
$h = 0 if $h >= 24 * 60;
|
|
push( @cols, $h );
|
|
}
|
|
foreach my $jobname ( sort $cfg->Sections() ) {
|
|
next if $jobname eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jobname);
|
|
if ( $job{'status'} ne "enabled" && not $opts{'all'} ) {
|
|
$disabled++;
|
|
next;
|
|
}
|
|
my $rpt;
|
|
my $rptfile = $job{'RootDir'} . "/$jobname/scheduled.0/.AFFA4-REPORT";
|
|
if ( -f $rptfile ) {
|
|
$rpt = Config::IniFiles->new( -file => $rptfile );
|
|
}
|
|
my $ExecTime = $rpt ? $rpt->val( 'Report', 'ExecutionTime' ) : 0;
|
|
$ExecTime = int( $ExecTime / 60 );
|
|
my $end = $rpt ? $rpt->val( 'Report', 'Date' ) : '0000';
|
|
$end =~ /.*(..)(..)$/;
|
|
$end = $1 * 60 + $2;
|
|
my $pid = getLock($jobname);
|
|
my $dedup = $rpt && $rpt->exists( 'Report', 'DedupDate' ) ? 1 : 0;
|
|
my $ddend = '0000';
|
|
|
|
if ( !$dedup ) {
|
|
if ( $job{'dedup'} && $job{'dedup'} eq 'yes' && $pid && findProcessId( $pid, $dedupBinary ) ) {
|
|
$ddend = Date::Format::time2str( "%Y%m%d%H%M", time() );
|
|
$dedup = 1;
|
|
}
|
|
}
|
|
else {
|
|
$ddend = $rpt->val( 'Report', 'DedupDate' );
|
|
}
|
|
$ddend =~ /.*(..)(..)$/;
|
|
$ddend = $1 * 60 + $2;
|
|
my $ddstart = $end;
|
|
my $start = $end - $ExecTime;
|
|
while ( $start < 0 ) { $start += 1440 }
|
|
my ( $s1, $e1, $s2, $e2 );
|
|
if ( $start > $end ) {
|
|
$s1 = 0;
|
|
$e1 = $end;
|
|
$s2 = $start;
|
|
$e2 = 1440;
|
|
}
|
|
else {
|
|
$s1 = $s2 = $start;
|
|
$e1 = $e2 = $end;
|
|
}
|
|
my ( $ds1, $de1, $ds2, $de2 );
|
|
if ( $ddstart > $ddend ) {
|
|
$ds1 = 0;
|
|
$de1 = $ddend;
|
|
$ds2 = $ddstart;
|
|
$de2 = 1440;
|
|
}
|
|
else {
|
|
$ds1 = $ds2 = $ddstart;
|
|
$de1 = $de2 = $ddend;
|
|
}
|
|
my $jn = substr( $jobname, 0, 26 );
|
|
$maxlen = length($jn) if length($jn) > $maxlen;
|
|
|
|
# Time Schedule
|
|
my @ts = sort $cfg->val( $jobname, 'TimeSchedule' );
|
|
for ( my $i = 0; $i < @ts; $i++ ) {
|
|
$ts[$i] =~ /(..)(..)/;
|
|
$ts[$i] = $1 * 60 + $2;
|
|
}
|
|
|
|
### fix bug 10196
|
|
my @am;
|
|
my @pm;
|
|
|
|
while (@ts) {
|
|
my $t = shift @ts;
|
|
if ( $t <= 720 ) {
|
|
push( @am, $t );
|
|
}
|
|
else {
|
|
push( @pm, $t );
|
|
}
|
|
}
|
|
push( @ts, @pm );
|
|
push( @ts, @am );
|
|
|
|
## end fix bug 10196
|
|
|
|
# Kill at
|
|
my $killat = -1;
|
|
if ( $job{'killAt'} ) {
|
|
$job{'killAt'} =~ /(..)(..)/;
|
|
$killat = $1 * 60 + $2;
|
|
}
|
|
|
|
# Resume at
|
|
my $resumeat = -1;
|
|
if ( $job{'resumeKilledAt'} ) {
|
|
$job{'resumeKilledAt'} =~ /(..)(..)/;
|
|
$resumeat = $1 * 60 + $2;
|
|
}
|
|
|
|
my $t = shift @ts if @ts;
|
|
for my $i (@cols) {
|
|
$out{$jn} .= " " if $i % $vt == 0;
|
|
if ( $killat >= $i && $killat < $i + $res ) {
|
|
$out{$jn} .= "K";
|
|
}
|
|
elsif ( $resumeat >= $i && $resumeat < $i + $res ) {
|
|
$out{$jn} .= "R";
|
|
####### next line modified: fix bug 10196
|
|
# } elsif( $t >= $i && $t < $i+$res ) {
|
|
}
|
|
elsif ( $t && $t >= $i && $t < $i + $res ) {
|
|
if ( $t >= $i && $t < $i + $res ) {
|
|
$out{$jn} .= "S";
|
|
|
|
###### 3 next lines: fix bug 10196
|
|
# while( @ts && $t < $i+$res ) {
|
|
# $t=shift @ts;
|
|
# }
|
|
##### replaced through:
|
|
while ( @ts && $t < 720 && $t < $i + $res ) {
|
|
$t = shift @ts;
|
|
}
|
|
|
|
while ( @ts && $t >= 720 && $t < $i + $res ) {
|
|
$t = shift @ts;
|
|
}
|
|
}
|
|
## end fix bug 10196
|
|
|
|
}
|
|
elsif ( $i >= $s1 && $i < $e1 or $i >= $s2 && $i < $e2 ) {
|
|
$out{$jn} .= "=";
|
|
}
|
|
elsif ( $dedup && ( $i >= $ds1 && $i < $de1 or $i >= $ds2 && $i < $de2 ) ) {
|
|
$out{$jn} .= "~";
|
|
}
|
|
else {
|
|
$out{$jn} .= "-";
|
|
}
|
|
}
|
|
$out{$jn} .= " busy" if $pid;
|
|
$out{$jn} .= sprintf "\n";
|
|
}
|
|
print "$affaTitle\n";
|
|
if (%out) {
|
|
printf "%" . $maxlen . "s", "TIME";
|
|
for my $i (@cols) {
|
|
my $h = int( $i / 60 );
|
|
my $m = sprintf "%02d", $i - $h * 60;
|
|
printf " %-8s", "$h:$m" if $i % $vt == 0;
|
|
}
|
|
print "\n";
|
|
foreach my $k ( sort { index( $out{$a}, 'S' ) cmp index( $out{$b}, 'S' ) } keys %out ) {
|
|
printf "%" . $maxlen . "s%s", $k, $out{$k};
|
|
}
|
|
}
|
|
printf "Symbols: S=scheduled K=kill R=resume '='=rsync '~'=dedup\n";
|
|
printf "%d disabled jobs not listed. Use --all to display.\n", $disabled if $disabled;
|
|
}
|
|
|
|
sub sendStatus() {
|
|
if ( $job{'sendStatus'} || '' =~ /^(daily|weekly|monthly)$/ ) {
|
|
$job{'sendStatus'} = ucfirst( $job{'sendStatus'} );
|
|
my $msg = new Mail::Send;
|
|
$msg->subject("$job{'sendStatus'} status from Affa server $SystemName");
|
|
$msg->to( $job{'_EmailAddress'} );
|
|
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
|
|
my $fh = $msg->open;
|
|
print $fh "Status:\n";
|
|
print $fh getStatus();
|
|
print $fh "\nDisk Usage:\n";
|
|
print $fh DiskUsage();
|
|
print $fh "\nArchive lists of all jobs:\n";
|
|
print $fh listArchives();
|
|
print $fh "\ngenerated on " . Date::Format::ctime( time() );
|
|
$fh->close;
|
|
lg( "$job{'sendStatus'} status message sent to " . $job{'_EmailAddress'} );
|
|
}
|
|
}
|
|
|
|
sub mailTest() {
|
|
my $jobname = $ARGV[0] || '';
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
my $msg = new Mail::Send;
|
|
$msg->subject("Testmail from job '$jobname' on Affa server $SystemName");
|
|
$msg->to( $job{'_EmailAddress'} );
|
|
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
|
|
my $fh = $msg->open;
|
|
print $fh "It works!\n\n";
|
|
$fh->close;
|
|
|
|
foreach my $k ( @{ $job{'EmailAddress'} } ) {
|
|
my $txt = "Testmail sent to $k";
|
|
lg($txt);
|
|
print "$txt\n";
|
|
}
|
|
|
|
####################################################################################
|
|
#### début des modifs: copié à partir de Affa V2 et ajouté "$jobname" à mailTestWatchdogRemote();
|
|
if ( $job{'Watchdog'} eq 'yes' and $job{'remoteHostName'} ne 'localhost' ) {
|
|
mailTestWatchdogRemote($jobname);
|
|
my $txt = "Watchdog testmail from $job{'remoteHostName'} sent to " . $job{'_EmailAddress'};
|
|
lg($txt);
|
|
print "$txt\n";
|
|
}
|
|
#### fin des modifs
|
|
####################################################################################
|
|
|
|
}
|
|
|
|
sub setupSamba() {
|
|
lg("Configuring Samba");
|
|
if ( $smbdStatus ne 'enabled' ) {
|
|
lg("Samba service is not installed or not properly configured.");
|
|
return;
|
|
}
|
|
|
|
my %inc;
|
|
foreach my $jobname ( $cfg->Sections() ) {
|
|
next if $jobname eq 'GlobalAffaConfig';
|
|
my %job = getJobConfig($jobname);
|
|
if ( not $job{'_SambaValidUser'} and $job{'SambaShare'} eq 'yes' ) {
|
|
lg( "Job $jobname: No Valid Users defined. Samba has been access disabled."
|
|
);
|
|
}
|
|
my $affasmb = "/etc/samba/Affa-Job-$jobname.conf";
|
|
if ( $job{'SambaShare'} eq 'yes' and $job{'_SambaValidUser'} ) {
|
|
open( FD, ">$affasmb" )
|
|
or affaErrorExit("Could not create $affasmb");
|
|
print FD "[$jobname]\n";
|
|
print FD "path = $job{'RootDir'}/$jobname\n";
|
|
print FD "comment = Affa archive: $job{'Description'}\n";
|
|
print FD "valid users = $job{'_SambaValidUser'}\n";
|
|
print FD "force user = root\n";
|
|
print FD "read only = yes\n";
|
|
print FD "writable = no\n";
|
|
print FD
|
|
"veto files = /.$jobname-setup.ini/,/.doneDates/,/.AFFA4-REPORT/,/.AFFA-REPORT/.AFFA-TRASH/\n\n";
|
|
close(FD);
|
|
$inc{"include = $affasmb"} = 1;
|
|
}
|
|
else {
|
|
unlink($affasmb);
|
|
}
|
|
}
|
|
|
|
open( FD, "<$smbconf" ) or affaErrorExit("Could not open $smbconf");
|
|
open( FW, ">$smbconf.$$" )
|
|
or affaErrorExit("Could not create $smbconf.$$");
|
|
while (<FD>) {
|
|
$_ = trim($_);
|
|
if ( $_ =~ /include = (\/etc\/samba\/Affa-Job.*conf)/
|
|
and not $inc{$_} )
|
|
{
|
|
unlink $1;
|
|
}
|
|
print FW "$_\n"
|
|
if not $_
|
|
=~ /(include = \/etc\/samba\/Affa-Job|# Affa archives. Updated on)/;
|
|
|
|
# ligne d'origine - Bug 9298 - if( $_ =~ /^\[global\]$/i ) {
|
|
if ( $_ =~ /^#\[Affa-jobs\]$/i ) {
|
|
printf FW "# Affa archives. Updated on "
|
|
. formatHTime(
|
|
Date::Format::time2str( "%Y%m%d%H%M", time() ) )
|
|
. "\n";
|
|
print FW join( "\n", sort keys %inc ) . "\n";
|
|
}
|
|
}
|
|
close(FD);
|
|
close(FW);
|
|
|
|
rename( "$smbconf.$$", "$smbconf" )
|
|
or affaErrorExit("Moving $smbconf.$$ to $smbconf failed");
|
|
|
|
#my @cmd = ( $servicecommand, 'smb', 'reload' ); # reetp changed to restart
|
|
#ExecCmd( @cmd, 0 ); # Reload Samba config
|
|
|
|
$systemD->{'serviceName'} = 'smbd';
|
|
|
|
my $smbdStatus = ( $systemD->reload() ); # reetp - failure??
|
|
|
|
print "Status $smbdStatus\n";
|
|
|
|
}
|
|
|
|
|
|
sub sendErrorMesssage() {
|
|
return if not $jobname or not $Command;
|
|
my %job;
|
|
if ( $cfg->SectionExists($jobname) ) {
|
|
%job = getJobConfig($jobname);
|
|
}
|
|
return if not $job{'_EmailAddress'};
|
|
my $msg = new Mail::Send;
|
|
$msg->subject("Error on Affa server $SystemName: Job '$jobname' failed.");
|
|
$msg->to( $job{'_EmailAddress'} );
|
|
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
|
|
my $fh = $msg->open;
|
|
print $fh "Excerpt from log file $logfile:\n";
|
|
|
|
foreach my $k (@Messages) {
|
|
chomp($k);
|
|
$k =~ s/ /_/g;
|
|
print $fh "$k\n";
|
|
}
|
|
$fh->close;
|
|
lg( "Failure message sent to " . $job{'_EmailAddress'} );
|
|
}
|
|
|
|
sub sendSuccessMesssage() {
|
|
return
|
|
if not $jobname
|
|
or $Command ne "scheduled"
|
|
or not $job{'_EmailAddress'}
|
|
or ( $job{'chattyOnSuccess'} || 0 ) <= 0;
|
|
if ( $job{'chattyOnSuccess'} > 0 ) {
|
|
$job{'chattyOnSuccess'}--;
|
|
my $err = rewriteConfigVal( $jobname, 'chattyOnSuccess', $job{'chattyOnSuccess'} );
|
|
dbg("Decremented 'chattyOnSuccess' value to $job{'chattyOnSuccess'} in config file of job '$jobname'")
|
|
if ( not $err );
|
|
}
|
|
my $msg = new Mail::Send;
|
|
$msg->subject("Success on Affa server $SystemName: Job '$jobname' completed.");
|
|
$msg->to( $job{'_EmailAddress'} );
|
|
$msg->set( "From", "\"Affa Server $SystemName\" <noreply\@$DomainName>" );
|
|
my $fh = $msg->open;
|
|
print $fh "Affa job '$jobname' successfully completed.\n\n";
|
|
print $fh listArchives() . "\n";
|
|
print $fh "\nDisk Usage:\n";
|
|
print $fh DiskUsage();
|
|
print $fh "\nYou will receive "
|
|
. ( $job{'chattyOnSuccess'} ? $job{'chattyOnSuccess'} : 'no' )
|
|
. " further success notifications.\n";
|
|
$fh->close;
|
|
lg( "Success message sent to " . $job{'_EmailAddress'} );
|
|
}
|
|
|
|
sub cronSetup() {
|
|
my $md5 = Digest::MD5->new;
|
|
my $mt = -M '/etc/cron.d/affa';
|
|
foreach my $s ( getConfigFileList() ) {
|
|
$mt = -M $s if $mt > -M $s;
|
|
$md5->add($s);
|
|
}
|
|
open( IN, "</etc/cron.d/affa" );
|
|
( my $md5x = <IN> ) =~ s/.*MD5:(.*)$/$1/;
|
|
chomp($md5x);
|
|
close(IN);
|
|
my $md5n = $md5->hexdigest;
|
|
return if ( $md5x eq $md5n ) && ( $mt >= -M '/etc/cron.d/affa' );
|
|
my @sects = $cfg->Sections();
|
|
open( CF, ">/tmp/affa.$$.$curtime" );
|
|
printf CF "# updated on "
|
|
. formatHTime( Date::Format::time2str( "%Y%m%d%H%M", time() ) ) . " MD5:"
|
|
. $md5n . "\n\n";
|
|
|
|
foreach my $j ( sort @sects ) {
|
|
next if ( $j eq "GlobalAffaConfig" );
|
|
my %job = getJobConfig($j);
|
|
if ( $job{'status'} eq 'enabled' ) {
|
|
print CF "# Job $j; $job{'remoteHostName'}, $job{'Description'}\n";
|
|
for my $t ( sort @{ $job{'TimeSchedule'} } ) {
|
|
$t =~ /(\d\d)(\d\d)/;
|
|
printf CF "%02d %02d * * * root %s --silent --run %s\n", $2, $1, '/usr/sbin/affa', $j;
|
|
}
|
|
if ( $job{'killAt'} ) {
|
|
( my $t = $job{'killAt'} ) =~ /(\d\d)(\d\d)/;
|
|
my $nt = 3600 * $1 + 60 * $2;
|
|
my $r = '';
|
|
if ( $job{'resumeKilledAt'} ) {
|
|
$job{'resumeKilledAt'} =~ /(\d\d)(\d\d)/;
|
|
my $sec = ( 3600 * $1 + 60 * $2 ) - $nt;
|
|
$sec = $sec <= 0 ? $sec += 86400 : $sec;
|
|
$r = "--resume=$sec";
|
|
}
|
|
printf CF "%02d %02d * * * root %s --silent --kill %s %s\n", $2, $1, '/usr/sbin/affa', $r, $j;
|
|
}
|
|
}
|
|
else {
|
|
print CF "# DISABLED Job $j; $job{'remoteHostName'}, $job{'Description'}\n";
|
|
}
|
|
print CF "\n";
|
|
}
|
|
my %job = getJobConfig('');
|
|
print CF "# Send status\n";
|
|
if ( $job{'sendStatus'} =~ /(daily|weekly|monthly)/ ) {
|
|
print CF "00 5 * * *" if $job{'sendStatus'} eq 'daily';
|
|
print CF "00 5 * * 0" if $job{'sendStatus'} eq 'weekly';
|
|
print CF "00 5 1 * *" if $job{'sendStatus'} eq 'monthly';
|
|
print CF " root /usr/sbin/affa --silent --send-status\n\n";
|
|
}
|
|
print CF "# update this file if needed\n";
|
|
print CF "*/15 * * * * root /usr/sbin/affa --silent --_cronupdate\n\n";
|
|
close(CF);
|
|
move( "/tmp/affa.$$.$curtime", "/etc/cron.d/affa" );
|
|
lg("/etc/cron.d/affa updated");
|
|
setupSamba();
|
|
}
|
|
|
|
sub moveFileorDir($$) {
|
|
( my $src, my $dst ) = @_;
|
|
return if not -e $src;
|
|
dbg("Moving $src to $dst");
|
|
move( $src, $dst ) or affaErrorExit("Moving $src to $dst failed");
|
|
}
|
|
|
|
sub removeDir($$) {
|
|
( my $dir, my $bg ) = @_;
|
|
return if ( not -d $dir );
|
|
$bg = $bg ? '&' : '';
|
|
dbg("Deleting $dir");
|
|
|
|
# after the first rm run, do a chmod 777 and run rm again. This delete files with wrong permissions.
|
|
# This is an issue on mounted CIFS filesystems.
|
|
#my @cmd=('/bin/rm', '-rf', $dir, ';', '/bin/chmod', '-R', '777', "$dir/*", '&>', '/dev/null', ';', '/bin/rm', '-rf', $dir );
|
|
my @cmd = ("(/bin/rm -rf $dir;/bin/chmod -R 777 $dir/*;/bin/rm -rf $dir) &>/dev/null $bg");
|
|
ExecCmd( @cmd, 0 );
|
|
}
|
|
|
|
sub remoteCopy($$$) {
|
|
( my $jobname, my $src, my $dst ) = @_;
|
|
my %job = getJobConfig($jobname);
|
|
my @cmd = (
|
|
$job{'_rsyncLocal'},
|
|
"--archive",
|
|
$job{'BandwidthLimit'} ? "--bwlimit=$job{'BandwidthLimit'}" : '',
|
|
$job{'rsync--modify-window'} > 0 ? "--modify-window=$job{'rsync--modify-window'}" : '',
|
|
$job{'rsyncTimeout'} ? "--timeout=$job{'rsyncTimeout'}" : "",
|
|
$job{'rsyncCompress'} eq 'yes' ? "--compress" : "",
|
|
"--rsync-path=\"$job{'_rsyncRemote'}\"",
|
|
"--rsh=\"$job{'localSSHBinary'} $job{'_sshOpts'}\"",
|
|
$src,
|
|
$job{'remoteUser'} . '@' . $job{'remoteHostName'} . ":$dst"
|
|
);
|
|
lg ("sub RemoteCopy");
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't copy $src to remote host.");
|
|
}
|
|
|
|
sub showVersion() {
|
|
print "$affaTitle\n";
|
|
|
|
# Ony shows if script detected
|
|
printf " Samba service is%s installed.\n", -f $SambaStartScript ? '' : ' NOT';
|
|
printf " NRPE service is%s installed.\n", -f $NRPEStartScript ? '' : ' NOT';
|
|
printf " Freedup program is%s installed.\n", -x $dedupBinary ? '' : ' NOT';
|
|
}
|
|
|
|
sub showTextfile($) {
|
|
my $f = shift;
|
|
if ( not -f $f ) {
|
|
print "File $f not found.\n";
|
|
exit -1;
|
|
}
|
|
open( FI, "<$f" );
|
|
while (<FI>) {
|
|
print $_;
|
|
}
|
|
}
|
|
|
|
sub showHelp($) {
|
|
my $short = shift;
|
|
if ( not $short ) {
|
|
print "$affaTitle\n";
|
|
print "Affa is a rsync based backup program for Linux.\n";
|
|
print "It remotely backups Linux or other systems, which have either the rsync\n";
|
|
print "program and the sshd service or the rsyncd service installed.\n";
|
|
print "Please see http://affa.sf.net for full documentation.\n";
|
|
print "\n";
|
|
print "Copyright (C) 2004-2012 by Michael Weinberger\n";
|
|
print "This program comes with ABSOLUTELY NO WARRANTY; for details type 'affa --warranty'.\n";
|
|
print "This is free software, and you are welcome to redistribute it\n";
|
|
print "under certain conditions; type 'affa --license' for details.\n";
|
|
print "\n";
|
|
}
|
|
print "Usage: affa --run JOB\n";
|
|
print " or affa --configcheck\n";
|
|
print " or affa --make-cronjobs\n";
|
|
print " or affa --send-key [JOB]\n";
|
|
print " or affa --check-connections [JOB JOB ...]\n";
|
|
print " or affa --resume-interrupted\n";
|
|
print " or affa --full-restore [--preserve-newer=no] [--delete=yes] JOB [ARCHIVE]\n";
|
|
print " or affa --rise JOB [ARCHIVE]\n";
|
|
print " or affa --list-archives [--csv] [JOB JOB ...]\n";
|
|
print " or affa --status [--csv]\n";
|
|
print " or affa --show-config-pathes [--csv] [JOB JOB ...]\n";
|
|
print " or affa --show-default-config\n";
|
|
print " or affa --show-schedule [-15]\n";
|
|
print " or affa --show-property PROPERTY\n";
|
|
print " or affa --log-tail [JOB]\n";
|
|
print " or affa --send-status\n";
|
|
print " or affa --disk-usage [--csv]\n";
|
|
print " or affa --cleanup JOB\n";
|
|
print " or affa --rename-job JOB NEWNAME\n";
|
|
print " or affa --move-archive JOB NEWROOTDIR\n";
|
|
print " or affa --delete-job JOB\n";
|
|
print " or affa --revoke-key JOB\n";
|
|
print " or affa --kill JOB\n";
|
|
print " or affa --killall\n";
|
|
print " or affa --mailtest JOB\n";
|
|
print " or affa --nrpe [JOB JOB ...]\n";
|
|
print " or affa --init-nrpe\n";
|
|
print " or affa --version\n";
|
|
print " or affa --warranty\n";
|
|
print " or affa --license\n";
|
|
print " or affa --help\n";
|
|
}
|
|
|
|
sub SignalHandler() {
|
|
my $sig = shift;
|
|
killProcessGroup( 0, $sig );
|
|
}
|
|
|
|
sub killall() {
|
|
foreach my $job ( sort $cfg->Sections() ) {
|
|
next if $job eq 'GlobalAffaConfig';
|
|
killJob($job);
|
|
}
|
|
}
|
|
|
|
sub killJob($) {
|
|
$jobname = shift;
|
|
$jobname =~ /([a-z0-9_\.-]*)/i;
|
|
$jobname = $1; # untaint
|
|
$allow_retry = 0;
|
|
if ( not $cfg->SectionExists($jobname) ) {
|
|
my $txt = "Error: Job '$jobname' undefined.";
|
|
print("$txt\n");
|
|
affaErrorExit("$txt");
|
|
}
|
|
my %job = getJobConfig($jobname);
|
|
my $pid = getLock($jobname);
|
|
if ($pid) {
|
|
if ( $opts{'all'} || $job{'dedupKill'} eq 'yes' || !findProcessId( $pid, $dedupBinary ) ) {
|
|
killProcessGroup( $pid, 'TERM' );
|
|
print "Affa job $jobname killed (pid=$pid)\n" if not $opts{'silent'};
|
|
my $resume = $opts{'resume'};
|
|
if ($resume) {
|
|
system("/usr/sbin/affa --run --_delay=$resume $jobname &");
|
|
}
|
|
}
|
|
else {
|
|
print "Deduplication still running. Job $jobname was not killed. Use --all to force kill.\n"
|
|
if not $opts{'silent'};
|
|
}
|
|
}
|
|
}
|
|
|
|
sub killProcessGroup($$) {
|
|
( my $pid, my $sig ) = @_;
|
|
$allow_retry = 0;
|
|
$SIG{'TERM'} = sub { };
|
|
my $pgrp = getpgrp($pid);
|
|
kill 'TERM', -$pgrp;
|
|
if ( $pid == 0 ) { # current process
|
|
lg("$Command run killed");
|
|
if ( $Command eq "scheduled" ) {
|
|
affaErrorExit("Caught interrupt signal $sig");
|
|
}
|
|
else {
|
|
exit -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
sub affaErrorExit($) {
|
|
( my $msg ) = @_;
|
|
my $package = (caller)[0];
|
|
my $err = (caller)[2];
|
|
my $sub = ( caller(1) )[3] || '';
|
|
my $txt = "Error ($err): $msg";
|
|
lg($txt);
|
|
print "$txt\n" if $interactive == 1;
|
|
unmountAll();
|
|
my $retry = ( defined $opts{'RetryAttempts'} ? $opts{'RetryAttempts'} : $job{'RetryAttempts'} ) - 1;
|
|
my $retryCmd = '';
|
|
|
|
if ( $allow_retry && $retry >= 0 && ( $Command eq 'scheduled' ) ) {
|
|
my $sleep = $opts{'RetryAfter'} || ( $job{'RetryAfter'} ) || 0;
|
|
$sleep = 0 if $sleep < 0;
|
|
$retry = int( 86400 / $sleep ) - 1 if $retry * $sleep > 86400;
|
|
$retryCmd = "/usr/sbin/affa --run $jobname --RetryAttempts=$retry --RetryAfter=$sleep &";
|
|
lg( "Starting re-run "
|
|
. ( $job{'RetryAttempts'} - $retry )
|
|
. " of $job{'RetryAttempts'} in $job{'RetryAfter'} seconds." );
|
|
}
|
|
lg( "Total execution time: " . timeUnit( time() - $StartTime ) ) if $StartTime;
|
|
sendErrorMesssage() if not $retryCmd or not $allow_retry or ( $job{'RetryNotification'} || '' ) eq 'yes';
|
|
removeLock();
|
|
|
|
####################################################################################
|
|
###################################################### début modifs ajouté comme dans Affa2
|
|
startServices() if $ServicesStopped; # reetp - note to self
|
|
###################################################### fin modifs
|
|
#######################################################################################
|
|
|
|
lg("Exiting.");
|
|
lg('.');
|
|
system("sleep 3 && $retryCmd") if $retryCmd;
|
|
exit -1;
|
|
}
|
|
|
|
sub affaExit( $ ) {
|
|
my $msg = shift(@_);
|
|
unmountAll();
|
|
lg($msg);
|
|
removeLock();
|
|
lg( "Total execution time: " . timeUnit( time() - $StartTime ) ) if $StartTime;
|
|
lg("Exiting.");
|
|
lg('.');
|
|
exit 0;
|
|
}
|
|
|
|
###
|
|
|
|
sub setlog( $ ) {
|
|
my $s = shift;
|
|
$logfile = "$logdir/$s";
|
|
}
|
|
|
|
sub lg( $ ) {
|
|
if ($runninglog) {
|
|
my $txt = $runninglog;
|
|
$runninglog = '';
|
|
lg($txt);
|
|
}
|
|
my $str = shift(@_);
|
|
chomp($str);
|
|
if ( defined($logfile) ) {
|
|
if ($interrupt) {
|
|
my $txt = $interrupt;
|
|
$interrupt = '';
|
|
lg($txt);
|
|
}
|
|
open( LOG, ">> $logfile" ) or die "Error: Couldn't open logfile $logfile for writing\n";
|
|
my $tag = Date::Format::time2str( "%a %b %e %T", time() ) . "[$process_id]:";
|
|
$tag .= " " if ( ( $Command || '' ) =~ /^(daily|weekly|monthly|yearly)$/ );
|
|
my @s = split( /[\r\n]+/, $str );
|
|
foreach my $se (@s) {
|
|
print LOG "$tag $se\n";
|
|
}
|
|
close(LOG) or warn "Error: Couldn't close logfile $logfile\n";
|
|
chown( 0, 101, $logfile );
|
|
chmod( 0640, $logfile );
|
|
}
|
|
push( @Messages, $str );
|
|
}
|
|
|
|
sub dbg( $ ) {
|
|
if ( ( $job{'Debug'} || '' ) eq 'yes' ) {
|
|
lg( shift(@_) );
|
|
}
|
|
else {
|
|
push( @Messages, @_ );
|
|
}
|
|
}
|
|
|
|
sub ExecCmd( \@$ ) {
|
|
( my $cmdRef, my $forcelog ) = @_;
|
|
my @cmd = @$cmdRef;
|
|
|
|
# reetp remove empty array keys
|
|
@cmd = grep { $_ ne '' } @cmd;
|
|
dbg "sub ExecCmd this CMD: @cmd\n";
|
|
my $pipestatus = '';
|
|
$forcelog = 1 if $job{'Debug'};
|
|
die "Fork failed: $!\n" unless defined( my $pid = open( RCHILD, "-|" ) );
|
|
if ($pid) {
|
|
$ExecCmdOut = '';
|
|
while (<RCHILD>) {
|
|
chomp($_);
|
|
next if $_ eq '';
|
|
dbg("Exec Out: $pipestatus") if $forcelog and $pipestatus; # one step offset
|
|
s/\e\[[0-9\;]+[A-Za-z]//g; # remove ANSI escape sequences
|
|
$ExecCmdOut .= "$_\n";
|
|
$pipestatus = $_;
|
|
}
|
|
close(RCHILD);
|
|
}
|
|
else {
|
|
dbg("Exec Cmd: @cmd");
|
|
exec("@cmd 2>&1; echo \${PIPESTATUS}") or die "exec failed: $!\n";
|
|
}
|
|
$ExecCmdOut =~ s/$pipestatus\n$//;
|
|
$pipestatus = $? if not $pipestatus;
|
|
dbg("Exec Out: exit status: $pipestatus - ExecCmdOut: $ExecCmdOut") if $forcelog;
|
|
return $pipestatus;
|
|
}
|
|
|
|
|
|
sub ExecCmdBackupList( \@$ ) {
|
|
( my $cmdRef, my $forcelog ) = @_;
|
|
my @cmd = @$cmdRef;
|
|
|
|
# reetp remove empty array keys
|
|
@cmd = grep { $_ ne '' } @cmd;
|
|
if ($cliDebug == 1) {
|
|
printf "ExecCMD = @cmd\n";
|
|
}
|
|
|
|
|
|
my $pipestatus = '';
|
|
$forcelog = 1 if $job{'Debug'};
|
|
die "Fork failed: $!\n" unless defined( my $pid = open( RCHILD, "-|" ) );
|
|
if ($pid) {
|
|
$ExecCmdOut = '';
|
|
while (<RCHILD>) {
|
|
chomp($_);
|
|
next if $_ eq '';
|
|
dbg("Exec Out: $pipestatus") if $forcelog and $pipestatus; # one step offset
|
|
s/\e\[[0-9\;]+[A-Za-z]//g; # remove ANSI escape sequences
|
|
$ExecCmdOut .= "$_\n";
|
|
$pipestatus = $_;
|
|
}
|
|
close(RCHILD);
|
|
}
|
|
else {
|
|
dbg("Exec Cmd: @cmd");
|
|
exec("@cmd 2>&1; echo \${PIPESTATUS}") or die "exec failed: $!\n";
|
|
}
|
|
$ExecCmdOut =~ s/$pipestatus\n$//;
|
|
$pipestatus = $? if not $pipestatus;
|
|
dbg("Exec Out: exitstatus=$pipestatus ExecCmdOut: $ExecCmdOut") if $forcelog;
|
|
return $pipestatus, $ExecCmdOut;
|
|
}
|
|
|
|
sub trim($) {
|
|
my $s = shift;
|
|
$s =~ s/^\s+//;
|
|
$s =~ s/\s+$//;
|
|
return $s;
|
|
}
|
|
|
|
sub formatHTime($) {
|
|
my $ts = shift(@_);
|
|
my ( $y, $m, $d, $H, $M ) = $ts =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
|
|
return Date::Format::time2str( "%a %Y-%m-%d %H:%M", ( timelocal( 0, $M, $H, $d, $m - 1, $y ) ) );
|
|
}
|
|
|
|
sub hTime2Timestamp($) {
|
|
my $ts = shift(@_);
|
|
my ( $y, $m, $d, $H, $M ) = $ts =~ /(\d\d\d\d)(\d\d)(\d\d)(\d\d)(\d\d)/;
|
|
return timelocal( 0, $M, $H, $d, $m - 1, $y );
|
|
}
|
|
|
|
sub countUnit($) {
|
|
my $count = shift(@_);
|
|
my $unit = "";
|
|
if ( $count > 9999E9 ) {
|
|
$count = int( ( $count + 0.5E12 ) / 1E12 );
|
|
$unit = "T";
|
|
}
|
|
elsif ( $count > 9999E6 ) {
|
|
$count = int( ( $count + 0.5E9 ) / 1E9 );
|
|
$unit = "G";
|
|
}
|
|
elsif ( $count > 9999E3 ) {
|
|
$count = int( ( $count + 0.5E6 ) / 1E6 );
|
|
$unit = "M";
|
|
}
|
|
elsif ( $count > 9999E0 ) {
|
|
$count = int( ( $count + 0.5E3 ) / 1E3 );
|
|
$unit = "k";
|
|
}
|
|
$count .= "$unit";
|
|
return $count;
|
|
}
|
|
|
|
sub timeUnit($) {
|
|
my $res = '';
|
|
my $t = shift(@_);
|
|
my $days = int( $t / 86400 );
|
|
$t -= $days * 86400;
|
|
my $hours = int( $t / 3600 );
|
|
$t -= $hours * 3600;
|
|
my $minutes = int( $t / 60 );
|
|
my $seconds = $t - $minutes * 60;
|
|
if ($days) {
|
|
$res = sprintf( "%dd%02dh", $days, $hours );
|
|
}
|
|
elsif ($hours) {
|
|
$res = sprintf( "%2dh%02dm", $hours, $minutes );
|
|
}
|
|
else {
|
|
$res = sprintf( "%2dm%02ds", $minutes, $seconds );
|
|
}
|
|
return $res;
|
|
}
|
|
|
|
sub sizeUnit($) {
|
|
my $size = shift(@_);
|
|
my $unit = '';
|
|
if ( $size > 1024**4 ) {
|
|
$size = $size / 1024**4;
|
|
$unit = "T";
|
|
}
|
|
elsif ( $size > 1024**3 ) {
|
|
$size = $size / 1024**3;
|
|
$unit = "G";
|
|
}
|
|
elsif ( $size > 1024**2 ) {
|
|
$size = $size / 1024**2;
|
|
$unit = "M";
|
|
}
|
|
elsif ( $size > 1024 ) {
|
|
$size = $size / 1024;
|
|
$unit = "k";
|
|
}
|
|
if ( $unit && length( sprintf( "%2.1f", $size ) ) <= 3 ) {
|
|
$size = sprintf( "%2.1f", $size );
|
|
}
|
|
else {
|
|
$size = sprintf( "%3.0f", $size );
|
|
}
|
|
return "$size$unit";
|
|
}
|
|
|
|
sub stopServices() {
|
|
lg("Stopping services...");
|
|
|
|
# my @cmd;
|
|
|
|
foreach my $svc (@services) {
|
|
|
|
#reetp stop services
|
|
$systemD->{'serviceName'} = $svc;
|
|
my $Status = ( $systemD->stop() );
|
|
|
|
# @cmd = ( $serviceCommand, $svc, 'stop' );
|
|
# ExecCmd( @cmd, 0 );
|
|
|
|
# print $Affa::lib::ExecCmdOut;
|
|
# print $ExecCmdOut;
|
|
print "Stop $svc $Status\n"; #reetp - status?
|
|
}
|
|
$ServicesStopped = 1;
|
|
}
|
|
|
|
sub startServices() {
|
|
lg("Starting services...");
|
|
|
|
#my @cmd;
|
|
|
|
foreach my $svc (@services) {
|
|
|
|
|
|
$systemD->{'serviceName'} = $svc;
|
|
my $Status = ( $systemD->start() );
|
|
|
|
# @cmd = ( $serviceCommand, $svc, 'start' );
|
|
# ExecCmd( @cmd, 0 );
|
|
|
|
# print $Affa::lib::ExecCmdOut;
|
|
# print $ExecCmdOut;
|
|
print "Start $svc $Status\n"; #reetp - status?
|
|
}
|
|
$ServicesStopped = 0;
|
|
}
|
|
|
|
sub saveMySoul() {
|
|
my $txt = 'Backing up the Affa server base (this server).';
|
|
print "$txt\n";
|
|
lg($txt);
|
|
|
|
####
|
|
#### début copie de sme-affa-conf2ini.perl avec modifs AG
|
|
#### faire attention: $ServerBasename.conf dans /root et /root est écrasé par sub runRiseRsync($$)
|
|
|
|
# File::Path::mkpath( "/root/affa3conf/", 0, 0755 );
|
|
open( CF, ">/etc/affa/$ServerBasename.conf" );
|
|
print CF "[$ServerBasename]\n";
|
|
|
|
# foreach my $k ( sort keys %job )
|
|
# {
|
|
# my $key=$k;
|
|
# if( $k=~/^Include\[/ ){
|
|
# $key="Include";
|
|
# } elsif( $k=~/^Exclude\[/ ){
|
|
# $key="Exclude";
|
|
# } elsif( $k=~/TimeSchedule/ ){
|
|
# my @ts = split(/,/, $job{$k});
|
|
# foreach my $s (sort @ts) {
|
|
# print CF "TimeSchedule=$s\n";
|
|
# }
|
|
# next;
|
|
# } elsif( $k=~/EmailAddresses/ ){
|
|
# my @ts = split(/,/, $job{$k});
|
|
# foreach my $s (sort @ts) {
|
|
# next if $s eq 'admin';
|
|
# print CF "EmailAddress=$s\n";
|
|
# $postprefix="WARNING: EmailAddress=admin skipped!";
|
|
# }
|
|
# next;
|
|
# } elsif( $k eq 'SMEServer' && $job{$k} eq 'yes' ) {
|
|
# print CF "Include=/etc/e-smith/templates-custom\n";
|
|
# print CF "Include=/etc/e-smith/templates-user-custom\n";
|
|
# print CF "Include=/etc/group\n";
|
|
# print CF "Include=/etc/gshadow\n";
|
|
# print CF "Include=/etc/passwd\n";
|
|
# print CF "Include=/etc/samba/secrets.tdb\n";
|
|
# print CF "Include=/etc/samba/smbpasswd\n";
|
|
# print CF "Include=/etc/shadow\n";
|
|
# print CF "Include=/etc/smbpasswd\n";
|
|
# print CF "Include=/etc/ssh\n";
|
|
# print CF "Include=/etc/sudoers\n";
|
|
# print CF "Include=/home/e-smith\n";
|
|
# print CF "Include=/root\n";
|
|
# print CF "preJobCommandRemote=SME/signal-pre-backup\n";
|
|
# print CF "postJobCommandRemote=SME/signal-post-backup\n";
|
|
# } elsif( $k =~ /JobCommand$/ && $job{$k} =~ /^\// ) {
|
|
# $postprefix="WARNING: preJobCommand/postJobCommand needs fix!";
|
|
# $job{$k} =~ s/.*\///;
|
|
# }
|
|
# next if( ! defined $defs{$key} );
|
|
# next if( $defs{$key} eq $job{$k} );
|
|
# print CF "$key=$job{$k}\n" if $job{$k};
|
|
# }
|
|
|
|
print CF "remoteHostName=localhost\n";
|
|
print CF "Description=for internal use only\n";
|
|
print CF "scheduledKeep=1\n";
|
|
print CF "dailyKeep=1\n";
|
|
print CF "weeklyKeep=1\n";
|
|
print CF "monthlyKeep=1\n";
|
|
print CF "yearlyKeep=1\n";
|
|
print CF "SMEServer=yes\n";
|
|
print CF "DiskSpaceWarn=none\n";
|
|
print CF "status=disabled\n";
|
|
print CF "RetryAfter=0\n";
|
|
print CF "RetryAttempts=0\n";
|
|
close(CF);
|
|
|
|
# File::Path::mkpath( "/var/affa/", 0, 0755 );
|
|
# open( DD, ">/var/affa/$ServerBasename/.doneDates.conf");
|
|
# print DD "[doneDates]\n";
|
|
# print DD "daily=$thisDay\n";
|
|
# print DD "weekly=$thisWeek\n";
|
|
# print DD "monthly=$thisMonth\n";
|
|
# print DD "yearly=$thisYear\n";
|
|
# close(DD);
|
|
|
|
# print "/root/affa3conf/$xjob.conf created. $postprefix\n";
|
|
print "/etc/affa/$ServerBasename.conf created.\n";
|
|
|
|
#### fin de la copie de sme-affa-conf2ini.perl et des modifs AG
|
|
####################################################################################
|
|
|
|
# my $asb = $affa->get($ServerBasename);
|
|
# $asb->delete() if $asb;
|
|
# my $me = $affa->new_record($ServerBasename);
|
|
# $me->set_prop('type','job');
|
|
# my %props=(
|
|
# 'remoteHostName'=>'localhost',
|
|
# 'RootDir'=>'/var/affa',
|
|
# 'TimeSchedule'=>'',
|
|
# 'Description'=>'for internal use only',
|
|
# 'scheduledKeep'=>1,
|
|
# 'dailyKeep'=>1,
|
|
# 'weeklyKeep'=>1,
|
|
# 'monthlyKeep'=>1,
|
|
# 'yearlyKeep'=>1,
|
|
# 'SMEServer'=>'yes',
|
|
# 'RPMCheck'=>'no',
|
|
# 'DiskSpaceWarn'=>'none',
|
|
# 'localNice'=>'+19',
|
|
# 'remoteNice'=>0,
|
|
# 'Watchdog'=>'no',
|
|
# 'ConnectionCheckTimeout'=>120,
|
|
# 'rsyncTimeout'=>900,
|
|
# 'rsyncCompress'=>'yes',
|
|
# 'EmailAddresses'=>'admin',
|
|
# 'postJobCommand'=>'',
|
|
# 'preJobCommand'=>'',
|
|
# 'doneDaily'=>$thisDay,
|
|
# 'doneMonthly'=>$thisMonth,
|
|
# 'doneWeekly'=>$thisWeek,
|
|
# 'doneYearly'=>$thisYear,
|
|
# 'status'=>'disabled',
|
|
# 'Debug'=>'no',
|
|
# );
|
|
# while( (my $p, my $v) = each %props )
|
|
# {
|
|
# $me->set_prop($p,$v);
|
|
# }
|
|
|
|
####################################################################################
|
|
##### début modifs
|
|
%job = getJobConfig($ServerBasename);
|
|
#### fin modifs
|
|
####################################################################################
|
|
|
|
if ( not -d $job{'RootDir'} ) {
|
|
my $input = '';
|
|
while ( not $input =~ /^(yes|no)$/ ) {
|
|
print "Directory $job{'RootDir'} does not exist. Create? (yes|no) ";
|
|
$input = lc(<STDIN>);
|
|
chomp($input);
|
|
}
|
|
if ( $input ne 'yes' ) {
|
|
$interactive = 0;
|
|
affaErrorExit("Terminated by user");
|
|
}
|
|
File::Path::mkpath( $job{'RootDir'}, 0, 0700 );
|
|
}
|
|
|
|
#### ligne d'origine de AffaV2: my @cmd=('/sbin/e-smith/affa','--run', $ServerBasename);
|
|
my @cmd = ( '/usr/sbin/affa', '--run', $ServerBasename );
|
|
not ExecCmd( @cmd, 0 ) or affaErrorExit("Couldn't backup myself");
|
|
print "Done.\n";
|
|
}
|
|
|
|
sub imapIndexFilesDeleteCommand() {
|
|
return
|
|
'USERS=`/usr/bin/find /home/e-smith/files/users -maxdepth 1 -type d`;USERS="/home/e-smith/ $USERS"; for u in $USERS ; do ! /usr/bin/test -d $u/Maildir && continue; /usr/bin/find $u/Maildir -maxdepth 2 -type f -name ".imap.index*" -exec /bin/rm -f \'{}\' \; ; /usr/bin/find $u/Maildir -maxdepth 2 -type f -name "dovecot.index*" -exec /bin/rm -f \'{}\' \; ; done';
|
|
}
|
|
|
|
sub imapIndexFilesDelete() {
|
|
|
|
# Sometimes Dovecot index files are corrupted after a restore
|
|
# It is save to delete them all. Dovecot will rebuild them when the mailbox is accessed.
|
|
print "Deleting Dovecot's index files\n";
|
|
my @cmd = ( imapIndexFilesDeleteCommand() );
|
|
ExecCmd( @cmd, 0 );
|
|
}
|
|
|
|
##### fin des modifs
|
|
####################################################################################
|
|
|
|
END {
|
|
unlink($configfile) if $configfile;
|
|
}
|