App-BorgRestore
view release on metacpan
or search on metacpan
Changes
view on Meta::CPAN
45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | - Add --json option
- Add --detail option
- Log cache update status message per archive to INFO
- Add --quiet option
- Remove sqlite cache size warning
3.0.0 2018-03-23T14:57:52Z
- No longer automatically enable --adhoc when cache is empty
2.3.0 2018-02-06T15:58:36Z
- Add --list option to search for paths occuring in backups
- Warn if sqlite's memory cache is is filled during cache updates
- Improve documentation of @backup_prefixes setting
2.2.0 2017-11-25T23:16:04Z
- Add borg 1.1 support
- Mention required positive return code of config in documentation
- Enable adhoc mode automatically when cache is empty
2.1.1 2017-10-05T07:58:12Z
- Fix incorrect/missing dependencies
- Use autodie everywhere to catch errors early
- Add basic documentation to internal packages
|
META.json
view on Meta::CPAN
1 2 3 4 5 6 7 8 9 10 11 12 | {
"abstract" : "Restore paths from borg backups" ,
"author" : [
"Florian Pritz <bluewind@xinu.at>"
],
"dynamic_config" : 0,
"generated_by" : "Minilla/v3.1.22" ,
"license" : [
"gpl_3"
],
"meta-spec" : {
|
META.yml
view on Meta::CPAN
1 2 3 4 5 6 7 8 9 10 11 12 | ---
abstract: 'Restore paths from borg backups'
author:
- 'Florian Pritz <bluewind@xinu.at>'
build_requires:
Log::Any::Adapter::TAP: '0'
Software::License::GPL_3: '0'
Test::Differences: '0'
Test::Exception: '0'
Test::MockObject: '0'
Test::More: '0.98'
Test::Pod: '0'
|
README.md
view on Meta::CPAN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | borg-restore.pl - Restore paths from borg backups
borg-restore.pl \[options\] < ;path>
Options:
--help, -h short help message
--debug show debug messages
--quiet show only warnings and errors
--detail Output additional detail for some operations
(currently only --list)
--json Output JSON instead of human readable text
(currently only --list)
--update-cache, -u update cache files
--list [pattern] List paths contained in the backups, optionally
matching an SQLite LIKE pattern
--destination, -d <path> Restore backup to directory <path>
-- time , -t <timespec> Automatically find newest backup that is at least
< time spec> old
--adhoc Do not use the cache, instead provide an unfiltered list of archive to choose from
--version display the version of the program
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), y (year)
> borg-restore.pl bin/backup.sh
0: Sat. 2016-04-16 17:47:48 +0200 backup-20160430-232909
1: Mon. 2016-08-15 16:11:29 +0200 backup-20160830-225145
2: Mon. 2017-02-20 16:01:04 +0100 backup-20170226-145909
3: Sat. 2017-03-25 14:45:29 +0100 backup-20170325-232957
Enter ID to restore (Enter to skip): 3
INFO Restoring home/flo/bin/backup.sh to /home/flo/bin from archive backup-20170325-232957
borg-restore.pl helps to restore files from borg backups.
It takes one path, looks for its backups, shows a list of distinct versions and
allows to select one to be restored. Versions are based on the modification
time of the file.
It is also possible 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 and restore it without further user interaction.
* *borg -restore.pl --update-cache** has to be executed regularly, ideally after
creating or removing backups.
It can be used to build your own restoration script.
- **--help**, **-h**
Show help message.
|
README.md
view on Meta::CPAN
77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 | Output additional detail information with some operations. Refer to the
specific options for more information. Currently only works with **--list**
- **--json**
Output JSON instead of human readable text with some operations. Refer to the
specific options for more information. Currently only works with **--list**
- **--update-cache**, **-u**
Update the lookup database. You should run this after creating or removing a backup.
- **--list** **\[pattern\]**
List paths contained in the backups, optionally matching an SQLite LIKE
pattern. If no % occurs in the pattern, the patterns is automatically wrapped
between two % so it may match anywhere in the path.
If **--detail** is used, also outputs which archives contain a version of the
file. If the same version is part of multiple archives, only one archive is
shown.
If **--json** is used, the output is JSON. Can also be combined with **--detail**.
- **--destination=* *_path_ , **-d * *_path_
Restore the backup to 'path' instead of its original location. The destination
either has to be a directory or missing in which case it will be created. The
backup will then be restored into the directory with its original file or
directory name.
- **-- time =* *_timespec_ , **-t * *_timespec_
Automatically find the newest backup that is at least as old as _timespec_
specifies. _timespec_ is a string of the form "<_number_><_unit_>" with _unit_ being one of the following:
s (seconds), min (minutes), h (hours), d (days), m (months = 31 days), y (year). Example: 5.5d
- **--adhoc**
Disable usage of the database. In this mode, the list of archives is fetched
directly from borg at run time . Use this when the cache has not been created
yet and you want to restore a file without having to manually call borg
extract. Using this option will show all archives that borg knows about, even
if they do not contain the file that shall be restored.
|
lib/App/BorgRestore.pm
view on Meta::CPAN
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 | |
lib/App/BorgRestore.pm
view on Meta::CPAN
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 | method new( $class : $deps = {}) {
$deps ->{settings} //= App::BorgRestore::Settings->new();
my $config = $deps ->{settings}->get_config();
$deps ->{borg} //= App::BorgRestore::Borg->new(@{ $config ->{borg}}{ qw(repo backup_prefix) });
$deps ->{db} //= App::BorgRestore::DB->new( $config ->{cache}->{database_path}, $config ->{cache}->{sqlite_memory_cache_size});
return $class ->new_no_defaults( $deps , $config );
}
|
lib/App/BorgRestore.pm
view on Meta::CPAN
132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 | if (! defined ( $abs_path )) {
$log ->errorf( "Failed to resolve path to absolute path: %s: %s" , $canon_path , $!);
$log ->error( "Make sure that all parts of the path, except the last one, exist." );
die "Path resolving failed\n" ;
}
return $abs_path ;
}
method map_path_to_backup_path( $abs_path ) {
my $backup_path = $abs_path ;
for my $backup_prefix (@{ $self ->{config}->{borg}->{path_prefixes}}) {
if ( $backup_path =~ m/ $backup_prefix ->{regex}/) {
$backup_path =~ s/ $backup_prefix ->{regex}/ $backup_prefix ->{replacement}/;
last ;
}
}
return $backup_path ;
}
|
lib/App/BorgRestore.pm
view on Meta::CPAN
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 | if ( exists ( $factors { $unit })) {
return $value * $factors { $unit };
}
}
return ;
}
method restore( $path , $archive , $destination ) {
$destination = untaint( $destination , qr(.*) );
$path = untaint( $path , qr(.*) );
|
lib/App/BorgRestore.pm
view on Meta::CPAN
358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 | C< $destination > is not specified, it is set to the parent directory of C< $path >
so that C< $path > is restored to its original place.
Refer to L</ "select_archive_timespec" > for an explanation of the C< $timespec >
variable.
|
lib/App/BorgRestore/Borg.pm
view on Meta::CPAN
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | method new( $class : $borg_repo , $backup_prefix ) {
my $self = {};
bless $self , $class ;
$self ->{borg_repo} = $borg_repo ;
$self ->{backup_prefix} = $backup_prefix ;
$self ->{borg_version} = $self ->borg_version();
return $self ;
}
|
lib/App/BorgRestore/Borg.pm
view on Meta::CPAN
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 | run [ qw(borg --version) ], ">" , \ my $output or die $log ->error( "Failed to determined borg version" ). "\n" ;
if ( $output =~ m/^.* ([0-9.a-z]+)$/) {
return $1;
}
die $log ->error( "Unable to extract borg version from borg --version output" ). "\n" ;
}
method borg_list() {
my @archives ;
my $backup_prefix = $self ->{backup_prefix};
if (Version::Compare::version_compare( $self ->{borg_version}, "1.2" ) >= 0) {
$log ->debug( "Getting archive list via json" );
run [ qw(borg list --glob-archives) , "$backup_prefix*" , qw(--json) , $self ->{borg_repo}], '>' , \ my $output or die $log ->error( "borg list returned $?" ). "\n" ;
my $json = decode_json( $output );
for my $archive (@{ $json ->{archives}}) {
push @archives , $archive ->{archive};
}
} elsif (Version::Compare::version_compare( $self ->{borg_version}, "1.1" ) >= 0) {
$log ->debug( "Getting archive list via json" );
run [ qw(borg list --prefix) , $backup_prefix , qw(--json) , $self ->{borg_repo}], '>' , \ my $output or die $log ->error( "borg list returned $?" ). "\n" ;
my $json = decode_json( $output );
for my $archive (@{ $json ->{archives}}) {
push @archives , $archive ->{archive};
}
} else {
$log ->debug( "Getting archive list" );
run [ qw(borg list --prefix) , $backup_prefix , $self ->{borg_repo}], '>' , \ my $output or die $log ->error( "borg list returned $?" ). "\n" ;
for ( split /^/, $output ) {
if (m/^([^\s]+)\s/) {
push @archives , $1;
}
}
}
$log ->warning( "No archives detected in borg output. Either you have no backups or this is a bug" ) if @archives == 0;
return \ @archives ;
}
method borg_list_time() {
my @archives ;
if (Version::Compare::version_compare( $self ->{borg_version}, "1.1" ) >= 0) {
$log ->debug( "Getting archive list via json" );
run [ qw(borg list --json) , $self ->{borg_repo}], '>' , \ my $output or die $log ->error( "borg list returned $?" ). "\n" ;
|
lib/App/BorgRestore/Borg.pm
view on Meta::CPAN
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 | if ( $time ) {
push @archives , {
"archive" => $1,
"modification_time" => $time ,
};
}
}
}
}
$log ->warning( "No archives detected in borg output. Either you have no backups or this is a bug" ) if @archives == 0;
return \ @archives ;
}
method restore( $components_to_strip , $archive_name , $path ) {
$log ->debugf( "Restoring '%s' from archive %s, stripping %d components of the path" , $path , $archive_name , $components_to_strip );
$archive_name = untaint( $archive_name , qr(.*) );
system ( qw(borg extract -v --strip-components) , $components_to_strip , $self ->{borg_repo}. "::" . $archive_name , $path );
}
|
lib/App/BorgRestore/Settings.pm
view on Meta::CPAN
38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 | Also note that it is important that the last statement of the file is positive
because it is used to check that running the config went well. You can simply
use "1;" on the last line as shown in the example config.
|
lib/App/BorgRestore/Settings.pm
view on Meta::CPAN
98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 | multiple time , thus writing directly to the database is slower, but preparing
the data in memory may require a substaintial amount of memory. New in version 3.2.0. Deprecated in v3.2.0 for future removal possibly in v4.0.0.
|
lib/App/BorgRestore/Settings.pm
view on Meta::CPAN
124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | Licensed under the GNU General Public License version 3 or later.
See LICENSE for the full license text.
|
lib/App/BorgRestore/Settings.pm
view on Meta::CPAN
160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 | $cache_path_base = untaint( $cache_path_base , qr/.*/ );
return $self ;
}
method get_config() {
return {
borg => {
repo => $borg_repo ,
backup_prefix => $backup_prefix ,
path_prefixes => [ @backup_prefixes ],
},
cache => {
base_path => $cache_path_base ,
database_path => "$cache_path_base/v3/archives.db" ,
prepare_data_in_memory => $prepare_data_in_memory ,
sqlite_memory_cache_size => $sqlite_cache_size ,
}
};
}
|
script/borg-restore.pl
view on Meta::CPAN
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | |
script/borg-restore.pl
view on Meta::CPAN
84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 | Output additional detail information with some operations. Refer to the
specific options for more information. Currently only works with B<--list>
|
script/borg-restore.pl
view on Meta::CPAN
305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 | if ( $opts {json}) {
print encode_json( $json_data );
}
return 0;
}
if (! $app ->cache_contains_data() && ! $opts {adhoc}) {
$log ->error( "Cache is empty. Either the cache path is incorrect or you did not run --update yet." );
$log ->error( "If you did not create a cache yet, you may want to rerun with --adhoc to simply list all backups." );
return 1;
}
my @paths = @ARGV ;
my $path ;
my $timespec ;
my $destination ;
my $archives ;
|
script/borg-restore.pl
view on Meta::CPAN
333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 | $timespec = $opts { time };
}
if ( @ARGV > 1) {
die "Too many arguments" ;
}
my $abs_path = $app ->resolve_relative_path( $path );
$destination = dirname( $abs_path ) unless defined ( $destination );
my $backup_path = $app ->map_path_to_backup_path( $abs_path );
$log ->debug( "Asked to restore $backup_path to $destination" );
if ( $opts {adhoc}) {
$archives = $app ->get_all_archives();
} else {
$archives = $app ->find_archives( $backup_path );
}
my $selected_archive ;
if ( defined ( $timespec )) {
$selected_archive = $app ->select_archive_timespec( $archives , $timespec );
} else {
$selected_archive = user_select_archive( $archives );
}
if (! defined ( $selected_archive )) {
die "No archive selected or selection invalid" ;
}
$app ->restore( $backup_path , $selected_archive , $destination );
return 0;
}
exit main();
|