290 lines
11 KiB
Perl
Executable File
290 lines
11 KiB
Perl
Executable File
#!/usr/bin/perl
|
|
################################################################################ rt - restic wrapper (mwx'2026)
|
|
use File::Basename;use Term::ReadKey;use Getopt::Std;getopts('c:n:i:t:p:');
|
|
|
|
$VERSION='1.5.6';
|
|
|
|
$CONFIG='default';$CONFIG=$opt_c if ($opt_c!~/^\s*$/); # set deault options options
|
|
chomp($host=`hostname`);$host=~s/^([^\.]+).*/$1/;
|
|
$NAME=$host;
|
|
$POLICY='--keep-daily 14 --keep-weekly 12 --keep-monthly 12 --keep-yearly 100';
|
|
|
|
for ('/db/etc/rt.conf',$ENV{"HOME"}.'/.rt.conf') { $CONF=$_ if (-f $_);} # find/load/check configuration
|
|
die "Fatal: no config file found\n" if ($CONF=~/^\s*$/);
|
|
|
|
require($CONF);
|
|
|
|
$NAME = $opt_n if ($opt_n!~/^\s*$/); # overwrite options with commandline (name, id, tag, password)
|
|
$ID = $opt_i if ($opt_i!~/^\s*$/);
|
|
$TAG = $opt_t if ($opt_t!~/^\s*$/);
|
|
$PASSWORD = $opt_p if ($opt_p!~/^\s*$/);
|
|
|
|
for ('$NAME','$TARGET','$SRC','$RESTIC','$LOG') {
|
|
die "Fatal: missing $_ in config\n" if (eval($_)=~/^\s*$/);
|
|
}
|
|
|
|
$ID='0' if ($ID=~/^\s*$/); # set repository
|
|
$REPOSITORY="$TARGET/$NAME.$ID";
|
|
|
|
|
|
print "rt $VERSION, Name: $NAME, Repository: $REPOSITORY, Config: $CONFIG\n";
|
|
|
|
|
|
open(PW,"> $PWFILE");print PW "$PASSWORD\n";close(PW);
|
|
|
|
$ENV{'RESTIC_REPOSITORY'} = $REPOSITORY; # set environment
|
|
$ENV{'RESTIC_PASSWORD_FILE'} = $PWFILE;
|
|
|
|
$r=`$RESTIC version`; # find restic version
|
|
if ($r=~/restic\s+([\d\.]+)\s+compiled/) {
|
|
$RVERSION=$1;
|
|
@RV=split('\.',$1);$RVN=sprintf("%02d%02d%02d",$RV[0],$RV[1],$RV[2]);
|
|
}
|
|
|
|
$TMP="/tmp";
|
|
|
|
my(@z)=localtime(time());$z[4]+=1;$z[5]-=100;$z[6]+=1;
|
|
$z=sprintf("%02d\.%02d\.%02d %02d\:%02d",$z[3],$z[4],$z[5],$z[2],$z[1]);
|
|
|
|
|
|
if ($ARGV[0] eq 'cleanup') { # ----------------------------------------------------------- forget, prune, check
|
|
|
|
$ts=time();
|
|
|
|
print "$RESTIC -r $REPOSITORY forget $POLICY --prune\n"; # forget & prune
|
|
|
|
$rms=0;
|
|
open(IN,"$RESTIC forget $POLICY --prune |");
|
|
while(<IN>) {
|
|
print;
|
|
$rms=$1 if (/^(\d+)\s+snapshots\s+have\s+been\s+removed/)
|
|
}
|
|
|
|
print "$RESTIC -r $REPOSITORY check\n"; # check
|
|
|
|
open(IN,"$RESTIC check |");
|
|
while(<IN>) {
|
|
print;
|
|
chomp($lastline=$_);
|
|
}
|
|
|
|
$rt=time()-$ts;
|
|
|
|
if ($HTTP_LOG!~/^\s*$/) { # http loging
|
|
$tmp= "RTC,$VERSION/$RVERSION,$NAME,$ID,$CONFIG,$TAG,$REPOSITORY,$lastline,$rms,$rt";
|
|
use LWP::UserAgent;
|
|
my $ua = new LWP::UserAgent();
|
|
$response = $ua->post("https://$HTTP_LOG", Content => $tmp);
|
|
}
|
|
|
|
if ($MY_HOST!~/^\s*$/) { # mysql loging
|
|
use DBI;
|
|
$dbh=DBI->connect("DBI:mysql:$MY_DB:$MY_HOST:$MY_PORT",$MY_USER,$MY_PASSWD,{RaiseError=>1,PrintError=>1});
|
|
$sql="INSERT INTO resticcleanup VALUES(now(),'$VERSION/$RVERSION',
|
|
'$NAME','$ID','$CONFIG','$TAG','$REPOSITORY','$lastline',$rms,$rt)";
|
|
$dbh->do($sql);
|
|
}
|
|
|
|
} elsif ($ARGV[0] eq 'backup' || $ARGV[0] eq 'delaybackup') { # ----------------------------------- run backup
|
|
$snap='';$added='';$total='';
|
|
|
|
if ($ARGV[0] eq 'delaybackup') {
|
|
printf "$z --- delaybackup (%d sec) ---\n",&dfn($NAME);
|
|
sleep(&dfn($NAME))
|
|
} else {
|
|
printf "$z --- backup ---\n";
|
|
}
|
|
|
|
print "$RESTIC -r $REPOSITORY -vv backup $EXCLUDE $SRC\n";
|
|
|
|
$ts=time();
|
|
open(IN,"$RESTIC -vv backup $EXCLUDE $SRC |");
|
|
while(<IN>) {
|
|
print if (!/^unchanged/);
|
|
|
|
if (/Files:\s*(\d+)\s*new,\s*(\d+)\s*changed,\s*(\d+)\s*unmodified/) {
|
|
$fn+=$1;$fc+=$2;$fu+=$3;
|
|
}
|
|
if (/Dirs:\s*(\d+)\s*new,\s*(\d+)\s*changed,\s*(\d+)\s*unmodified/) {
|
|
$fn+=$1;$fc+=$2;$fu+=$3;
|
|
}
|
|
$snap=$1 if (/snapshot\s+([^\s]+)\s+saved/);
|
|
$added=&deform($1) if (/Added\s+to\s+the\s+repo:\s+(.*)/);
|
|
$added=&deform($1) if (/Added\s+to\s+the\s+repository:\s+([^\(]*)/);
|
|
|
|
$total=&deform($1) if (/processed\s+\d+\s+files,\s+(.*)\s+in/);
|
|
$files=$1 if (/processed\s+(\d+)\s+files,\s+.*\s+in/);
|
|
}
|
|
|
|
if ($snap!~/^\s*$/ && $added!~/^\s*$/ && $total!~/^\s*$/) {
|
|
$rt=time()-$ts;
|
|
|
|
open(LOG,">> $LOG"); # write logfile
|
|
print LOG "$z [$NAME/$snap], Files: $files, New: $fn, Changed: $fc, ".
|
|
"Unmodified: $fu, Added: $added, Total: $total, Runtime: $rt\n";
|
|
close(LOG);
|
|
|
|
if ($HTTP_LOG!~/^\s*$/) { # http loging
|
|
$tmp= "RT,$VERSION/$RVERSION,$NAME,$ID,$CONFIG,$TAG,$REPOSITORY,".
|
|
"$snap,$files,$fn,$fc,$fu,$added,$total,$rt";
|
|
use LWP::UserAgent;
|
|
my $ua = new LWP::UserAgent();
|
|
$response = $ua->post("https://$HTTP_LOG", Content => $tmp);
|
|
}
|
|
|
|
if ($MY_HOST!~/^\s*$/) { # mysql loging
|
|
use DBI;
|
|
$dbh=DBI->connect("DBI:mysql:$MY_DB:$MY_HOST:$MY_PORT",$MY_USER,$MY_PASSWD,{RaiseError=>1,PrintError=>1});
|
|
$sql="INSERT INTO restic VALUES(now(),'$VERSION/$RVERSION',
|
|
'$NAME','$ID','$CONFIG','$TAG','$REPOSITORY','$snap',$files,$fn,$fc,$fu,$added,$total,$rt)";
|
|
$dbh->do($sql);
|
|
}
|
|
}
|
|
|
|
|
|
} elsif ($ARGV[0] eq 'password') { # ----------------------------------------------- setup restic password file
|
|
system("mkdir -p ".&dirname($PWFILE)) if (!-d &dirname($PWFILE));
|
|
|
|
print "new repository password: ";
|
|
chomp($pwd = &ReadLine(0));
|
|
|
|
if ($pwd!~/^\s*$/) {
|
|
open(OUT,"> $PWFILE");print OUT "$pwd\n";close(OUT);
|
|
}
|
|
|
|
|
|
} elsif ($ARGV[0] eq 'help' || $ARGV[0]=~/^\s*$/) { # ----------------------------------------------- show help
|
|
|
|
&help();
|
|
&advhelp() if ($ARGV[0] eq 'help');
|
|
|
|
|
|
} else { # ------------------------------------------------------------------------------ other restic commands
|
|
|
|
system("$RESTIC @ARGV");
|
|
|
|
if ($ARGV[0] eq 'init') {
|
|
system("rm -f /var/log/restic.log /var/log/rt.log");
|
|
}
|
|
|
|
}
|
|
|
|
####################################################################################################### SUPPORT
|
|
|
|
sub deform() { # transform sizes
|
|
my($s)=@_;
|
|
return $1<<40 if ($s=~/([\d\.]+)\s*TiB/i);
|
|
return $1<<30 if ($s=~/([\d\.]+)\s*GiB/i);
|
|
return $1<<20 if ($s=~/([\d\.]+)\s*MiB/i);
|
|
return $1<<10 if ($s=~/([\d\.]+)\s*KiB/i);
|
|
return $1 if ($s=~/([\d\.]+)\s*B/i);
|
|
}
|
|
|
|
sub dfn() { # calculate delay from string
|
|
my($s)=@_;my $v=0;my $n=1;
|
|
$v+=ord($_)*$n++ for (split('',$s));
|
|
return ($v%24)*5*60;
|
|
}
|
|
|
|
sub help() { # help
|
|
print "\nrt v$VERSION - restic wrapper (mwx'2022)\n";
|
|
print <<'ENDOFHELP';
|
|
|
|
USAGE:
|
|
------
|
|
rt [options] backup run restic backup
|
|
rt [options] delaybackup run delayed restic backup
|
|
rt [options] cleanup run forget, prune and check
|
|
rt [options] password setup restic password file
|
|
rt [options] init initialize restic repository
|
|
rt help advanced help
|
|
|
|
rt <command> other commands will be forwarded directly to restic
|
|
|
|
options: -c <config> sets $CONFIG (default is 'default')
|
|
-n <name> overwrite $NAME
|
|
-i <id> overwrite $ID
|
|
-t <tag> overwrite $TAG
|
|
-p <password> overwrite $PASSWORD
|
|
|
|
ENDOFHELP
|
|
}
|
|
|
|
sub advhelp() { # advanced help
|
|
print <<'ENDOFHELP';
|
|
CONFIG FILE (/db/etc/rt.conf)
|
|
-----------------------------
|
|
$NAME = 'NAME'; # backup name
|
|
$ID = '0'; # repository id (optional, default is 0)
|
|
$TAG = 'TAG'; # custom tag for logging (optional)
|
|
|
|
$TARGET = "/backup/restic"; # restic target
|
|
= "s3:http://188.68.34.116:9234/restic";
|
|
= "b2:mwx002:restic/server";
|
|
|
|
$SRC = '/etc /db /var /root /home /usr/local /opt'; # source paths
|
|
|
|
$EXCLUDE = '-e /var/lib/lxcfs -e /var/log/journal'; # exclude options
|
|
|
|
$RESTIC = '/db/bin/restic'; # restic binary
|
|
$LOG = '/var/log/restic.log'; # log file
|
|
$PWFILE = '/db/etc/restic.pw'; # restic password file
|
|
|
|
$HTTP_LOG = 'HOST'; # host for logging via https
|
|
|
|
$MY_HOST = 'HOST'; # mysql host
|
|
$MY_PORT = 'PORT'; # mysql port
|
|
$MY_USER = 'USER'; # mysql user
|
|
$MY_PASSWD = 'PASSWORD'; # mysql password
|
|
$MY_DB = 'DATABASE'; # mysql database
|
|
|
|
|
|
$ENV{'AWS_ACCESS_KEY_ID'} = '123456'; # S3 token example
|
|
$ENV{'AWS_SECRET_ACCESS_KEY'} = 'abcdefg';
|
|
|
|
$ENV{'B2_ACCOUNT_ID'} = '123456'; # B2 token example
|
|
$ENV{'B2_ACCOUNT_KEY'} = 'abcdefg';
|
|
|
|
MYSQL LOGGING TABLES:
|
|
---------------------
|
|
CREATE TABLE `restic` (
|
|
`ts` timestamp NULL DEFAULT NULL,
|
|
`version` varchar(255) DEFAULT NULL,
|
|
`name` varchar(255) DEFAULT NULL,
|
|
`id` int(11) DEFAULT NULL,
|
|
`config` varchar(255) DEFAULT NULL,
|
|
`tag` varchar(255) DEFAULT NULL,
|
|
`repository` varchar(255) DEFAULT NULL,
|
|
`snapshot` varchar(255) DEFAULT NULL,
|
|
`files` bigint(11) DEFAULT NULL,
|
|
`new` bigint(11) DEFAULT NULL,
|
|
`changed` bigint(11) DEFAULT NULL,
|
|
`unmodified` bigint(11) DEFAULT NULL,
|
|
`added` bigint(11) DEFAULT NULL,
|
|
`total` bigint(11) DEFAULT NULL,
|
|
`runtime` int(11) DEFAULT NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
|
|
CREATE TABLE `resticcleanup` (
|
|
`ts` timestamp NULL DEFAULT NULL,
|
|
`version` varchar(255) DEFAULT NULL,
|
|
`name` varchar(255) DEFAULT NULL,
|
|
`id` int(11) DEFAULT NULL,
|
|
`config` varchar(255) DEFAULT NULL,
|
|
`tag` varchar(255) DEFAULT NULL,
|
|
`repository` varchar(255) DEFAULT NULL,
|
|
`txt` varchar(255) DEFAULT NULL,
|
|
`rmsnap` int(11) DEFAULT NULL,
|
|
`runtime` int(11) DEFAULT NULL
|
|
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
|
|
|
|
CRONTAB ENTRIES:
|
|
---------------
|
|
0 2 * * * /db/bin/rt delaybackup >> /var/log/rt.log 2>&1
|
|
0 7 * * 6 /db/bin/rt cleanup >> /var/log/rt.log 2>&1
|
|
|
|
ENDOFHELP
|
|
}
|
|
|
|
########################################################################################################### END
|