Brackup
view release on metacpan or search on metacpan
doc/overview.txt view on Meta::CPAN
Also, don't trust sending my data in cleartext, and having it stored
in cleartext, so public key encryption is a must. Then I can run
automated backups from many hosts, without much fear of keys being
compromised.
Don't want people being able to do size-analysis, and huge files are a pain anyway, so big files are cut into chunks.
Files stored on Amazon/Google are of form:
-- meta files: backup_rootname-yyyymmddnn.meta, encrypted (YAML?) file mapping relative paths from backup directory root to the stat() information, original SHA1, and array of chunk keys (SHA1s of encrypted chunks) that comprise the file.
-- [sha1ofencryptedchunk].chunk -- content being <= ,say, 20MB chunk of encrypted data.
Then every night different hosts/laptops recurse directory trees,
consult a stat() cache (on, say, inode number, mtime, size, whatever)
and do SHA1 calculations on changed files, lookup rest from cache, and
build the metafile, upload any new chunks, encrypt the metafile,
upload the metafile.
Result:
-- I can restore any host from any point in time, with Amazon/Google
storing all my data, and only paying $0.15 cents/GB-month.
lib/Brackup/File.pm view on Meta::CPAN
sub root {
my $self = shift;
return $self->{root};
}
# returns File::stat object
sub stat {
my $self = shift;
return $self->{stat} if $self->{stat};
my $path = $self->fullpath;
my $stat = File::stat::lstat($path);
return $self->{stat} = $stat;
}
sub size {
my $self = shift;
return $self->stat->size;
}
sub is_dir {
my $self = shift;
lib/Brackup/Root.pm view on Meta::CPAN
next if $self->{digcache_file} && $path eq $self->{digcache_file};
# GC: seems to work fine as of at least gpg 1.4.5, so commenting out
# gpg seems to barf on files ending in whitespace, blowing
# stuff up, so we just skip them instead...
#if ($self->gpg_rcpts && $path =~ /\s+$/) {
# warn "Skipping file ending in whitespace: <$path>\n";
# next;
#}
my $statobj = File::stat::lstat($path);
my $is_dir = -d _;
foreach my $pattern (@{ $self->{ignore} }) {
next DENTRY if $path =~ /$pattern/;
next DENTRY if $is_dir && "$path/" =~ /$pattern/;
next DENTRY if $path =~ m!(^|/)\.brackup-digest\.db(-journal)?$!;
}
$statcache{$path} = $statobj;
push @good_dentries, $dentry;
lib/Brackup/Target/Filesystem.pm view on Meta::CPAN
my $dir = $self->metapath();
return () unless -d $dir;
opendir(my $dh, $dir) or
die "Failed to open $dir: $!\n";
my @ret = ();
while (my $fn = readdir($dh)) {
next unless $fn =~ s/\.brackup$//;
my $stat = File::stat::stat("$dir/$fn.brackup");
push @ret, Brackup::TargetBackupStatInfo->new($self, $fn,
time => $stat->mtime,
size => $stat->size);
}
closedir($dh);
return @ret;
}
# downloads the given backup name to the current directory (with
lib/Brackup/Target/Sftp.pm view on Meta::CPAN
});
unless (defined($result)) {
die "Listing failed for $path: " . $self->{sftp}->error;
}
return wantarray ? @$result : $result;
}
sub size {
my ($self, $path) = @_;
my $size = $self->_autoretry(sub {
my $attr = $self->{sftp}->stat($path)
or die "Cannot stat path '$path'";
return $attr->size;
});
unless (defined($size)) {
die "Getting size for $path failed: " . $self->{sftp}->error;
}
return $size;
}
sub _mtime {
my ($self, $path) = @_;
my $mtime = $self->_autoretry(sub {
my $attr = $self->{sftp}->stat($path)
or die "Cannot stat path '$path'";
return $attr->mtime;
});
unless (defined $mtime) {
die "Getting mtime of $_ failed: " . $self->{sftp}->error;
}
return $mtime;
}
sub _mkdir {
my ($self, $dir) = @_;
return if ! $dir || $dir eq '/';
my $parent = dirname($dir);
$self->_autoretry(sub {
$self->{sftp}->stat($parent) or $self->_mkdir($parent);
$self->{sftp}->stat($dir) or $self->{sftp}->mkdir($dir);
}) or die "Creating directory $dir failed: " . $self->{sftp}->error;
}
sub _put_chunk {
my ($self, $path, $content) = @_;
$self->_mkdir(dirname($path));
$self->_autoretry(sub {
my $fh = $self->{sftp}->open($path, SSH2_FXF_WRITE|SSH2_FXF_CREAT)
lib/Brackup/Test.pm view on Meta::CPAN
sub ok_dir_empty {
my $dir = shift;
unless (-d $dir) { ok(0, "not a dir"); return; }
opendir(my $dh, $dir) or die "failed to opendir: $!";
is_deeply([ sort readdir($dh) ], ['.', '..'], "dir is empty: $dir");
}
sub file_meta {
my $path = shift;
my $st = File::stat::lstat($path);
my $meta = {};
$meta->{size} = $st->size unless -d $path;
$meta->{is_file} = 1 if -f $path;
$meta->{is_link} = 1 if -l $path;
if ($meta->{is_link}) {
$meta->{link} = readlink $path;
} else {
# we ignore these for links, since Linux doesn't let us restore anyway,
# as Linux as no lutimes(2) syscall, as of Linux 2.6.16 at least
( run in 1.840 second using v1.01-cache-2.11-cpan-49f99fa48dc )