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 cmd_generate { |
---|
132 | local @ARGV = @_; |
---|
133 | my $config_file = shift @ARGV; |
---|
134 | |
---|
135 | my ($verbose); |
---|
136 | |
---|
137 | GetOptions( |
---|
138 | 'verbose' => \$verbose, |
---|
139 | ); |
---|
140 | |
---|
141 | my $CONFIG; |
---|
142 | |
---|
143 | eval { |
---|
144 | $CONFIG = YAML::LoadFile($config_file); |
---|
145 | }; |
---|
146 | if ($@) { |
---|
147 | warning "Error: bad YAML in file $config_file"; |
---|
148 | return; |
---|
149 | } |
---|
150 | |
---|
151 | $CONFIG->{default}{namespace} ||= basename($config_file, '.yaml'); |
---|
152 | $CONFIG->{default}{path} ||= "/etc/backuppc/auto/$CONFIG->{default}{namespace}"; |
---|
153 | $CONFIG->{default}{hosts} ||= "$CONFIG->{default}{path}/hosts"; |
---|
154 | $CONFIG->{default}{exclude} ||= "/usr/lib/kont/etc/backuppc/exclude.txt"; |
---|
155 | |
---|
156 | if (not -d "/etc/backuppc/auto/$CONFIG->{default}{namespace}") { |
---|
157 | io("/etc/backuppc/auto/$CONFIG->{default}{namespace}")->mkpath({mode => 0755}); |
---|
158 | } |
---|
159 | |
---|
160 | print '' > io($CONFIG->{default}{hosts}); |
---|
161 | |
---|
162 | open_ldap(); |
---|
163 | LOOP_ON_COMPUTER: |
---|
164 | for my $computer ( keys %{$CONFIG->{computers}}) { |
---|
165 | my $login = $CONFIG->{computers}{$computer}{login} || 'root'; |
---|
166 | |
---|
167 | LOOP_ON_USER: |
---|
168 | for my $user ( keys %{$CONFIG->{computers}{$computer}{users}}) { |
---|
169 | my $pathshare = $CONFIG->{computers}{$computer}{share} |
---|
170 | || $CONFIG->{default}{share} |
---|
171 | || "/home/users"; |
---|
172 | my $share = $CONFIG->{computers}{$computer}{users}{$user}{share} |
---|
173 | || "$pathshare/$user"; |
---|
174 | my $status = $CONFIG->{computers}{$computer}{users}{$user}{status} |
---|
175 | || $CONFIG->{computers}{$computer}{status} |
---|
176 | || $CONFIG->{default}{status} |
---|
177 | || 'auto'; |
---|
178 | my $admin = $CONFIG->{computers}{$computer}{users}{$user}{admin} |
---|
179 | || $CONFIG->{computers}{$computer}{admin} |
---|
180 | || $CONFIG->{default}{admin} |
---|
181 | || 'root'; |
---|
182 | |
---|
183 | my $exclude = $CONFIG->{computers}{$computer}{users}{$user}{exclude} |
---|
184 | || $CONFIG->{computers}{$computer}{exclude} |
---|
185 | || ''; |
---|
186 | |
---|
187 | my @exclude_list = (); |
---|
188 | if (ref($exclude) eq "ARRAY") { |
---|
189 | push @exclude_list, @{$exclude}; |
---|
190 | } |
---|
191 | else { |
---|
192 | push @exclude_list, $exclude if not $exclude =~ m/^$/; |
---|
193 | } |
---|
194 | push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp; |
---|
195 | my $exclude_string = join ",\n", map { " '$_'" } @exclude_list; |
---|
196 | |
---|
197 | if ($status eq "auto") { |
---|
198 | $status = "disable"; |
---|
199 | my $ldb = $LDAP_H->search( |
---|
200 | base => "$LDAP_BASE", |
---|
201 | filter => "(uid=$user)", |
---|
202 | attrs => ['shadowExpire', 'sambaKickoffTime'], |
---|
203 | ); |
---|
204 | if (not $ldb->code ) { |
---|
205 | |
---|
206 | LDAP_RESULT: |
---|
207 | foreach my $entry ($ldb->entries) { |
---|
208 | |
---|
209 | my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0; |
---|
210 | my $user_shadow_expire = $entry->get_value('shadowExpire') || 0; |
---|
211 | |
---|
212 | if ($user_shadow_expire == 0) { |
---|
213 | $status = "enable"; |
---|
214 | last LDAP_RESULT; |
---|
215 | } |
---|
216 | elsif ( |
---|
217 | ( $user_expire_timestamp ne "" ) |
---|
218 | and ( $user_expire_timestamp > $LIMIT_TIMESTAMP ) |
---|
219 | ) { |
---|
220 | $status = "enable"; |
---|
221 | last LDAP_RESULT; |
---|
222 | } |
---|
223 | |
---|
224 | } |
---|
225 | } |
---|
226 | } |
---|
227 | |
---|
228 | write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string); |
---|
229 | |
---|
230 | } |
---|
231 | |
---|
232 | if (exists $CONFIG->{computers}{$computer}{subfolder}) { |
---|
233 | my $home_path = $CONFIG->{computers}{$computer}{subfolder}; |
---|
234 | print STDERR "\nInfo: ssh on $login\@$computer\n" if $verbose; |
---|
235 | my @ls = `/bin/ping -W 2 -c 1 $computer > /dev/null 2>&1 && { |
---|
236 | /usr/bin/nmap -p 22 -PN $computer | grep -q '^22/tcp[[:space:]]*open\b' && { |
---|
237 | /usr/bin/rsync --dry-run $login\@$computer:$home_path /tmp/backuppc-test/ || echo Error for $login\@$computer | logger -t backuppc-silzigan; |
---|
238 | }; |
---|
239 | };`; |
---|
240 | |
---|
241 | $home_path =~ s{/[^/]*$}{}; |
---|
242 | LINE: |
---|
243 | for my $line (@ls) { |
---|
244 | chomp $line; |
---|
245 | next LINE if not $line =~ m/skipping\sdirectory/; |
---|
246 | next LINE if $line =~ m/lost\+found/; |
---|
247 | next LINE if $line =~ m/administrator/; |
---|
248 | my ($user) = reverse split /\s+/, $line; |
---|
249 | $user =~ s{/$}{}; |
---|
250 | $user =~ s{.*/}{}; |
---|
251 | next LINE if not $user =~ m/^\w/; |
---|
252 | |
---|
253 | my $share = "$home_path/$user"; |
---|
254 | |
---|
255 | my @exclude_list = (); |
---|
256 | push @exclude_list, io($CONFIG->{default}{exclude})->chomp->slurp; |
---|
257 | my $exclude_string = join ",\n", map { " '$_'" } @exclude_list; |
---|
258 | |
---|
259 | my $admin = $CONFIG->{computers}{$computer}{admin} |
---|
260 | || $CONFIG->{default}{admin} |
---|
261 | || 'root'; |
---|
262 | |
---|
263 | my $status = "disable"; |
---|
264 | my $ldb = $LDAP_H->search( |
---|
265 | base => "$LDAP_BASE", |
---|
266 | filter => "(uid=$user)", |
---|
267 | attrs => ['shadowExpire', 'sambaKickoffTime'], |
---|
268 | ); |
---|
269 | if (not $ldb->code ) { |
---|
270 | |
---|
271 | LDAP_RESULT: |
---|
272 | foreach my $entry ($ldb->entries) { |
---|
273 | |
---|
274 | my $user_expire_timestamp = $entry->get_value('sambaKickoffTime') || 0; |
---|
275 | my $user_shadow_expire = $entry->get_value('shadowExpire') || 0; |
---|
276 | |
---|
277 | if ($user_shadow_expire == 0) { |
---|
278 | $status = "enable"; |
---|
279 | last LDAP_RESULT; |
---|
280 | } |
---|
281 | elsif ( |
---|
282 | ( $user_expire_timestamp ne "" ) |
---|
283 | and ( $user_expire_timestamp > $LIMIT_TIMESTAMP ) |
---|
284 | ) { |
---|
285 | $status = "enable"; |
---|
286 | last LDAP_RESULT; |
---|
287 | } |
---|
288 | |
---|
289 | } |
---|
290 | } |
---|
291 | |
---|
292 | write_config($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude_string); |
---|
293 | } |
---|
294 | } |
---|
295 | } |
---|
296 | close_ldap(); |
---|
297 | } |
---|
298 | |
---|
299 | #------------------------------------------------------------------------------- |
---|
300 | |
---|
301 | sub cmd_exclude_list { |
---|
302 | print io('/usr/lib/kont/etc/backuppc/exclude.txt')->all; |
---|
303 | } |
---|
304 | |
---|
305 | #------------------------------------------------------------------------------- |
---|
306 | |
---|
307 | sub write_config { |
---|
308 | my ($user, $computer, $share, $login, $admin, $status, $CONFIG, $exclude) = @_; |
---|
309 | my ($c) = split /\./, $computer; |
---|
310 | my $backup_name = $c . '_'. $user; |
---|
311 | |
---|
312 | return if $status eq 'disable' and not -e "/var/lib/backuppc/pc/$backup_name/backups"; |
---|
313 | |
---|
314 | print "$backup_name 0 $login $admin,$user\n" >> io($CONFIG->{default}{hosts}); |
---|
315 | |
---|
316 | my $share_string = "'$share'"; |
---|
317 | if (ref($share) eq "ARRAY") { |
---|
318 | $share_string = join ', ', map("'$_'", @{$share}); |
---|
319 | } |
---|
320 | |
---|
321 | # my $exclude = join ",\n", map { " '$_'" } io('/usr/lib/kont/etc/backuppc/exclude.txt')->chomp->slurp; |
---|
322 | |
---|
323 | print <<END > io("$CONFIG->{default}{path}/$backup_name.pl"); |
---|
324 | \$Conf{XferMethod} = 'rsync'; |
---|
325 | \$Conf{ClientNameAlias} = '$computer'; |
---|
326 | \$Conf{RsyncShareName} = [ $share_string ]; |
---|
327 | \$Conf{RsyncClientCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+'; |
---|
328 | \$Conf{RsyncClientRestoreCmd} = '\$sshPath -q -x -l $login \$host \$rsyncPath \$argList+'; |
---|
329 | \$Conf{BackupFilesExclude} = { |
---|
330 | '$share' => [ |
---|
331 | $exclude |
---|
332 | ] |
---|
333 | }; |
---|
334 | END |
---|
335 | |
---|
336 | print '$Conf{FullPeriod} = "-2";' >> io("$CONFIG->{default}{path}/$backup_name.pl") if $status eq 'disable'; |
---|
337 | symlink "$CONFIG->{default}{path}/$backup_name.pl", "/etc/backuppc/pc/$backup_name.pl"; |
---|
338 | } |
---|
339 | |
---|
340 | #------------------------------------------------------------------------------- |
---|
341 | |
---|
342 | sub update_hosts { |
---|
343 | io->catfile('/etc/backuppc/hosts.main') > io('/etc/backuppc/hosts.order'); |
---|
344 | my @hosts = io('/etc/backuppc/hosts.main')->chomp->slurp; |
---|
345 | |
---|
346 | push @hosts, io('/etc/backuppc/hosts.oldcomputer')->chomp->slurp; |
---|
347 | |
---|
348 | my $search_host_file = File::Finder->type('f')->name('hosts'); |
---|
349 | for my $host_file (File::Finder->eval($search_host_file)->in('/etc/backuppc/auto')) { |
---|
350 | print "#\n# $host_file\n#\n" >> io('/etc/backuppc/hosts.order'); |
---|
351 | io->catfile("$host_file") >> io('/etc/backuppc/hosts.order'); |
---|
352 | push @hosts, io("$host_file")->chomp->slurp; |
---|
353 | } |
---|
354 | my ($first, @host) = grep(/./, grep(!/^#/, @hosts)); |
---|
355 | print "$first\n" > io('/etc/backuppc/hosts'); |
---|
356 | print "$_\n" >> io('/etc/backuppc/hosts') for shuffle(@host); |
---|
357 | } |
---|
358 | |
---|
359 | #------------------------------------------------------------------------------- |
---|
360 | |
---|
361 | sub cmd_init_db { |
---|
362 | my $cfg = { |
---|
363 | computers => { |
---|
364 | 'machine36.hmg.priv' => { |
---|
365 | login => 'root', |
---|
366 | share => '/home/users/%u', |
---|
367 | status => 'auto', |
---|
368 | users => { |
---|
369 | 'dupond' => { |
---|
370 | share => [ '/home/users/dupond', '/var/www/dupond' ], |
---|
371 | status => 'enable', |
---|
372 | }, |
---|
373 | 'durand' => { |
---|
374 | share => '/home/users/durand', |
---|
375 | }, |
---|
376 | }, |
---|
377 | }, |
---|
378 | }, |
---|
379 | }; |
---|
380 | |
---|
381 | YAML::DumpFile("/tmp/template-backuppc.yaml", $cfg); |
---|
382 | } |
---|
383 | |
---|
384 | #------------------------------------------------------------------------------- |
---|
385 | |
---|
386 | sub cmd_help { |
---|
387 | print <<'END'; |
---|
388 | backuppc-silzigan - cut into small pieces a computer configuration for backuppc |
---|
389 | |
---|
390 | backuppc-silzigan init |
---|
391 | backuppc-silzigan generate config_file.yaml [--verbose] |
---|
392 | backuppc-silzigan update [--verbose] |
---|
393 | backuppc-silzigan help |
---|
394 | backuppc-silzigan version |
---|
395 | backuppc-silzigan exclude-list |
---|
396 | END |
---|
397 | return; |
---|
398 | } |
---|
399 | |
---|
400 | #------------------------------------------------------------------------------- |
---|
401 | |
---|
402 | sub cmd_version { |
---|
403 | print <<'END'; |
---|
404 | backuppc-silzigan - cut into small pieces a computer configuration for backuppc |
---|
405 | Author: Gabriel Moreau <Gabriel.Moreau@univ-grenoble-alpes.fr> |
---|
406 | Copyright (C) 2011-2019, LEGI UMR 5519 / CNRS UGA G-INP, Grenoble, France |
---|
407 | |
---|
408 | $Id: backuppc-silzigan..host.legilnx32 3821 2013-12-20 08:03:14Z g7moreau $ |
---|
409 | END |
---|
410 | return; |
---|
411 | } |
---|