Files
2026-08-10 16:12:09 +02:00

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