summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorFlorian Pritz <bluewind@xinu.at>2016-08-13 00:14:29 +0200
committerFlorian Pritz <bluewind@xinu.at>2016-08-13 00:14:29 +0200
commit44578ace80a974631b4becddba997affac625765 (patch)
tree95364fd3cc8b4839a0115aae5fd6ffe9ddc238e7
parent41e3fbe815a074a3d55c9c46564a8f0c642c3d38 (diff)
downloadbin-44578ace80a974631b4becddba997affac625765.tar.gz
bin-44578ace80a974631b4becddba997affac625765.tar.xz
borg-restore.pl: WIP
Signed-off-by: Florian Pritz <bluewind@xinu.at>
-rwxr-xr-xborg-restore.pl135
1 files changed, 116 insertions, 19 deletions
diff --git a/borg-restore.pl b/borg-restore.pl
index 0a50578..95bb75d 100755
--- a/borg-restore.pl
+++ b/borg-restore.pl
@@ -10,20 +10,33 @@ borg-restore.pl - Restore paths from borg backups
Script that helps to restore files from borg backups.
-Takes one or more paths, looks for their backups, shows a list of distinct
+Takes one path, looks for its backups, shows a list of distinct
versions and allows to select one to be restored.
+Also allows to specify a time for automatic selection of the backup that has to
+be restored. If a time is specified, the script will automatically select the
+newest backup that is at least as old as the time value that is passed.
+
+Backups are restored to their original destination if that destination does not
+exist. If restoration should be attempted to an existing destination, the
+destination has to be passed as an argument.
+
=cut
=head1 SYNOPSIS
-borg-restore.pl [options] <path>
+borg-restore.pl [options] <path> [[<time spec>] <destination>]
Options:
--help, -h short help message
--debug show debug messages
--update-cache, -u update cache files
+ Time spec:
+ Select the newest backup that is at least <time spec> old.
+ Format: <number><unit>
+ Units: s (seconds), min (minutes), h (hours), d (days), m (months = 31 days)
+
=cut
use v5.10;
@@ -95,10 +108,9 @@ sub find_archives {
my %seen_modtime;
my @ret;
- #debug(sprintf("Found %d archive(s) with the following times for %s", @$modtimes+0, $path));
debug("All modification times from the database:\n", Dumper($modtimes));
- debug("Building archive list");
+ debug("Building unique archive list");
for my $archive (@$archives) {
my $modtime = $$modtimes[get_archive_index($archive, $archives)];
@@ -120,7 +132,7 @@ sub find_archives {
return \@ret;
}
-sub select_archive {
+sub user_select_archive {
my $archives = shift;
my $selected_archive;
@@ -145,6 +157,71 @@ sub select_archive {
return ${$archives}[$selection];
}
+sub select_archive_timespec {
+ my $archives = shift;
+ my $timespec = shift;
+
+ my $seconds = timespec_to_seconds($timespec);
+ if (!defined($seconds)) {
+ say STDERR "Error: Invalid time specification";
+ return undef;
+ }
+
+ my $target_timestamp = time - $seconds;
+
+ debug("Searching for newest archive that contains a copy before ", format_timestamp($target_timestamp));
+
+ for my $archive (reverse @$archives) {
+ if ($archive->{modification_time} < $target_timestamp) {
+ return $archive;
+ }
+ }
+
+ return undef;
+}
+
+sub format_timestamp {
+ my $timestamp = shift;
+
+ my $dt = DateTime->from_epoch(epoch => $timestamp);
+ return $dt->strftime("%a. %F %H:%M:%S");
+}
+
+sub timespec_to_seconds {
+ my $timespec = shift;
+
+ if ($timespec =~ m/^(?<value>[0-9.]+)(?<unit>.+)$/) {
+ my $value = $+{value};
+ my $unit = $+{unit};
+
+ my %factors = (
+ s => 1,
+ second => 1,
+ seconds => 1,
+ minute => 60,
+ minutes => 60,
+ h => 60*60,
+ hour => 60*60,
+ hours => 60*60,
+ d => 60*60*24,
+ day => 60*60*24,
+ days => 60*60*24,
+ m => 60*60*24*31,
+ month => 60*60*24*31,
+ months => 60*60*24*31,
+ y => 60*60*24*365,
+ year => 60*60*24*365,
+ years => 60*60*24*365,
+ );
+
+ if (exists($factors{$unit})) {
+ return $value * $factors{$unit};
+ }
+ }
+
+ return undef;
+}
+
sub restore {
my $path = shift;
my $archive = shift;
@@ -490,6 +567,21 @@ sub main {
my @paths = @ARGV;
+ my $path;
+ my $timespec;
+ my $destination;
+
+ if (@ARGV == 1) {
+ $path = $ARGV[0];
+ } elsif (@ARGV == 2) {
+ $path = $ARGV[0];
+ $destination = $ARGV[1];
+ } elsif (@ARGV == 3) {
+ $path = $ARGV[0];
+ $timespec = $ARGV[1];
+ $destination = $ARGV[2];
+ }
+
# TODO only accept one source path
# <source path> [time spec] <destination path>
# handle destination path similar to rsync
@@ -498,27 +590,32 @@ sub main {
#
# time spec: take most recent backup that is at least $timespec old
- #if (@paths > 1) {
- #say STDERR "ERROR: more than one path is currently not supported";
- #exit 1;
- #}
+ my $abs_path = abs_path($path);
+ if (!defined($destination)) {
+ $destination = $abs_path;
+ }
+ my $backup_path = $abs_path;
+ $backup_path =~ s/^\Q$backup_prefix\E//;
- for my $path (@paths) {
- my $abs_path = abs_path($path);
- my $destination = $abs_path;
- my $backup_path = $abs_path;
- $backup_path =~ s/^\Q$backup_prefix\E//;
+ debug("Asked to restore $backup_path to $destination");
- debug( "Asked to restore $backup_path to $destination");
+ my $archives = find_archives($backup_path);
- my $archives = find_archives($backup_path);
+ my $selected_archive;
- my $selected_archive = select_archive($archives);
- next if not defined($selected_archive);
+ if (defined($timespec)) {
+ $selected_archive = select_archive_timespec($archives, $timespec);
+ } else {
+ $selected_archive = user_select_archive($archives);
+ }
- restore($backup_path, $selected_archive, $destination);
+ if (!defined($selected_archive)) {
+ say STDERR "Error: No archive selected or selection invalid";
+ return 1;
}
+ restore($backup_path, $selected_archive, $destination);
+
return 0;
}