#!/usr/bin/perl # # 2011/06/21 Gabriel Moreau # # apt-get install iputils-ping rsync nmap perl-base libyaml-perl libio-all-perl libfile-finder-perl libnet-ldap-perl use strict; use warnings; use Getopt::Long qw(GetOptions); use YAML; use IO::All; use File::Basename; use File::Finder; use Net::LDAP; use List::Util qw(shuffle); use Logger::Syslog; my $command = shift @ARGV || 'help'; my %cmd_db = ( 'help' => \&cmd_help, 'version' => \&cmd_version, 'generate' => \&cmd_generate, 'update' => \&cmd_update, 'init' => \&cmd_init_db, 'exclude-list' => \&cmd_exclude_list, ); #------------------------------------------------------------------------------- my ($LDAP_H, $LDAP_BASE); my $LIMIT_TIMESTAMP = time() - (8 * 24 * 3600); # 8 days if (defined $cmd_db{$command}) { $cmd_db{$command}->(@ARGV); } else { print {*STDERR} "backuppc-silzigan: command $command not found\n\n"; $cmd_db{help}->(); exit 1; } exit; #------------------------------------------------------------------------------- sub open_ldap { # AuthLDAPUrl "ldap://ldapserver.mylab.fr/ou=Users,dc=mylab,dc=fr?uid?sub" # AuthLDAPBindDN "cn=ldapconnect,ou=System,dc=mylab,dc=fr" # AuthLDAPBindPassword "Rhalala128" my ($masterLDAP, $masterDN, $masterPw); for my $config_line (io('/etc/apache2/conf.d/backuppc.conf')->chomp->slurp) { ($masterLDAP, $LDAP_BASE) = ($1, $2) if $config_line =~ m{ ^\s* AuthLDAPUrl [^/]+ // ([^/]+) / (ou=[^?]+) }xms; $masterDN = $1 if $config_line =~ m{ ^\s* AuthLDAPBindDN \s+ " ([^"]+) " }xms; $masterPw = $1 if $config_line =~ m{ ^\s* AuthLDAPBindPassword \s+ " ([^"]+) " }xms; } $LDAP_H = Net::LDAP->new( "$masterLDAP" ) or die "$@"; my $mesg = $LDAP_H->bind("$masterDN", password => "$masterPw"); return; } #------------------------------------------------------------------------------- sub close_ldap { $LDAP_H->unbind(); return; } #------------------------------------------------------------------------------- sub cmd_update { local @ARGV = @_; my $search_config_file = File::Finder->type('f')->name('*.yaml'); for my $config_file (File::Finder->eval($search_config_file)->in('/etc/backuppc')) { cmd_generate("$config_file", @ARGV); } add_oldcomputer(); update_hosts(); } #------------------------------------------------------------------------------- sub add_oldcomputer { my $admin = 'sys-admin'; my %hostdb = (); for my $hostline (io('/etc/backuppc/hosts.order')->chomp->slurp) { next if not $hostline =~ m/^\w/; my ($host) = split /\s+/, $hostline, 2; $hostdb{$host}++; } # Reset hosts database print '' > io('/etc/backuppc/hosts.oldcomputer'); for my $pc (io->dir('/var/lib/backuppc/pc')->all_dirs) { my $pcname = $pc->filename; my $pcpath = $pc->pathname; next if not -e "$pcpath/backups"; next if not -e "/etc/backuppc/$pcname.pl"; my $user = 'root'; my $host = $pcname; ($host, $user) = split /_/, $pcname, 2 if $pcname =~ m/_/; next if exists $hostdb{$pcname}; my $full_period; for (io("/etc/backuppc/$pcname.pl")->chomp->slurp) { m/FullPeriod/ or next; $full_period++; last; } io("/etc/backuppc/$pcname.pl")->append('$Conf{FullPeriod} = "-2";') if not $full_period; # Add to hosts database print "$pcname 0 sleeping $admin,$user\n" >> io('/etc/backuppc/hosts.oldcomputer'); } } #------------------------------------------------------------------------------- sub shell_command { my $cmd = shift; require FileHandle; my $fh = new FileHandle; my @result = (); open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n"; @result = <$fh>; close $fh; chomp @result; return @result; } #------------------------------------------------------------------------------- sub cmd_generate { local @ARGV = @_; my $config_file = shift @ARGV; my ($verbose); GetOptions( 'verbose' => \$verbose, ); my $CONFIG; eval { $CONFIG = YAML::LoadFile($config_file); }; if ($@) { warning "Error: bad YAML in file $config_file"; return; } $CONFIG->{default}{namespace} ||= basename($config_file, '.yaml'); $CONFIG->{default}{path} ||= "/etc/backuppc/auto/$CONFIG->{default}{namespace}"; $CONFIG->{default}{hosts} ||= "$CONFIG->{default}{path}/hosts"; $CONFIG->{default}{exclude} ||= "/usr/lib/kont/etc/backuppc/exclude.txt"; if (not -d "/etc/backuppc/auto/$CONFIG->{default}{namespace}") { io("/etc/backuppc/auto/$CONFIG->{default}{namespace}")->mkpath({mode => 0755}); } print '' > io($CONFIG->{default}{hosts}); open_ldap(); LOOP_ON_COMPUTER: for my $computer ( keys %{$CONFIG->{computers}}) { my $login = $CONFIG->{computers}{$computer}{login} || 'root'; LOOP_ON_USER: for my $user ( keys %{$CONFIG->{computers}{$computer}{users}}) { my $pathshare = $CONFIG->{computers}{$computer}{share} || $CONFIG->{default}{share} || "/home/users"; my $share = $CONFIG->{computers}{$computer}{users}{$user}{share} || "$pathshare/$user"; my $status = $CONFIG->{computers}{$computer}{users}{$user}{status} || $CONFIG->{computers}{$computer}{status} || $CONFIG->{default}{status} || 'auto'; my $admin = $CONFIG->{computers}{$computer}{users}{$user}{admin} || $CONFIG->{computers}{$computer}{admin} || $CONFIG->{default}{admin} || 'root'; my $exclude = $CONFIG->{computers}{$computer}{users}{$user}{exclude} || $CONFIG->{computers}{$computer}{exclude} || ''; my @exclude_list = (); if (ref($exclude) eq "ARRAY") { push @exclude_list, @{$exclude}; } else { push @exclude_list, $exclude if not $exclude =~ m/^$/; } push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp; my $exclude_string = join ",\n", map { " '$_'" } @exclude_list; if ($status eq "auto") { $status = "disable"; my $ldb = $LDAP_H->search( base => "$LDAP_BASE", filter => "(uid=$user)", attrs => ['shadowExpire', 'sambaKickoffTime'], ); if (not $ldb->code ) { LDAP_RESULT: foreach my $entry ($ldb->entries) { my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0; my $user_shadow_expire = $entry->get_value('shadowExpire') || 0; if ($user_shadow_expire == 0) { $status = "enable"; last LDAP_RESULT; } elsif ( ( $user_expire_timestamp ne "" ) and ( $user_expire_timestamp > $LIMIT_TIMESTAMP ) ) { $status = "enable"; last LDAP_RESULT; } } } } print STDERR "Info: write_config($user, $computer, $share, $login, $admin, $status)\n" if $verbose; write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string); } if (exists $CONFIG->{computers}{$computer}{subfolder}) { my $home_path = $CONFIG->{computers}{$computer}{subfolder}; print STDERR "\nInfo: ssh on $login\@$computer\n" if $verbose; my @ls = shell_command("/bin/ping -W 2 -c 1 '$computer' > /dev/null 2>&1 && { /usr/bin/nmap -p 22 -PN '$computer' | grep -q '^22/tcp[[:space:]]*open' && { /usr/bin/rsync --dry-run '$login\@$computer:$home_path' /tmp/backuppc-test/ || echo Error for '$login\@$computer' | logger -t backuppc-silzigan; }; };"); $home_path =~ s{/[^/]*$}{}; LINE: for my $line (@ls) { chomp $line; next LINE if not $line =~ m/skipping\sdirectory/; next LINE if $line =~ m/lost\+found/; next LINE if $line =~ m/administrator/; my ($user) = reverse split /\s+/, $line; $user =~ s{/$}{}; $user =~ s{.*/}{}; next LINE if not $user =~ m/^\w/; my $share = "$home_path/$user"; my @exclude_list = (); push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp; my $exclude_string = join ",\n", map { " '$_'" } @exclude_list; my $admin = $CONFIG->{computers}{$computer}{admin} || $CONFIG->{default}{admin} || 'root'; my $status = "disable"; my $ldb = $LDAP_H->search( base => "$LDAP_BASE", filter => "(uid=$user)", attrs => ['shadowExpire', 'sambaKickoffTime'], ); if (not $ldb->code ) { LDAP_RESULT: foreach my $entry ($ldb->entries) { my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0; my $user_shadow_expire = $entry->get_value('shadowExpire') || 0; if ($user_shadow_expire == 0) { $status = "enable"; last LDAP_RESULT; } elsif ( ( $user_expire_timestamp ne "" ) and ( $user_expire_timestamp > $LIMIT_TIMESTAMP ) ) { $status = "enable"; last LDAP_RESULT; } } } print STDERR "Info: write_config($user, $computer, $share, $login, $admin, $status)\n" if $verbose; write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string); } } } close_ldap(); } #------------------------------------------------------------------------------- sub cmd_exclude_list { print io('/usr/lib/kont/etc/backuppc/exclude.txt')->all; } #------------------------------------------------------------------------------- sub write_config { my ($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude) = @_; my ($c) = split /\./, $computer; my $backup_name = $c . '_'. $user; return if $status eq 'disable' and not -e "/var/lib/backuppc/pc/$backup_name/backups"; print "$backup_name 0 $login $admin,$user\n" >> io($CONFIG->{default}{hosts}); my $share_string = "'$share'"; if (ref($share) eq "ARRAY") { $share_string = join ', ', map("'$_'", @{$share}); } # my $exclude = join ",\n", map { " '$_'" } io('/usr/lib/kont/etc/backuppc/exclude.txt')->chomp->slurp; print < io("$CONFIG->{default}{path}/$backup_name.pl"); \$Conf{XferMethod} = 'rsync'; \$Conf{ClientNameAlias} = '$computer'; \$Conf{RsyncShareName} = [ $share_string ]; \$Conf{RsyncClientCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+'; \$Conf{RsyncClientRestoreCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+'; \$Conf{BackupFilesExclude} = { '$share' => [ $exclude ] }; END print '$Conf{FullPeriod} = "-2";' >> io("$CONFIG->{default}{path}/$backup_name.pl") if $status eq 'disable'; symlink "$CONFIG->{default}{path}/$backup_name.pl", "/etc/backuppc/pc/$backup_name.pl"; } #------------------------------------------------------------------------------- sub update_hosts { io->catfile('/etc/backuppc/hosts.main') > io('/etc/backuppc/hosts.order'); my @hosts = io('/etc/backuppc/hosts.main')->chomp->slurp; push @hosts, io('/etc/backuppc/hosts.oldcomputer')->chomp->slurp; my $search_host_file = File::Finder->type('f')->name('hosts'); for my $host_file (File::Finder->eval($search_host_file)->in('/etc/backuppc/auto')) { print "#\n# $host_file\n#\n" >> io('/etc/backuppc/hosts.order'); io->catfile("$host_file") >> io('/etc/backuppc/hosts.order'); push @hosts, io("$host_file")->chomp->slurp; } my ($first, @host) = grep(/./, grep(!/^#/, @hosts)); print "$first\n" > io('/etc/backuppc/hosts'); print "$_\n" >> io('/etc/backuppc/hosts') for shuffle(@host); } #------------------------------------------------------------------------------- sub cmd_init_db { my $cfg = { computers => { 'machine36.hmg.priv' => { login => 'root', share => '/home/users/%u', status => 'auto', users => { 'dupond' => { share => [ '/home/users/dupond', '/var/www/dupond' ], status => 'enable', }, 'durand' => { share => '/home/users/durand', }, }, }, }, }; YAML::DumpFile("/tmp/template-backuppc.yaml", $cfg); } #------------------------------------------------------------------------------- sub cmd_help { print <<'END'; backuppc-silzigan - cut into small pieces a computer configuration for backuppc backuppc-silzigan init backuppc-silzigan generate config_file.yaml [--verbose] backuppc-silzigan update [--verbose] backuppc-silzigan help backuppc-silzigan version backuppc-silzigan exclude-list END return; } #------------------------------------------------------------------------------- sub cmd_version { print <<'END'; backuppc-silzigan - cut into small pieces a computer configuration for backuppc Author: Gabriel Moreau Copyright (C) 2011-2019, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France $Id: backuppc-silzigan..host.legilnx32 3821 2013-12-20 08:03:14Z g7moreau $ END return; }