1 | #!/usr/bin/perl |
---|
2 | # |
---|
3 | # 2011/06/21 Gabriel Moreau <Gabriel.Moreau@univ-grenoble-alpes.fr> |
---|
4 | # |
---|
5 | # apt-get install iputils-ping rsync nmap perl-base libyaml-perl libio-all-perl libfile-finder-perl libnet-ldap-perl |
---|
6 | |
---|
7 | use strict; |
---|
8 | use warnings; |
---|
9 | |
---|
10 | use Getopt::Long qw(GetOptions); |
---|
11 | use YAML; |
---|
12 | use IO::All; |
---|
13 | use File::Basename; |
---|
14 | use File::Finder; |
---|
15 | use Net::LDAP; |
---|
16 | use List::Util qw(shuffle); |
---|
17 | use Logger::Syslog; |
---|
18 | |
---|
19 | my $command = shift @ARGV || 'help'; |
---|
20 | |
---|
21 | my %cmd_db = ( |
---|
22 | 'help' => \&cmd_help, |
---|
23 | 'version' => \&cmd_version, |
---|
24 | 'generate' => \&cmd_generate, |
---|
25 | 'update' => \&cmd_update, |
---|
26 | 'init' => \&cmd_init_db, |
---|
27 | 'exclude-list' => \&cmd_exclude_list, |
---|
28 | ); |
---|
29 | |
---|
30 | #------------------------------------------------------------------------------- |
---|
31 | |
---|
32 | my ($LDAP_H, $LDAP_BASE); |
---|
33 | my $LIMIT_TIMESTAMP = time() - (8 * 24 * 3600); # 8 days |
---|
34 | |
---|
35 | if (defined $cmd_db{$command}) { |
---|
36 | $cmd_db{$command}->(@ARGV); |
---|
37 | } |
---|
38 | else { |
---|
39 | print {*STDERR} "backuppc-silzigan: command $command not found\n\n"; |
---|
40 | $cmd_db{help}->(); |
---|
41 | exit 1; |
---|
42 | } |
---|
43 | |
---|
44 | exit; |
---|
45 | |
---|
46 | #------------------------------------------------------------------------------- |
---|
47 | |
---|
48 | sub open_ldap { |
---|
49 | # AuthLDAPUrl "ldap://ldapserver.mylab.fr/ou=Users,dc=mylab,dc=fr?uid?sub" |
---|
50 | # AuthLDAPBindDN "cn=ldapconnect,ou=System,dc=mylab,dc=fr" |
---|
51 | # AuthLDAPBindPassword "Rhalala128" |
---|
52 | |
---|
53 | my ($masterLDAP, $masterDN, $masterPw); |
---|
54 | |
---|
55 | for my $config_line (io('/etc/apache2/conf.d/backuppc.conf')->chomp->slurp) { |
---|
56 | ($masterLDAP, $LDAP_BASE) = ($1, $2) if $config_line =~ m{ ^\s* AuthLDAPUrl [^/]+ // ([^/]+) / (ou=[^?]+) }xms; |
---|
57 | $masterDN = $1 if $config_line =~ m{ ^\s* AuthLDAPBindDN \s+ " ([^"]+) " }xms; |
---|
58 | $masterPw = $1 if $config_line =~ m{ ^\s* AuthLDAPBindPassword \s+ " ([^"]+) " }xms; |
---|
59 | } |
---|
60 | |
---|
61 | $LDAP_H = Net::LDAP->new( "$masterLDAP" ) or die "$@"; |
---|
62 | my $mesg = $LDAP_H->bind("$masterDN", password => "$masterPw"); |
---|
63 | |
---|
64 | return; |
---|
65 | } |
---|
66 | |
---|
67 | #------------------------------------------------------------------------------- |
---|
68 | |
---|
69 | sub close_ldap { |
---|
70 | $LDAP_H->unbind(); |
---|
71 | return; |
---|
72 | } |
---|
73 | |
---|
74 | #------------------------------------------------------------------------------- |
---|
75 | |
---|
76 | sub cmd_update { |
---|
77 | local @ARGV = @_; |
---|
78 | |
---|
79 | my $search_config_file = File::Finder->type('f')->name('*.yaml'); |
---|
80 | for my $config_file (File::Finder->eval($search_config_file)->in('/etc/backuppc')) { |
---|
81 | cmd_generate("$config_file", @ARGV); |
---|
82 | } |
---|
83 | |
---|
84 | add_oldcomputer(); |
---|
85 | |
---|
86 | update_hosts(); |
---|
87 | } |
---|
88 | |
---|
89 | #------------------------------------------------------------------------------- |
---|
90 | |
---|
91 | sub add_oldcomputer { |
---|
92 | my $admin = 'sys-admin'; |
---|
93 | |
---|
94 | my %hostdb = (); |
---|
95 | for my $hostline (io('/etc/backuppc/hosts.order')->chomp->slurp) { |
---|
96 | next if not $hostline =~ m/^\w/; |
---|
97 | my ($host) = split /\s+/, $hostline, 2; |
---|
98 | $hostdb{$host}++; |
---|
99 | } |
---|
100 | |
---|
101 | # Reset hosts database |
---|
102 | print '' > io('/etc/backuppc/hosts.oldcomputer'); |
---|
103 | |
---|
104 | for my $pc (io->dir('/var/lib/backuppc/pc')->all_dirs) { |
---|
105 | my $pcname = $pc->filename; |
---|
106 | my $pcpath = $pc->pathname; |
---|
107 | next if not -e "$pcpath/backups"; |
---|
108 | next if not -e "/etc/backuppc/$pcname.pl"; |
---|
109 | |
---|
110 | my $user = 'root'; |
---|
111 | my $host = $pcname; |
---|
112 | ($host, $user) = split /_/, $pcname, 2 if $pcname =~ m/_/; |
---|
113 | next if exists $hostdb{$pcname}; |
---|
114 | |
---|
115 | my $full_period; |
---|
116 | for (io("/etc/backuppc/$pcname.pl")->chomp->slurp) { |
---|
117 | m/FullPeriod/ or next; |
---|
118 | $full_period++; |
---|
119 | last; |
---|
120 | } |
---|
121 | |
---|
122 | io("/etc/backuppc/$pcname.pl")->append('$Conf{FullPeriod} = "-2";') if not $full_period; |
---|
123 | |
---|
124 | # Add to hosts database |
---|
125 | print "$pcname 0 sleeping $admin,$user\n" >> io('/etc/backuppc/hosts.oldcomputer'); |
---|
126 | } |
---|
127 | } |
---|
128 | |
---|
129 | #------------------------------------------------------------------------------- |
---|
130 | |
---|
131 | sub shell_command { |
---|
132 | my $cmd = shift; |
---|
133 | |
---|
134 | require FileHandle; |
---|
135 | my $fh = new FileHandle; |
---|
136 | my @result = (); |
---|
137 | open $fh, q{-|}, "LANG=C $cmd" or die "Can't exec $cmd\n"; |
---|
138 | @result = <$fh>; |
---|
139 | close $fh; |
---|
140 | chomp @result; |
---|
141 | return @result; |
---|
142 | } |
---|
143 | |
---|
144 | #------------------------------------------------------------------------------- |
---|
145 | |
---|
146 | sub cmd_generate { |
---|
147 | local @ARGV = @_; |
---|
148 | my $config_file = shift @ARGV; |
---|
149 | |
---|
150 | my ($verbose); |
---|
151 | |
---|
152 | GetOptions( |
---|
153 | 'verbose' => \$verbose, |
---|
154 | ); |
---|
155 | |
---|
156 | my $CONFIG; |
---|
157 | |
---|
158 | eval { |
---|
159 | $CONFIG = YAML::LoadFile($config_file); |
---|
160 | }; |
---|
161 | if ($@) { |
---|
162 | warning "Error: bad YAML in file $config_file"; |
---|
163 | return; |
---|
164 | } |
---|
165 | |
---|
166 | $CONFIG->{default}{namespace} ||= basename($config_file, '.yaml'); |
---|
167 | $CONFIG->{default}{path} ||= "/etc/backuppc/auto/$CONFIG->{default}{namespace}"; |
---|
168 | $CONFIG->{default}{hosts} ||= "$CONFIG->{default}{path}/hosts"; |
---|
169 | $CONFIG->{default}{exclude} ||= "/usr/lib/kont/etc/backuppc/exclude.txt"; |
---|
170 | |
---|
171 | if (not -d "/etc/backuppc/auto/$CONFIG->{default}{namespace}") { |
---|
172 | io("/etc/backuppc/auto/$CONFIG->{default}{namespace}")->mkpath({mode => 0755}); |
---|
173 | } |
---|
174 | |
---|
175 | print '' > io($CONFIG->{default}{hosts}); |
---|
176 | |
---|
177 | open_ldap(); |
---|
178 | LOOP_ON_COMPUTER: |
---|
179 | for my $computer ( keys %{$CONFIG->{computers}}) { |
---|
180 | my $login = $CONFIG->{computers}{$computer}{login} || 'root'; |
---|
181 | |
---|
182 | LOOP_ON_USER: |
---|
183 | for my $user ( keys %{$CONFIG->{computers}{$computer}{users}}) { |
---|
184 | my $pathshare = $CONFIG->{computers}{$computer}{share} |
---|
185 | || $CONFIG->{default}{share} |
---|
186 | || "/home/users"; |
---|
187 | my $share = $CONFIG->{computers}{$computer}{users}{$user}{share} |
---|
188 | || "$pathshare/$user"; |
---|
189 | my $status = $CONFIG->{computers}{$computer}{users}{$user}{status} |
---|
190 | || $CONFIG->{computers}{$computer}{status} |
---|
191 | || $CONFIG->{default}{status} |
---|
192 | || 'auto'; |
---|
193 | my $admin = $CONFIG->{computers}{$computer}{users}{$user}{admin} |
---|
194 | || $CONFIG->{computers}{$computer}{admin} |
---|
195 | || $CONFIG->{default}{admin} |
---|
196 | || 'root'; |
---|
197 | |
---|
198 | my $exclude = $CONFIG->{computers}{$computer}{users}{$user}{exclude} |
---|
199 | || $CONFIG->{computers}{$computer}{exclude} |
---|
200 | || ''; |
---|
201 | |
---|
202 | my @exclude_list = (); |
---|
203 | if (ref($exclude) eq "ARRAY") { |
---|
204 | push @exclude_list, @{$exclude}; |
---|
205 | } |
---|
206 | else { |
---|
207 | push @exclude_list, $exclude if not $exclude =~ m/^$/; |
---|
208 | } |
---|
209 | push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp; |
---|
210 | my $exclude_string = join ",\n", map { " '$_'" } @exclude_list; |
---|
211 | |
---|
212 | if ($status eq "auto") { |
---|
213 | $status = "disable"; |
---|
214 | my $ldb = $LDAP_H->search( |
---|
215 | base => "$LDAP_BASE", |
---|
216 | filter => "(uid=$user)", |
---|
217 | attrs => ['shadowExpire', 'sambaKickoffTime'], |
---|
218 | ); |
---|
219 | if (not $ldb->code ) { |
---|
220 | |
---|
221 | LDAP_RESULT: |
---|
222 | foreach my $entry ($ldb->entries) { |
---|
223 | |
---|
224 | my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0; |
---|
225 | my $user_shadow_expire = $entry->get_value('shadowExpire') || 0; |
---|
226 | |
---|
227 | if ($user_shadow_expire == 0) { |
---|
228 | $status = "enable"; |
---|
229 | last LDAP_RESULT; |
---|
230 | } |
---|
231 | elsif ( |
---|
232 | ( $user_expire_timestamp ne "" ) |
---|
233 | and ( $user_expire_timestamp > $LIMIT_TIMESTAMP ) |
---|
234 | ) { |
---|
235 | $status = "enable"; |
---|
236 | last LDAP_RESULT; |
---|
237 | } |
---|
238 | |
---|
239 | } |
---|
240 | } |
---|
241 | } |
---|
242 | |
---|
243 | print STDERR "Info: write_config($user, $computer, $share, $login, $admin, $status)\n" if $verbose; |
---|
244 | write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string); |
---|
245 | |
---|
246 | } |
---|
247 | |
---|
248 | if (exists $CONFIG->{computers}{$computer}{subfolder}) { |
---|
249 | my $home_path = $CONFIG->{computers}{$computer}{subfolder}; |
---|
250 | print STDERR "\nInfo: ssh on $login\@$computer\n" if $verbose; |
---|
251 | my @ls = shell_command("/bin/ping -W 2 -c 1 '$computer' > /dev/null 2>&1 && { |
---|
252 | /usr/bin/nmap -p 22 -PN '$computer' | grep -q '^22/tcp[[:space:]]*open' && { |
---|
253 | /usr/bin/rsync --dry-run '$login\@$computer:$home_path' /tmp/backuppc-test/ || echo Error for '$login\@$computer' | logger -t backuppc-silzigan; |
---|
254 | }; |
---|
255 | };"); |
---|
256 | |
---|
257 | $home_path =~ s{/[^/]*$}{}; |
---|
258 | LINE: |
---|
259 | for my $line (@ls) { |
---|
260 | chomp $line; |
---|
261 | next LINE if not $line =~ m/skipping\sdirectory/; |
---|
262 | next LINE if $line =~ m/lost\+found/; |
---|
263 | next LINE if $line =~ m/administrator/; |
---|
264 | my ($user) = reverse split /\s+/, $line; |
---|
265 | $user =~ s{/$}{}; |
---|
266 | $user =~ s{.*/}{}; |
---|
267 | next LINE if not $user =~ m/^\w/; |
---|
268 | |
---|
269 | my $share = "$home_path/$user"; |
---|
270 | |
---|
271 | my @exclude_list = (); |
---|
272 | push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp; |
---|
273 | my $exclude_string = join ",\n", map { " '$_'" } @exclude_list; |
---|
274 | |
---|
275 | my $admin = $CONFIG->{computers}{$computer}{admin} |
---|
276 | || $CONFIG->{default}{admin} |
---|
277 | || 'root'; |
---|
278 | |
---|
279 | my $status = "disable"; |
---|
280 | my $ldb = $LDAP_H->search( |
---|
281 | base => "$LDAP_BASE", |
---|
282 | filter => "(uid=$user)", |
---|
283 | attrs => ['shadowExpire', 'sambaKickoffTime'], |
---|
284 | ); |
---|
285 | if (not $ldb->code ) { |
---|
286 | |
---|
287 | LDAP_RESULT: |
---|
288 | foreach my $entry ($ldb->entries) { |
---|
289 | |
---|
290 | my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0; |
---|
291 | my $user_shadow_expire = $entry->get_value('shadowExpire') || 0; |
---|
292 | |
---|
293 | if ($user_shadow_expire == 0) { |
---|
294 | $status = "enable"; |
---|
295 | last LDAP_RESULT; |
---|
296 | } |
---|
297 | elsif ( |
---|
298 | ( $user_expire_timestamp ne "" ) |
---|
299 | and ( $user_expire_timestamp > $LIMIT_TIMESTAMP ) |
---|
300 | ) { |
---|
301 | $status = "enable"; |
---|
302 | last LDAP_RESULT; |
---|
303 | } |
---|
304 | |
---|
305 | } |
---|
306 | } |
---|
307 | |
---|
308 | print STDERR "Info: write_config($user, $computer, $share, $login, $admin, $status)\n" if $verbose; |
---|
309 | write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string); |
---|
310 | } |
---|
311 | } |
---|
312 | } |
---|
313 | close_ldap(); |
---|
314 | } |
---|
315 | |
---|
316 | #------------------------------------------------------------------------------- |
---|
317 | |
---|
318 | sub cmd_exclude_list { |
---|
319 | print io('/usr/lib/kont/etc/backuppc/exclude.txt')->all; |
---|
320 | } |
---|
321 | |
---|
322 | #------------------------------------------------------------------------------- |
---|
323 | |
---|
324 | sub write_config { |
---|
325 | my ($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude) = @_; |
---|
326 | my ($c) = split /\./, $computer; |
---|
327 | my $backup_name = $c . '_'. $user; |
---|
328 | |
---|
329 | return if $status eq 'disable' and not -e "/var/lib/backuppc/pc/$backup_name/backups"; |
---|
330 | |
---|
331 | print "$backup_name 0 $login $admin,$user\n" >> io($CONFIG->{default}{hosts}); |
---|
332 | |
---|
333 | my $share_string = "'$share'"; |
---|
334 | if (ref($share) eq "ARRAY") { |
---|
335 | $share_string = join ', ', map("'$_'", @{$share}); |
---|
336 | } |
---|
337 | |
---|
338 | # my $exclude = join ",\n", map { " '$_'" } io('/usr/lib/kont/etc/backuppc/exclude.txt')->chomp->slurp; |
---|
339 | |
---|
340 | print <<END > io("$CONFIG->{default}{path}/$backup_name.pl"); |
---|
341 | \$Conf{XferMethod} = 'rsync'; |
---|
342 | \$Conf{ClientNameAlias} = '$computer'; |
---|
343 | \$Conf{RsyncShareName} = [ $share_string ]; |
---|
344 | \$Conf{RsyncClientCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+'; |
---|
345 | \$Conf{RsyncClientRestoreCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+'; |
---|
346 | \$Conf{BackupFilesExclude} = { |
---|
347 | '$share' => [ |
---|
348 | $exclude |
---|
349 | ] |
---|
350 | }; |
---|
351 | END |
---|
352 | |
---|
353 | print '$Conf{FullPeriod} = "-2";' >> io("$CONFIG->{default}{path}/$backup_name.pl") if $status eq 'disable'; |
---|
354 | symlink "$CONFIG->{default}{path}/$backup_name.pl", "/etc/backuppc/pc/$backup_name.pl"; |
---|
355 | } |
---|
356 | |
---|
357 | #------------------------------------------------------------------------------- |
---|
358 | |
---|
359 | sub update_hosts { |
---|
360 | io->catfile('/etc/backuppc/hosts.main') > io('/etc/backuppc/hosts.order'); |
---|
361 | my @hosts = io('/etc/backuppc/hosts.main')->chomp->slurp; |
---|
362 | |
---|
363 | push @hosts, io('/etc/backuppc/hosts.oldcomputer')->chomp->slurp; |
---|
364 | |
---|
365 | my $search_host_file = File::Finder->type('f')->name('hosts'); |
---|
366 | for my $host_file (File::Finder->eval($search_host_file)->in('/etc/backuppc/auto')) { |
---|
367 | print "#\n# $host_file\n#\n" >> io('/etc/backuppc/hosts.order'); |
---|
368 | io->catfile("$host_file") >> io('/etc/backuppc/hosts.order'); |
---|
369 | push @hosts, io("$host_file")->chomp->slurp; |
---|
370 | } |
---|
371 | my ($first, @host) = grep(/./, grep(!/^#/, @hosts)); |
---|
372 | print "$first\n" > io('/etc/backuppc/hosts'); |
---|
373 | print "$_\n" >> io('/etc/backuppc/hosts') for shuffle(@host); |
---|
374 | } |
---|
375 | |
---|
376 | #------------------------------------------------------------------------------- |
---|
377 | |
---|
378 | sub cmd_init_db { |
---|
379 | my $cfg = { |
---|
380 | computers => { |
---|
381 | 'machine36.hmg.priv' => { |
---|
382 | login => 'root', |
---|
383 | share => '/home/users/%u', |
---|
384 | status => 'auto', |
---|
385 | users => { |
---|
386 | 'dupond' => { |
---|
387 | share => [ '/home/users/dupond', '/var/www/dupond' ], |
---|
388 | status => 'enable', |
---|
389 | }, |
---|
390 | 'durand' => { |
---|
391 | share => '/home/users/durand', |
---|
392 | }, |
---|
393 | }, |
---|
394 | }, |
---|
395 | }, |
---|
396 | }; |
---|
397 | |
---|
398 | YAML::DumpFile("/tmp/template-backuppc.yaml", $cfg); |
---|
399 | } |
---|
400 | |
---|
401 | #------------------------------------------------------------------------------- |
---|
402 | |
---|
403 | sub cmd_help { |
---|
404 | print <<'END'; |
---|
405 | backuppc-silzigan - cut into small pieces a computer configuration for backuppc |
---|
406 | |
---|
407 | backuppc-silzigan init |
---|
408 | backuppc-silzigan generate config_file.yaml [--verbose] |
---|
409 | backuppc-silzigan update [--verbose] |
---|
410 | backuppc-silzigan help |
---|
411 | backuppc-silzigan version |
---|
412 | backuppc-silzigan exclude-list |
---|
413 | END |
---|
414 | return; |
---|
415 | } |
---|
416 | |
---|
417 | #------------------------------------------------------------------------------- |
---|
418 | |
---|
419 | sub cmd_version { |
---|
420 | print <<'END'; |
---|
421 | backuppc-silzigan - cut into small pieces a computer configuration for backuppc |
---|
422 | Author: Gabriel Moreau <Gabriel.Moreau@univ-grenoble-alpes.fr> |
---|
423 | Copyright (C) 2011-2019, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France |
---|
424 | |
---|
425 | $Id: backuppc-silzigan..host.legilnx32 3821 2013-12-20 08:03:14Z g7moreau $ |
---|
426 | END |
---|
427 | return; |
---|
428 | } |
---|