3 # Copyright (c) 2005-2008 Zmanda Inc. All Rights Reserved.
5 # This program is free software; you can redistribute it and/or modify it
6 # under the terms of the GNU General Public License version 2 as published
7 # by the Free Software Foundation.
9 # This program is distributed in the hope that it will be useful, but
10 # WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
11 # or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
14 # You should have received a copy of the GNU General Public License along
15 # with this program; if not, write to the Free Software Foundation, Inc.,
16 # 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 # Contact information: Zmanda Inc, 465 S Mathlida Ave, Suite 300
19 # Sunnyvale, CA 94086, USA, or: http://www.zmanda.com
26 use Socket; # for gethostbyname
28 my $confdir="@CONFIG_DIR@";
29 my $tmpdir="@AMANDA_DBGDIR@";
31 my $prefix="@prefix@";
32 my $localstatedir="@localstatedir@";
33 my $amandahomedir="$localstatedir/lib/amanda";
35 my $amanda_user="@CLIENT_LOGIN@";
36 my $amanda_group="disk";
37 my $def_root_user="root";
38 my $def_dumptype="user-tar";
42 # Get the version suffix.
43 my $USE_VERSION_SUFFIXES = '@USE_VERSION_SUFFIXES@';
45 if ( $USE_VERSION_SUFFIXES =~ /^yes$/i ) {
52 print "\t\t--config <config> Required. Ex: DailySet1\n";
53 print "\t\t--client <FQDN-name> Required. Ex: server.zmanda.com\n";
54 print "\t\t--diskdev <directory> Required. Ex: /home\n";
55 print "\t\t--m Modify exisiting entry\n";
56 print "\t\t[--dumptype <dumptype> Default: user-tar]\n";
57 print "\t\t[--includefile <string> glob expression of file(s) to include]\n";
58 print "\t\t[--includelist <file> file contains glob expressions to include]\n";
59 print "\t\t[ specify either --includefile or --includelist ]\n";
60 print "\t\t[--excludefile <string> glob expression of file(s) to exclude]\n";
61 print "\t\t[--excludelist <file> file contains glob expressions to exclude]\n";
62 print "\t\t[ specify either --excludefile or --excludelist ]\n";
63 print "\t\t[--user <username> name of user running amrecover on the client]\n";
64 print "\t\t[--auth <string> authentication used when running amrecover]\n";
65 print "\t\t[--gnutar_list_dir <string> directory where gnutar keep its state file on the client]\n";
66 print "\t\t[--amandates <string> file where amanda keep the date of dumplevel on the client]\n";
67 print "\t\t[--batch batch mode used when copying file to client]\n";
68 print "\t\t[--no-client-update do not update files on the client";
69 print "\t\t[--help]\n";
73 for $fh ( STDOUT, LOG ) {
86 ( $user eq $amanda_user ) ||
87 die ("ERROR: $0 must be run by $amanda_user\n");
91 # alphabetics, numerics, and underscores,
92 # hyphen, at sign, dot and "/" are ok
95 if ( $arg =~ /^([-\@\/\w.]+)$/ ) {
98 return 1; # bad, tainted input
103 # modify existing entry.
104 # only got here if -m is used and entry is found.
106 my $open_seen=0; # '{' is seen
107 my $include_done=0; # original include line is parsed
108 my $exclude_done=0; # original exclude line is parsed
109 my $ok=0; # 1 if target entry is found
111 @ARGV = ("$confdir/$config/disklist");
112 $^I = ".tmp"; # allow inplace editing
114 my ($one, $two, $three ) = split(/\s+/, $_);
116 # if include or exclude is not previously there,
117 # take care of them here
120 if ( $include_done==0 && $ok ) {
121 print "include list \"$includelist\"\n" if ( $includelist );
122 print "include file \"$includefile\"\n" if ( $includefile );
124 if ( $exclude_done==0 && $ok ) {
125 print "exclude list \"$excludelist\"\n" if ( $excludelist );
126 print "exclude file \"$excludefile\"\n" if ( $excludefile );
128 $ok=0; # reset, done with one entry
131 # take care of entry that has '{'
132 if ( $open_seen==1 ) {
133 if ( !$two && !$three ) { # inside {, dumptype line has 1 field only
134 s/$one/$dumptype/ if ( $dumptype );
135 } elsif ( $two && $three ) { # inside {, include/exclude line
136 if ( $one eq "include" ) {
137 if ( $includelist ) {
138 s/$two.*$/list "$includelist"/;
139 } elsif ( $includefile ) {
140 s/$two.*$/file "$includefile"/;
144 if ( $one eq "exclude" ) {
145 if ( $excludelist ) {
146 s/$two.*$/list "$excludelist"/;
147 } elsif ( $excludefile ) {
148 s/$two.*$/file "$excludefile"/;
156 # entry which previously doesn't have include/exclude
157 if (( $one eq $client ) && ($two eq $diskdev) ) {
159 if ( $three && ($three ne "{") ) {
160 if ( $sp_diskfile==1 ) { #previously don't have include/exclude
161 $three = $dumptype if ( $dumptype );
162 $includeline="include list \"$includelist\"" if ( $includelist );
163 $includeline="include file \"$includefile\"" if ( $includefile );
164 $excludeline="exclude list \"$excludelist\"\n" if ( $excludelist );
165 $excludeline="exclude file \"$excludefile\"\n" if ( $excludefile );
166 s/$three/{\n$three\n$includeline\n$excludeline}/;
168 s/$three/$dumptype/ if ( $dumptype ); #easy one, just replace dumptype.
169 $ok=0; #done with one entry
177 unlink("$confdir/$config/disklist.tmp");
186 $ret= GetOptions ( "config=s"=>\$config,
187 "client=s"=>\$client,
188 "diskdev=s"=>\$diskdev,
189 "dumptype=s"=>\$dumptype,
190 "includefile=s"=>\$includefile,
191 "includelist=s"=>\$includelist,
192 "excludefile=s"=>\$excludefile,
193 "excludelist=s"=>\$excludelist,
194 "user=s"=>\$root_user,
196 "gnutar_list_dir=s"=>\$tarlist,
197 "amandates=s"=>\$amandates,
200 "no-client-update!"=>\$no_client_update,
216 unless (defined $config && defined $client && defined $diskdev ) {
217 print STDERR "--config, --client and --diskdev are required.\n";
222 die ("ERROR: Invalid data in config.\n") if is_tainted($config);
223 die ("ERROR: Invalid data in client.\n") if is_tainted($client);
227 if ( defined $includefile && defined $includelist ) {
228 print STDERR "Specify either --includefile or --includelist, not both.\n";
233 if ( defined $excludefile && defined $excludelist ) {
234 print STDERR "Specify either --excludefile or --excludelist, not both.\n";
239 $oldPATH = $ENV{'PATH'};
240 $ENV{'PATH'} = "/usr/bin:/usr/sbin:/sbin:/bin:/usr/ucb"; # force known path
241 $date=`date +%Y%m%d%H%M%S`;
243 my $logfile="$tmpdir/amaddclient.$date.debug";
246 open (LOG, ">$logfile") || die "ERROR: Cannot create logfile : $!\n";
247 print STDOUT "Logging to $logfile\n";
249 my $lhost=`hostname`;
251 # get our own canonical name, if possible (we don't sweat the IPv6 stuff here)
252 my $host=(gethostbyname($lhost))[0];
255 $host = $lhost; #gethostbyname() failed, go with hostname output
262 # make sure dumptype is defined in dumptypes or amanda.conf file
264 if ( defined $dumptype ) {
265 for $fhs ( "$confdir/template.d/dumptypes", "$confdir/$config/amanda.conf" ) {
266 open (DTYPE, $fhs) ||
267 &log_and_die ("ERROR: Cannot open $fhs file : $!\n");
269 if (/^\s*define\s*dumptype\s*$dumptype\s*{/) {
278 &log_and_die ("ERROR: $dumptype not defined in $confdir/template.d/dumptypes or $confdir/$config/amanda.conf\n");
282 # create disklist file
283 unless ( -e "$confdir/$config" ) {
284 &log_and_die ("ERROR: $confdir/$config not found\n");
287 if (defined $includefile || defined $includelist
288 || defined $excludefile || defined $excludelist) {
292 unless ( -e "$confdir/$config/disklist" ) { # create it if necessary
293 open (DLE, ">$confdir/$config/disklist") ||
294 &log_and_die ("ERROR: Cannot create $confdir/$config/disklist file : $!\n");
295 print DLE "#This file is generated by amaddclient.\n";
296 print DLE "#Don't edit it manually, otherwise, 'amaddclient -m ...' might not work\n";
299 open (DLE, "+<$confdir/$config/disklist") # open for read/write
300 || &log_and_die ("ERROR: Cannot open $confdir/$config/disklist file : $!\n");
302 my ($lclient, $ldiskdev, $dontcare ) = split(/\s+/, $_);
303 if (( $lclient eq $client ) && ($ldiskdev eq $diskdev) ) {
309 # if found and -m, do modification and exit
310 if ( defined $mod ) {
314 &log_and_die ("ERROR: $client $diskdev not found, cannot modify\n");
318 unless ( defined $dumptype ) {
319 $dumptype=$def_dumptype;
322 &mprint("$confdir/$config/disklist has '$client $diskdev ...' entry, file not updated\n"); }
324 print DLE "$client $diskdev ";
325 print DLE "{\n$dumptype\n" if ($sp_diskfile);
326 if ( defined $includefile ) {
327 print DLE "include file \"$includefile\"\n";
329 elsif ( defined $includelist ) {
330 print DLE "include list \"$includelist\"\n";
332 if ( defined $excludefile ) {
333 print DLE "exclude file \"$excludefile\"\n";
335 elsif ( defined $excludelist ) {
336 print DLE "exclude list \"$excludelist\"\n";
338 print DLE "}\n" if ($sp_diskfile);
340 print DLE " $dumptype\n" if ($sp_diskfile==0);
341 &mprint ("$confdir/$config/disklist updated\n");
346 # update .amandahosts on server and client
348 my $scp_opt1="-p"; # p: preserve mode
349 my $scp_opt2="-o ConnectTimeout=15"; #timeout after 15 seconds
351 my $ssh_opt="-x"; # -x as a placeholder, otherwise ssh complains
353 my $client_conf_dir="$confdir/$config";
354 my $amanda_client_conf="$client_conf_dir/amanda-client.conf";
355 my $file="$amandahomedir/.amandahosts";
356 my $client_file="$amandahomedir/amanda-client.conf-$client";
358 if ( defined $batch ) {
360 $ssh_opt="-o BatchMode=yes";
363 &mprint ("updating $file on $host\n");
364 unless ( defined $root_user ) {
365 $root_user=$def_root_user;
368 open (HFILE, "+<$file")
369 || &log_and_die ("ERROR: Cannot open $file : $!\n");
372 if (/^\s*$client\s*$root_user\s*amindexd\s*amidxtaped\s/) {
378 &mprint ("$file contains $client $root_user, file not updated\n") ; }
380 print HFILE "$client $root_user amindexd amidxtaped\n";
384 # update client .amandahosts
385 unless ( $no_client_update ) {
387 &mprint ("Attempting to update $file on $client\n");
389 chdir ("$amandahomedir");
390 system "$scp", "$scp_opt1", "$scp_opt2", "$amanda_user\@$client:$file", "$file.tmp";
391 $exit_value = $? >> 8;
392 if ( $exit_value !=0 ) {
393 &mprint ("WARNING: $scp from $client not successful.\n");
394 &mprint ("Check $client:$file file.\n");
395 &mprint ("If entry '$host $amanda_user' is not present,\n");
396 &mprint ("append the entry to the file manually.\n");
400 unless ( -e "$file.tmp" ) {
401 &mprint ("WARNING: $file.tmp not found\n"); }
403 open (CFILE, "+<$file.tmp")
404 || &log_and_die ("ERROR: Cannot open $file.tmp file : $!\n");
406 if (/^\s*$host\s*$amanda_user\s*amdump\s/) {
412 &mprint ("$file contains $host $amanda_user, file not updated\n") ; }
414 print CFILE "$host $amanda_user amdump\n";
417 #make sure permission mode is correct
418 chmod (0600, "$file.tmp");
419 system "$scp", "$scp_opt1", "$scp_opt2", "$file.tmp", "$client:$file";
420 $exit_value = $? >> 8;
421 if ( $exit_value !=0 ) {
422 &mprint ("WARNING: $scp to $client not successful.\n");
423 &mprint ("Check $client:$file file.\n");
424 &mprint ("If entry '$host $amanda_user amdump' is not present,\n");
425 &mprint ("append the entry to the file manually.\n");
430 unlink ("$file.tmp") || &mprint("unlink $file.tmp failed: $!\n");
431 &mprint ("$client:$file updated successfully\n");
435 # done updating client .amandahosts
437 #create amanda-client.conf and scp over to client
439 unless ( $no_client_update ) {
440 &mprint ("Creating amanda-client.conf for $client\n");
442 $auth="bsdtcp" unless ( defined $auth );
444 open (ACFILE, ">$client_file") || &log_and_die ("ERROR: Cannot open $client_file file : $!\n");
445 print ACFILE "#amanda-client.conf - Amanda client configuration file.\n";
446 print ACFILE "conf \"$config\"\n";
447 print ACFILE "index_server \"$host\"\n";
448 print ACFILE "tape_server \"$host\"\n";
449 print ACFILE "# auth - authentication scheme to use between server and client.\n";
450 print ACFILE "# Valid values are 'bsdtcp' or 'ssh'\n";
451 print ACFILE "auth \"$auth\"\n";
452 print ACFILE "# ssh keys file if ssh auth is used\n";
453 print ACFILE "ssh_keys \"$amandahomedir/.ssh/id_rsa_amrecover\"\n";
454 print ACFILE "gnutar_list_dir \"$tarlist\"\n" if ( defined $tarlist );
455 print ACFILE "amandates \"$amandates\"\n" if ( defined $amandates );
458 &mprint ("Creating $client_conf_dir on $client\n");
459 system "$ssh", "$ssh_opt", "$amanda_user\@$client", "$mkdir", "$client_conf_dir";
460 $exit_value = $? >> 8;
461 if ( $exit_value !=0 ) {
462 &mprint ("WARNING: Cannot create $client_conf_dir on $client\n");
463 &mprint ("Please copy $client_file to $client manually\n");
465 chmod (0600, "$client_file");
466 system "$scp", "$scp_opt1", "$scp_opt2", "$client_file", "$amanda_user\@$client:$amanda_client_conf";
467 $exit_value = $? >> 8;
468 if ( $exit_value !=0 ) {
469 &mprint ("WARNING: Cannot copy $client_file to $client\n");
470 &mprint ("Please copy $client_file to $client:$client_conf_dir manually\n");
472 &mprint ("Copy $client_file to $client successfully\n");
473 unlink($client_file);
478 #create gnutar_list_dir
479 if ( defined $tarlist && !defined $no_client_update ) {
480 system "$ssh", "$ssh_opt", "$amanda_user\@$client", "$mkdir", "$gnutar_list_dir";
481 $exit_value = $? >> 8;
482 if ( $exit_value !=0 ) {
483 &mprint ("WARNING: Cannot create $gnutar_list_dir on $client\n");
484 &mprint ("Please create $gnutar_list_dir on $client manually\n");
486 &mprint ("$client_file created on $client successfully\n");
490 &mprint ("File /var/lib/amanda/example/xinetd.amandaclient contains the latest Amanda client daemon configuration.\n");
491 &mprint ("Please merge it to /etc/xinetd.d/amandaclient.\n");
493 $ENV{'PATH'} = $oldPATH;