#!/usr/bin/perl use warnings; use strict; use Simba::CA; use POSIX qw(strftime); use Getopt::Long; use Filesys::Statvfs; use File::stat; my @filesets; my $parallel; GetOptions( 'filesets=i' => \@filesets, 'parallel=i' => \$parallel ); @filesets = split(/,/,join(',',@filesets)); $ENV{PATH} = "/usr/bin"; my $now = strftime('%Y-%m-%dT%H:%M:%S', localtime()); open(my $log, '>>', '/var/log/simba/ca.log.' . $now); $log->autoflush(1); my $ca = Simba::CA->new({ dbi_file => $ENV{SIMBA_DB_CONN} || "$ENV{HOME}/.dbi/simba", fh_log => $log, (@filesets ? ( filesets => \@filesets ) : ()), parallel => $parallel, }); $ca->log_level(9); # Try to find all devices suitable for backup and mount them. # We do this by trying to find a matching device for each subdirectory # of /backup. Another way might be to check all USB disks. my $st = stat("/backup/"); my $base_device = $st->dev; my %luks_devices; for (glob("/backup/*")) { my $st = stat($_); my $dir_device = $st->dev; $ca->log(0, "checking $_"); if ($base_device == $dir_device) { # not a mount point (my $basedir = $_) =~ s{^/backup/}{}; if ($basedir =~ /^luks-(.*)/) { my $key = $1; for my $dev (glob("/dev/disk/by-id/*$key*")) { my ($devbase) = $dev =~ m{([^/]+$)}; if (-e "/backup/keys/$devbase") { $ca->log(0, "opening /dev/disk/by-id/$devbase on $_"); system("/sbin/cryptsetup", "open", $dev, $basedir, "--key-file", "/backup/keys/$devbase"); $ca->log(0, "mounting /dev/mapper/$basedir on $_"); system("/bin/mount", "-o", "nodev,noexec,nomand,nosuid", "/dev/mapper/$basedir", $_); } } } elsif (-e "/dev/disk/by-id/$basedir") { # matching device exists $ca->log(0, "mounting /dev/disk/by-id/$basedir on $_"); system("/bin/mount", "-o", "nodev,noexec,nomand,nosuid", "/dev/disk/by-id/$basedir", $_); } } } # Choose a random backup directory. # The directories are weighted by free space (e.g, if we have two # directories with 3 TB and 2 TB free space, then their chances of being # chosen are 60% and 40%). my @backup_dirs = map { $_ = $1 if m{(.*)/.*}; # basedir and detaint my($bsize, $frsize, $blocks, $bfree, $bavail, $files, $ffree, $favail, $flag, $namemax) = statvfs($_); my $available_bytes = $bsize * $bavail; my $avg_file_size = $bavail * ($blocks - $bfree) / ($files - $ffree); my $available_bytes_by_files = $avg_file_size * $favail; $available_bytes = $available_bytes_by_files if $available_bytes_by_files < $available_bytes; $ca->log(3, "found base $_ (est. $available_bytes bytes)"); [ $_, $available_bytes ] } glob("/backup/*/active"); my $sum_free = 0; $sum_free += $_->[1] for (@backup_dirs); $ca->log(3, "total free est. $sum_free bytes"); my $rnd = rand(); $ca->log(3, "random (raw) = $rnd"); $rnd *= $sum_free; $ca->log(3, "random (scaled) = $rnd"); my $count_free = 0; my $backup_dir; for(@backup_dirs) { $count_free += $_->[1]; $ca->log(3, "considering base $_->[0] (est. $_->[1] bytes)"); if ($count_free >= $rnd) { $backup_dir = $_->[0]; $ca->log(3, "using base $_->[0]"); last; } } unless ($backup_dir) { $ca->log(0, "no backup directory found"); exit(1); } $ca->basedir($backup_dir); # umount all potential backup dirs again, except the one we are actually # using for (@backup_dirs) { next if $_->[0] eq $backup_dir; $ca->log(0, "unmounting $_->[0]"); system("/bin/umount", $_->[0]); if ($_->[0] =~ m{(luks-[^/]+)}) { $ca->log(0, "closing $1"); system("/sbin/cryptsetup", "close", $1) } } chdir($backup_dir); # prevent accidental umount $ca->run(); # umount backup dir chdir("/"); $ca->log(0, "unmounting $backup_dir"); system("/bin/umount", $backup_dir); if ($backup_dir =~ m{(luks-[^/]+)}) { $ca->log(0, "closing $1"); system("/sbin/cryptsetup", "close", $1) }