diff options
author | Florian Pritz <bluewind@xinu.at> | 2016-08-13 00:14:29 +0200 |
---|---|---|
committer | Florian Pritz <bluewind@xinu.at> | 2016-08-13 00:14:29 +0200 |
commit | 44578ace80a974631b4becddba997affac625765 (patch) | |
tree | 95364fd3cc8b4839a0115aae5fd6ffe9ddc238e7 /borg-restore.pl | |
parent | 41e3fbe815a074a3d55c9c46564a8f0c642c3d38 (diff) | |
download | bin-44578ace80a974631b4becddba997affac625765.tar.gz bin-44578ace80a974631b4becddba997affac625765.tar.xz |
borg-restore.pl: WIP
Signed-off-by: Florian Pritz <bluewind@xinu.at>
Diffstat (limited to 'borg-restore.pl')
-rwxr-xr-x | borg-restore.pl | 135 |
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; } |