* Various changes for 3.0.0beta1
[BackupPC.git] / configure.pl
1 #!/bin/perl
2 #============================================================= -*-perl-*-
3 #
4 # configure.pl: Configuration and installation program for BackupPC
5 #
6 # DESCRIPTION
7 #
8 #   This script should be run as root:
9 #
10 #        perl configure.pl
11 #
12 #   To read about the command-line options for this configure script:
13 #
14 #        perldoc configure.pl
15 #
16 #   The installation steps are described as the script runs.
17 #
18 # AUTHOR
19 #   Craig Barratt <cbarratt@users.sourceforge.net>
20 #
21 # COPYRIGHT
22 #   Copyright (C) 2001-2006  Craig Barratt
23 #
24 #   This program is free software; you can redistribute it and/or modify
25 #   it under the terms of the GNU General Public License as published by
26 #   the Free Software Foundation; either version 2 of the License, or
27 #   (at your option) any later version.
28 #
29 #   This program is distributed in the hope that it will be useful,
30 #   but WITHOUT ANY WARRANTY; without even the implied warranty of
31 #   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
32 #   GNU General Public License for more details.
33 #
34 #   You should have received a copy of the GNU General Public License
35 #   along with this program; if not, write to the Free Software
36 #   Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
37 #
38 #========================================================================
39 #
40 # Version 3.0.0alpha, released 8 Jul 2006.
41 #
42 # See http://backuppc.sourceforge.net.
43 #
44 #========================================================================
45
46 use strict;
47 no  utf8;
48 use vars qw(%Conf %OrigConf);
49 use lib "./lib";
50
51 my @Packages = qw(File::Path File::Spec File::Copy DirHandle Digest::MD5
52                   Data::Dumper Getopt::Std Getopt::Long Pod::Usage
53                   BackupPC::Lib BackupPC::FileZIO);
54
55 foreach my $pkg ( @Packages ) {
56     eval "use $pkg";
57     next if ( !$@ );
58     if ( $pkg =~ /BackupPC/ ) {
59         die <<EOF;
60
61 BackupPC cannot find the package $pkg, which is included in the
62 BackupPC distribution.  This probably means you did not cd to the
63 unpacked BackupPC distribution before running configure.pl, eg:
64
65     cd BackupPC-__VERSION__
66     ./configure.pl
67
68 Please try again.
69
70 EOF
71     }
72     die <<EOF;
73
74 BackupPC needs the package $pkg.  Please install $pkg
75 before installing BackupPC.
76
77 EOF
78 }
79
80 my %opts;
81 $opts{"set-perms"} = 1;
82 if ( !GetOptions(
83             \%opts,
84             "batch",
85             "backuppc-user=s",
86             "bin-path=s%",
87             "cgi-dir=s",
88             "compress-level=i",
89             "config-path=s",
90             "config-dir=s",
91             "data-dir=s",
92             "dest-dir=s",
93             "fhs!",
94             "help|?",
95             "hostname=s",
96             "html-dir=s",
97             "html-dir-url=s",
98             "install-dir=s",
99             "log-dir=s",
100             "man",
101             "set-perms!",
102             "uid-ignore!",
103         ) || @ARGV ) {
104     pod2usage(2);
105 }
106 pod2usage(1) if ( $opts{help} );
107 pod2usage(-exitstatus => 0, -verbose => 2) if $opts{man};
108
109 my $DestDir = $opts{"dest-dir"};
110 $DestDir = "" if ( $DestDir eq "/" );
111
112 if ( !$opts{"uid-ignore"} && $< != 0 ) {
113     print <<EOF;
114
115 This configure script should be run as root, rather than uid $<.
116 Provided uid $< has sufficient permissions to create the data and
117 install directories, then it should be ok to proceed.  Otherwise,
118 please quit and restart as root.
119
120 EOF
121     exit(1) if ( prompt("--> Do you want to continue?",
122                        "y") !~ /y/i );
123     exit(1) if ( $opts{batch} && !$opts{"uid-ignore"} );
124 }
125
126 #
127 # Whether we use the file system hierarchy conventions or not.
128 # Older versions did not.  BackupPC used to be installed in
129 # two main directories (in addition to CGI and html pages)
130 #
131 #    TopDir       which includes subdirs conf, log, pc, pool, cpool
132 #                
133 #    InstallDir   which includes subdirs bin, lib, doc
134 #
135 # With FHS enabled (which is the default for new installations)
136 # the config files move to /etc/BackupPC and log files to /var/log:
137 #
138 #    /etc/BackupPC/config.pl  main config file (was $TopDir/conf/config.pl)
139 #    /etc/BackupPC/hosts      hosts file (was $TopDir/conf/hosts)
140 #    /etc/BackupPC/pc/HOST.pl per-pc config file (was $TopDir/pc/HOST/config.pl)
141 #    /var/log/BackupPC        log files (was $TopDir/log)
142 #    /var/log/BackupPC        Pid, status and email info (was $TopDir/log)
143 #
144
145 #
146 # Check if this is an upgrade, in which case read the existing
147 # config file to get all the defaults.
148 #
149 my $ConfigPath = "";
150 my $ConfigFileOK = 1;
151 while ( 1 ) {
152     if ( $ConfigFileOK && -f "/etc/BackupPC/config.pl" ) {
153         $ConfigPath = "/etc/BackupPC/config.pl";
154         $opts{fhs} = 1 if ( !defined($opts{fhs}) );
155         print <<EOF;
156
157 Found /etc/BackupPC/config.pl, so this is an upgrade of an
158 existing BackupPC installation.  We will verify some existing
159 information, but you will probably not need to make any
160 changes - just hit ENTER to each question.
161 EOF
162     } else {
163         print <<EOF;
164
165 Is this a new installation or upgrade for BackupPC?  If this is
166 an upgrade please tell me the full path of the existing BackupPC
167 configuration file (eg: /etc/BackupPC/config.pl).  Otherwise, just
168 hit return.
169
170 EOF
171         $ConfigPath = prompt("--> Full path to existing main config.pl",
172                              $ConfigPath,
173                              "config-path");
174     }
175     last if ( $ConfigPath eq ""
176             || ($ConfigPath =~ /^\// && -f $ConfigPath && -w $ConfigPath) );
177     my $problem = "is not an absolute path";
178     $problem = "is not writable"        if ( !-w $ConfigPath );
179     $problem = "is not readable"        if ( !-r $ConfigPath );
180     $problem = "is not a regular file"  if ( !-f $ConfigPath );
181     $problem = "doesn't exist"          if ( !-e $ConfigPath );
182     print("The file '$ConfigPath' $problem.\n");
183     if ( $opts{batch} ) {
184         print("Need to specify a valid --config-path for upgrade\n");
185         exit(1);
186     }
187     $ConfigFileOK = 0;
188 }
189 $opts{fhs} = 1 if ( !defined($opts{fhs}) && $ConfigPath eq "" );
190 $opts{fhs} = 0 if ( !defined($opts{fhs}) );
191
192 my $bpc;
193 if ( $ConfigPath ne "" && -r $ConfigPath ) {
194     (my $confDir = $ConfigPath) =~ s{/[^/]+$}{};
195     die("BackupPC::Lib->new failed\n")
196             if ( !($bpc = BackupPC::Lib->new(".", ".", $confDir, 1)) );
197     %Conf = $bpc->Conf();
198     %OrigConf = %Conf;
199     if ( !$opts{fhs} ) {
200         ($Conf{TopDir} = $ConfigPath) =~ s{/[^/]+/[^/]+$}{}
201                     if ( $Conf{TopDir} eq '' );
202         $bpc->{LogDir} = $Conf{LogDir}  = "$Conf{TopDir}/log"
203                     if ( $Conf{LogDir} eq '' );
204     }
205     $bpc->{ConfDir} = $Conf{ConfDir} = $confDir;
206     my $err = $bpc->ServerConnect($Conf{ServerHost}, $Conf{ServerPort}, 1);
207     if ( $err eq "" ) {
208         print <<EOF;
209
210 BackupPC is running on $Conf{ServerHost}.  You need to stop BackupPC
211 before you can upgrade the code.  Depending upon your installation,
212 you could run "/etc/init.d/backuppc stop".
213
214 EOF
215         exit(1);
216     }
217 }
218
219 #
220 # Create defaults for FHS setup
221 #
222 if ( $opts{fhs} ) {
223     $Conf{TopDir}       ||= "/data/BackupPC";
224     $Conf{ConfDir}      ||= $opts{"config-dir"} || "/etc/BackupPC";
225     $Conf{InstallDir}   ||= "/usr/local/BackupPC";
226     $Conf{LogDir}       ||= $opts{"log-dir"} || "/var/log/BackupPC";
227 }
228
229 #
230 # These are the programs whose paths we need to find
231 #
232 my %Programs = (
233     perl           => "PerlPath",
234     'gtar/tar'     => "TarClientPath",
235     smbclient      => "SmbClientPath",
236     nmblookup      => "NmbLookupPath",
237     rsync          => "RsyncClientPath",
238     ping           => "PingPath",
239     df             => "DfPath",
240     'ssh/ssh2'     => "SshPath",
241     sendmail       => "SendmailPath",
242     hostname       => "HostnamePath",
243     split          => "SplitPath",
244     par2           => "ParPath",
245     cat            => "CatPath",
246     gzip           => "GzipPath",
247     bzip2          => "Bzip2Path",
248 );
249
250 foreach my $prog ( sort(keys(%Programs)) ) {
251     my $path;
252     foreach my $subProg ( split(/\//, $prog) ) {
253         $path = FindProgram("$ENV{PATH}:/bin:/usr/bin:/sbin:/usr/sbin",
254                             $subProg) if ( !length($path) );
255     }
256     $Conf{$Programs{$prog}} = $path if ( !length($Conf{$Programs{$prog}}) );
257 }
258
259 while ( 1 ) {
260     print <<EOF;
261
262 I found the following locations for these programs:
263
264 EOF
265     foreach my $prog ( sort(keys(%Programs)) ) {
266         printf("    %-12s => %s\n", $prog, $Conf{$Programs{$prog}});
267     }
268     print "\n";
269     last if (prompt('--> Are these paths correct?', 'y') =~ /^y/i);
270     foreach my $prog ( sort(keys(%Programs)) ) {
271         $Conf{$Programs{$prog}} = prompt("--> $prog path",
272                                          $Conf{$Programs{$prog}});
273     }
274 }
275
276 my $Perl56 = system($Conf{PerlPath}
277                         . q{ -e 'exit($^V && $^V ge v5.6.0 ? 1 : 0);'});
278
279 if ( !$Perl56 ) {
280     print <<EOF;
281
282 BackupPC needs perl version 5.6.0 or later.  $Conf{PerlPath} appears
283 to be an older version.  Please upgrade to a newer version of perl
284 and re-run this configure script.
285
286 EOF
287     exit(1);
288 }
289
290 print <<EOF;
291
292 Please tell me the hostname of the machine that BackupPC will run on.
293
294 EOF
295 chomp($Conf{ServerHost} = `$Conf{HostnamePath}`)
296         if ( defined($Conf{HostnamePath}) && !defined($Conf{ServerHost}) );
297 $Conf{ServerHost} = prompt("--> BackupPC will run on host",
298                            $Conf{ServerHost},
299                            "hostname");
300
301 print <<EOF;
302
303 BackupPC should run as a dedicated user with limited privileges.  You
304 need to create a user.  This user will need read/write permission on
305 the main data directory and read/execute permission on the install
306 directory (these directories will be setup shortly).
307
308 The primary group for this user should also be chosen carefully.
309 The data directories and files will have group read permission,
310 so group members can access backup files.
311
312 EOF
313 my($name, $passwd, $Uid, $Gid);
314 while ( 1 ) {
315     $Conf{BackupPCUser} = prompt("--> BackupPC should run as user",
316                                  $Conf{BackupPCUser} || "backuppc",
317                                  "backuppc-user");
318     if ( $opts{"set-perms"} ) {
319         ($name, $passwd, $Uid, $Gid) = getpwnam($Conf{BackupPCUser});
320         last if ( $name ne "" );
321         print <<EOF;
322
323 getpwnam() says that user $Conf{BackupPCUser} doesn't exist.  Please
324 check the name and verify that this user is in the passwd file.
325
326 EOF
327         exit(1) if ( $opts{batch} );
328     }
329 }
330
331 print <<EOF;
332
333 Please specify an install directory for BackupPC.  This is where the
334 BackupPC scripts, library and documentation will be installed.
335
336 EOF
337
338 while ( 1 ) {
339     $Conf{InstallDir} = prompt("--> Install directory (full path)",
340                                $Conf{InstallDir},
341                                "install-dir");
342     last if ( $Conf{InstallDir} =~ /^\// );
343     if ( $opts{batch} ) {
344         print("Need to specify --install-dir for new installation\n");
345         exit(1);
346     }
347 }
348
349 print <<EOF;
350
351 Please specify a data directory for BackupPC.  This is where the
352 all the PC backup data is stored.  This file system needs to be
353 big enough to accommodate all the PCs you expect to backup (eg:
354 at least several GB per machine).
355
356 EOF
357
358 while ( 1 ) {
359     $Conf{TopDir} = prompt("--> Data directory (full path)",
360                            $Conf{TopDir},
361                            "data-dir");
362     last if ( $Conf{TopDir} =~ /^\// );
363     if ( $opts{batch} ) {
364         print("Need to specify --data-dir for new installation\n");
365         exit(1);
366     }
367 }
368
369 $Conf{CompressLevel} = $opts{"compress-level"}
370                             if ( defined($opts{"compress-level"}) );
371
372 if ( !defined($Conf{CompressLevel}) ) {
373     $Conf{CompressLevel} = BackupPC::FileZIO->compOk ? 3 : 0;
374     if ( $ConfigPath eq "" && $Conf{CompressLevel} ) {
375         print <<EOF;
376
377 BackupPC can compress pool files, providing around a 40% reduction in pool
378 size (your mileage may vary). Specify the compression level (0 turns
379 off compression, and 1 to 9 represent good/fastest to best/slowest).
380 The recommended values are 0 (off) or 3 (reasonable compression and speed).
381 Increasing the compression level to 5 will use around 20% more cpu time
382 and give perhaps 2-3% more compression.
383
384 EOF
385     } elsif ( $ConfigPath eq "" ) {
386         print <<EOF;
387
388 BackupPC can compress pool files, but it needs the Compress::Zlib
389 package installed (see www.cpan.org). Compression will provide around a
390 40% reduction in pool size, at the expense of cpu time.  You can leave
391 compression off and run BackupPC without compression, in which case you
392 should leave the compression level at 0 (which means off).  You could
393 install Compress::Zlib and turn compression on later, but read the
394 documentation first about how to do this.  Or the better choice is
395 to quit, install Compress::Zlib, and re-run configure.pl.
396
397 EOF
398     } elsif ( $Conf{CompressLevel} ) {
399         $Conf{CompressLevel} = 0;
400         print <<EOF;
401
402 BackupPC now supports pool file compression.  Since you are upgrading
403 BackupPC you probably have existing uncompressed backups.  You have
404 several choices if you want to turn on compression.  You can run
405 the script BackupPC_compressPool to convert everything to compressed
406 form.  Or you can simply turn on compression, so that new backups
407 will be compressed.  This will increase the pool storage requirement,
408 since both uncompressed and compressed copies of files will be stored.
409 But eventually the old uncompressed backups will expire, recovering
410 the pool storage.  Please see the documentation for more details.
411
412 If you are not sure what to do, leave the Compression Level at 0,
413 which disables compression.  You can always read the documentation
414 and turn it on later.
415
416 EOF
417     } else {
418         $Conf{CompressLevel} = 0;
419         print <<EOF;
420
421 BackupPC now supports pool file compression, but it needs the
422 Compress::Zlib module (see www.cpan.org).  For now, leave
423 the compression level set at 0 to disable compression.  If you
424 want you can install Compress::Zlib and turn compression on.
425 Please see the documentation for more details about converting
426 old backups to compressed form.
427
428 EOF
429     }
430     while ( 1 ) {
431         $Conf{CompressLevel}
432                     = prompt("--> Compression level", $Conf{CompressLevel});
433         last if ( $Conf{CompressLevel} =~ /^\d+$/ );
434     }
435 }
436
437 print <<EOF;
438
439 BackupPC has a powerful CGI perl interface that runs under Apache.
440 A single executable needs to be installed in a cgi-bin directory.
441 This executable needs to run as set-uid $Conf{BackupPCUser}, or
442 it can be run under mod_perl with Apache running as user $Conf{BackupPCUser}.
443
444 Leave this path empty if you don't want to install the CGI interface.
445
446 EOF
447
448 while ( 1 ) {
449     $Conf{CgiDir} = prompt("--> CGI bin directory (full path)",
450                            $Conf{CgiDir},
451                            "cgi-dir");
452     last if ( $Conf{CgiDir} =~ /^\// || $Conf{CgiDir} eq "" );
453     if ( $opts{batch} ) {
454         print("Need to specify --cgi-dir for new installation\n");
455         exit(1);
456     }
457 }
458
459 if ( $Conf{CgiDir} ne "" ) {
460
461     print <<EOF;
462
463 BackupPC's CGI script needs to display various GIF images that
464 should be stored where Apache can serve them.  They should be
465 placed somewher under Apache's DocumentRoot.  BackupPC also
466 needs to know the URL to access these images.  Example:
467
468     Apache image directory:  /usr/local/apache/htdocs/BackupPC
469     URL for image directory: /BackupPC
470
471 The URL for the image directory should start with a slash.
472
473 EOF
474     while ( 1 ) {
475         $Conf{CgiImageDir} = prompt("--> Apache image directory (full path)",
476                                     $Conf{CgiImageDir},
477                                     "html-dir");
478         last if ( $Conf{CgiImageDir} =~ /^\// );
479         if ( $opts{batch} ) {
480             print("Need to specify --html-dir for new installation\n");
481             exit(1);
482         }
483     }
484     while ( 1 ) {
485         $Conf{CgiImageDirURL} = prompt("--> URL for image directory (omit http://host; starts with '/')",
486                                         $Conf{CgiImageDirURL},
487                                         "html-dir-url");
488         last if ( $Conf{CgiImageDirURL} =~ /^\// );
489         if ( $opts{batch} ) {
490             print("Need to specify --html-dir-url for new installation\n");
491             exit(1);
492         }
493     }
494 }
495
496 print <<EOF;
497
498 Ok, we're about to:
499
500   - install the binaries, lib and docs in $Conf{InstallDir},
501   - create the data directory $Conf{TopDir},
502   - create/update the config.pl file $Conf{ConfDir}/config.pl,
503   - optionally install the cgi-bin interface.
504
505 EOF
506
507 exit unless prompt("--> Do you want to continue?", "y") =~ /y/i;
508
509 #
510 # Create install directories
511 #
512 foreach my $dir ( qw(bin doc
513                      lib/BackupPC/CGI
514                      lib/BackupPC/Config
515                      lib/BackupPC/Lang
516                      lib/BackupPC/Storage
517                      lib/BackupPC/Xfer
518                      lib/BackupPC/Zip
519                  ) ) {
520     next if ( -d "$DestDir$Conf{InstallDir}/$dir" );
521     mkpath("$DestDir$Conf{InstallDir}/$dir", 0, 0755);
522     if ( !-d "$DestDir$Conf{InstallDir}/$dir"
523             || !my_chown($Uid, $Gid, "$DestDir$Conf{InstallDir}/$dir") ) {
524         die("Failed to create or chown $DestDir$Conf{InstallDir}/$dir\n");
525     } else {
526         print("Created $DestDir$Conf{InstallDir}/$dir\n");
527     }
528 }
529
530 #
531 # Create CGI image directory
532 #
533 foreach my $dir ( ($Conf{CgiImageDir}) ) {
534     next if ( $dir eq "" || -d $dir );
535     mkpath("$DestDir$dir", 0, 0755);
536     if ( !-d "$DestDir$dir" || !my_chown($Uid, $Gid, "$DestDir$dir") ) {
537         die("Failed to create or chown $DestDir$dir");
538     } else {
539         print("Created $DestDir$dir\n");
540     }
541 }
542
543 #
544 # Create other directories
545 #
546 foreach my $dir ( (
547             "$Conf{TopDir}",
548             "$Conf{TopDir}/pool",
549             "$Conf{TopDir}/cpool",
550             "$Conf{TopDir}/pc",
551             "$Conf{TopDir}/trash",
552             "$Conf{ConfDir}",
553             "$Conf{LogDir}",
554         ) ) {
555     mkpath("$DestDir$dir", 0, 0750) if ( !-d "$DestDir$dir" );
556     if ( !-d "$DestDir$dir"
557             || !my_chown($Uid, $Gid, "$DestDir$dir") ) {
558         die("Failed to create or chown $DestDir$dir\n");
559     } else {
560         print("Created $DestDir$dir\n");
561     }
562 }
563
564 printf("Installing binaries in $DestDir$Conf{InstallDir}/bin\n");
565 foreach my $prog ( qw(
566         __CONFIGURE_BIN_LIST__
567     ) ) {
568     InstallFile($prog, "$DestDir$Conf{InstallDir}/$prog", 0555);
569 }
570
571 printf("Installing library in $DestDir$Conf{InstallDir}/lib\n");
572 foreach my $lib ( qw(
573         __CONFIGURE_LIB_LIST__
574     ) ) {
575     InstallFile($lib, "$DestDir$Conf{InstallDir}/$lib", 0444);
576 }
577
578 if ( $Conf{CgiImageDir} ne "" ) {
579     printf("Installing images in $DestDir$Conf{CgiImageDir}\n");
580     foreach my $img ( <images/*> ) {
581         (my $destImg = $img) =~ s{^images/}{};
582         InstallFile($img, "$DestDir$Conf{CgiImageDir}/$destImg", 0444, 1);
583     }
584
585     #
586     # Install new CSS file, making a backup copy if necessary
587     #
588     my $cssBackup = "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css.pre-__VERSION__";
589     if ( -f "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css" && !-f $cssBackup ) {
590         rename("$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css", $cssBackup);
591     }
592     InstallFile("conf/BackupPC_stnd.css",
593                 "$DestDir$Conf{CgiImageDir}/BackupPC_stnd.css", 0444, 0);
594 }
595
596 printf("Making init.d scripts\n");
597 foreach my $init ( qw(gentoo-backuppc gentoo-backuppc.conf linux-backuppc
598                       solaris-backuppc debian-backuppc suse-backuppc
599                       slackware-backuppc ) ) {
600     InstallFile("init.d/src/$init", "init.d/$init", 0444);
601 }
602
603 printf("Installing docs in $DestDir$Conf{InstallDir}/doc\n");
604 foreach my $doc ( qw(BackupPC.pod BackupPC.html) ) {
605     InstallFile("doc/$doc", "$DestDir$Conf{InstallDir}/doc/$doc", 0444);
606 }
607
608 printf("Installing config.pl and hosts in $DestDir$Conf{ConfDir}\n");
609 InstallFile("conf/hosts", "$DestDir$Conf{ConfDir}/hosts", 0644)
610                     if ( !-f "$DestDir$Conf{ConfDir}/hosts" );
611
612 #
613 # Now do the config file.  If there is an existing config file we
614 # merge in the new config file, adding any new configuration
615 # parameters and deleting ones that are no longer needed.
616 #
617 my $dest = "$DestDir$Conf{ConfDir}/config.pl";
618 my ($distConf, $distVars) = ConfigParse("conf/config.pl");
619 my ($oldConf, $oldVars);
620 my ($newConf, $newVars) = ($distConf, $distVars);
621 if ( -f $dest ) {
622     ($oldConf, $oldVars) = ConfigParse($dest);
623     ($newConf, $newVars) = ConfigMerge($oldConf, $oldVars, $distConf, $distVars);
624 }
625
626 #
627 # Update various config parameters.  The old config is in Conf{}
628 # and the new config is an array in text form in $newConf->[].
629 #
630 $Conf{EMailFromUserName}  ||= $Conf{BackupPCUser};
631 $Conf{EMailAdminUserName} ||= $Conf{BackupPCUser};
632
633 #
634 # Guess $Conf{CgiURL}
635 #
636 if ( !defined($Conf{CgiURL}) ) {
637     if ( $Conf{CgiDir} =~ m{cgi-bin(/.*)} ) {
638         $Conf{CgiURL} = "'http://$Conf{ServerHost}/cgi-bin$1/BackupPC_Admin'";
639     } else {
640         $Conf{CgiURL} = "'http://$Conf{ServerHost}/cgi-bin/BackupPC_Admin'";
641     }
642 }
643
644 #
645 # The smbclient commands have moved from hard-coded to the config file.
646 # $Conf{SmbClientArgs} no longer exists, so merge it into the new
647 # commands if it still exists.
648 #
649 if ( defined($Conf{SmbClientArgs}) ) {
650     if ( $Conf{SmbClientArgs} ne "" ) {
651         foreach my $param ( qw(SmbClientRestoreCmd SmbClientFullCmd
652                                 SmbClientIncrCmd) ) {
653             $newConf->[$newVars->{$param}]{text}
654                             =~ s/(-E\s+-N)/$1 $Conf{SmbClientArgs}/;
655         }
656     }
657     delete($Conf{SmbClientArgs});
658 }
659
660 #
661 # CSS is now stored in a file rather than a big config variable.
662 #
663 delete($Conf{CSSstylesheet});
664
665 #
666 # The blackout timing settings are now stored in a list of hashes, rather
667 # than three scalar parameters.
668 #
669 if ( defined($Conf{BlackoutHourBegin}) ) {
670     $Conf{BlackoutPeriods} = [
671          {
672              hourBegin => $Conf{BlackoutHourBegin},
673              hourEnd   => $Conf{BlackoutHourEnd},
674              weekDays  => $Conf{BlackoutWeekDays},
675          } 
676     ];
677     delete($Conf{BlackoutHourBegin});
678     delete($Conf{BlackoutHourEnd});
679     delete($Conf{BlackoutWeekDays});
680 }
681
682 #
683 # $Conf{RsyncLogLevel} has been replaced by $Conf{XferLogLevel}
684 #
685 if ( defined($Conf{RsyncLogLevel}) ) {
686     $Conf{XferLogLevel} = $Conf{RsyncLogLevel};
687     delete($Conf{RsyncLogLevel});
688 }
689
690 #
691 # In 2.1.0 the default for $Conf{CgiNavBarAdminAllHosts} is now 1
692 #
693 $Conf{CgiNavBarAdminAllHosts} = 1;
694
695 #
696 # IncrFill should now be off
697 #
698 $Conf{IncrFill} = 0;
699
700 #
701 # Figure out sensible arguments for the ping command
702 #
703 if ( defined($Conf{PingArgs}) ) {
704     $Conf{PingCmd} = '$pingPath ' . $Conf{PingArgs};
705 } elsif ( !defined($Conf{PingCmd}) ) {
706     if ( $^O eq "solaris" || $^O eq "sunos" ) {
707         $Conf{PingCmd} = '$pingPath -s $host 56 1';
708     } elsif ( ($^O eq "linux" || $^O eq "openbsd" || $^O eq "netbsd")
709             && !system("$Conf{PingPath} -c 1 -w 3 localhost") ) {
710         $Conf{PingCmd} = '$pingPath -c 1 -w 3 $host';
711     } else {
712         $Conf{PingCmd} = '$pingPath -c 1 $host';
713     }
714     delete($Conf{PingArgs});
715 }
716
717 #
718 # Figure out sensible arguments for the df command
719 #
720 if ( !defined($Conf{DfCmd}) ) {
721     if ( $^O eq "solaris" || $^O eq "sunos" ) {
722         $Conf{DfCmd} = '$dfPath -k $topDir';
723     }
724 }
725
726 #
727 # $Conf{SmbClientTimeout} is now $Conf{ClientTimeout}
728 #
729 if ( defined($Conf{SmbClientTimeout}) ) {
730     $Conf{ClientTimeout} = $Conf{SmbClientTimeout};
731     delete($Conf{SmbClientTimeout});
732 }
733
734 #
735 # Replace --devices with -D in RsyncArgs and RsyncRestoreArgs
736 #
737 foreach my $param ( qw(RsyncArgs RsyncRestoreArgs) ) {
738     next if ( !defined($newVars->{$param}) );
739     $newConf->[$newVars->{$param}]{text} =~ s/--devices/-D/g;
740 }
741
742 #
743 # Merge any new user-editable parameters into CgiUserConfigEdit
744 # by copying the old settings forward.
745 #
746 if ( defined($Conf{CgiUserConfigEdit}) ) {
747     #
748     # This is a real hack.  The config file merging is done in text
749     # form without actually instantiating the new conf structure.
750     # So we need to extract the new hash of settings, update it,
751     # and merge the text.  Ugh...
752     #
753     my $new;
754     my $str = $distConf->[$distVars->{CgiUserConfigEdit}]{text};
755
756     $str =~ s/^\s*\$Conf\{.*?\}\s*=\s*/\$new = /m;
757     eval($str);
758     foreach my $p ( keys(%$new) ) {
759         $new->{$p} = $Conf{CgiUserConfigEdit}{$p}
760                 if ( defined($Conf{CgiUserConfigEdit}{$p}) );
761     }
762     $Conf{CgiUserConfigEdit} = $new;
763     my $d = Data::Dumper->new([$new], [*value]);
764     $d->Indent(1);
765     $d->Terse(1);
766     my $value = $d->Dump;
767     $value =~ s/(.*)\n/$1;\n/s;
768     $newConf->[$newVars->{CgiUserConfigEdit}]{text}
769             =~ s/(\s*\$Conf\{.*?\}\s*=\s*).*/$1$value/s;
770 }
771
772 #
773 # Now backup and write the config file
774 #
775 my $confCopy = "$dest.pre-__VERSION__";
776 if ( -f $dest && !-f $confCopy ) {
777     #
778     # Make copy of config file, preserving ownership and modes
779     #
780     printf("Making backup copy of $dest -> $confCopy\n");
781     my @stat = stat($dest);
782     my $mode = $stat[2];
783     my $uid  = $stat[4];
784     my $gid  = $stat[5];
785     die("can't copy($dest, $confCopy)\n")
786                                 unless copy($dest, $confCopy);
787     die("can't chown $uid, $gid $confCopy\n")
788                                 unless my_chown($uid, $gid, $confCopy);
789     die("can't chmod $mode $confCopy\n")
790                                 unless my_chmod($mode, $confCopy);
791 }
792 open(OUT, ">", $dest) || die("can't open $dest for writing\n");
793 binmode(OUT);
794 my $blockComment;
795 foreach my $var ( @$newConf ) {
796     if ( length($blockComment)
797           && substr($var->{text}, 0, length($blockComment)) eq $blockComment ) {
798         $var->{text} = substr($var->{text}, length($blockComment));
799         $blockComment = undef;
800     }
801     $blockComment = $1 if ( $var->{text} =~ /^([\s\n]*#{70}.*#{70}[\s\n]+)/s );
802     $var->{text} =~ s/^\s*\$Conf\{(.*?)\}(\s*=\s*['"]?)(.*?)(['"]?\s*;)/
803                 defined($Conf{$1}) && ref($Conf{$1}) eq ""
804                                    && $Conf{$1} ne $OrigConf{$1}
805                                    ? "\$Conf{$1}$2$Conf{$1}$4"
806                                    : "\$Conf{$1}$2$3$4"/emg;
807     print OUT $var->{text};
808 }
809 close(OUT);
810 if ( !defined($oldConf) ) {
811     die("can't chmod 0640 mode $dest\n")  unless my_chmod(0640, $dest);
812     die("can't chown $Uid, $Gid $dest\n") unless my_chown($Uid, $Gid, $dest);
813 }
814
815 if ( $Conf{CgiDir} ne "" ) {
816     printf("Installing cgi script BackupPC_Admin in $DestDir$Conf{CgiDir}\n");
817     mkpath("$DestDir$Conf{CgiDir}", 0, 0755);
818     InstallFile("cgi-bin/BackupPC_Admin", "$DestDir$Conf{CgiDir}/BackupPC_Admin",
819                 04554);
820 }
821
822 print <<EOF;
823
824 Ok, it looks like we are finished.  There are several more things you
825 will need to do:
826
827   - Browse through the config file, $Conf{ConfDir}/config.pl,
828     and make sure all the settings are correct.  In particular,
829     you will need to set \$Conf{CgiAdminUsers} so you have
830     administration privileges in the CGI interface.
831
832   - Edit the list of hosts to backup in $Conf{ConfDir}/hosts.
833
834   - Read the documentation in $Conf{InstallDir}/doc/BackupPC.html.
835     Please pay special attention to the security section.
836
837   - Verify that the CGI script BackupPC_Admin runs correctly.  You might
838     need to change the permissions or group ownership of BackupPC_Admin.
839
840   - BackupPC should be ready to start.  Don't forget to run it
841     as user $Conf{BackupPCUser}!  The installation also contains an
842     init.d/backuppc script that can be copied to /etc/init.d
843     so that BackupPC can auto-start on boot.  This will also enable
844     administrative users to start the server from the CGI interface.
845     See init.d/README.
846
847 Enjoy!
848 EOF
849
850 if ( `$Conf{PerlPath} -V` =~ /uselargefiles=undef/ ) {
851     print <<EOF;
852
853 Warning: your perl, $Conf{PerlPath}, does not support large files.
854 This means BackupPC won't be able to backup files larger than 2GB.
855 To solve this problem you should build/install a new version of perl
856 with large file support enabled.  Use
857
858     $Conf{PerlPath} -V | egrep uselargefiles
859
860 to check if perl has large file support (undef means no support).
861 EOF
862 }
863
864 eval "use File::RsyncP;";
865 if ( !$@ && $File::RsyncP::VERSION < 0.64 ) {
866     print("\nWarning: you need to upgrade File::RsyncP;"
867         . " I found $File::RsyncP::VERSION and BackupPC needs 0.64\n");
868 }
869
870 exit(0);
871
872 ###########################################################################
873 # Subroutines
874 ###########################################################################
875
876 sub InstallFile
877 {
878     my($prog, $dest, $mode, $binary) = @_;
879     my $first = 1;
880     my($uid, $gid) = ($Uid, $Gid);
881
882     if ( -f $dest ) {
883         #
884         # preserve ownership and modes of files that already exist
885         #
886         my @stat = stat($dest);
887         $mode = $stat[2];
888         $uid  = $stat[4];
889         $gid  = $stat[5];
890     }
891     unlink($dest) if ( -f $dest );
892     if ( $binary ) {
893         die("can't copy($prog, $dest)\n") unless copy($prog, $dest);
894     } else {
895         open(PROG, $prog)     || die("can't open $prog for reading\n");
896         open(OUT, ">", $dest) || die("can't open $dest for writing\n");
897         binmode(PROG);
898         binmode(OUT);
899         while ( <PROG> ) {
900             s/__INSTALLDIR__/$Conf{InstallDir}/g;
901             s/__LOGDIR__/$Conf{LogDir}/g;
902             s/__CONFDIR__/$Conf{ConfDir}/g;
903             s/__TOPDIR__/$Conf{TopDir}/g;
904             s/^(\s*my \$useFHS\s*=\s*)\d;/${1}$opts{fhs};/
905                                     if ( $prog =~ /Lib.pm/ );
906             s/__BACKUPPCUSER__/$Conf{BackupPCUser}/g;
907             s/__CGIDIR__/$Conf{CgiDir}/g;
908             if ( $first && /^#.*bin\/perl/ ) {
909                 #
910                 # Fill in correct path to perl (no taint for >= 2.0.1).
911                 #
912                 print OUT "#!$Conf{PerlPath}\n";
913             } else {
914                 print OUT;
915             }
916             $first = 0;
917         }
918         close(PROG);
919         close(OUT);
920     }
921     die("can't chown $uid, $gid $dest") unless my_chown($uid, $gid, $dest);
922     die("can't chmod $mode $dest")      unless my_chmod($mode, $dest);
923 }
924
925 sub FindProgram
926 {
927     my($path, $prog) = @_;
928
929     if ( defined($opts{"bin-path"}{$prog}) ) {
930         return $opts{"bin-path"}{$prog};
931     }
932     foreach my $dir ( split(/:/, $path) ) {
933         my $file = File::Spec->catfile($dir, $prog);
934         return $file if ( -x $file );
935     }
936     return;
937 }
938
939 sub ConfigParse
940 {
941     my($file) = @_;
942     open(C, $file) || die("can't open $file");
943     binmode(C);
944     my($out, @conf, $var);
945     my $comment = 1;
946     my $allVars = {};
947     my $endLine = undef;
948     while ( <C> ) {
949         if ( /^#/ && !defined($endLine) ) {
950             if ( $comment ) {
951                 $out .= $_;
952             } else {
953                 if ( $out ne "" ) {
954                     $allVars->{$var} = @conf if ( defined($var) );
955                     push(@conf, {
956                         text => $out,
957                         var => $var,
958                     });
959                 }
960                 $var = undef;
961                 $comment = 1;
962                 $out = $_;
963             }
964         } elsif ( /^\s*\$Conf\{([^}]*)/ ) {
965             $comment = 0;
966             if ( defined($var) ) {
967                 $allVars->{$var} = @conf if ( defined($var) );
968                 push(@conf, {
969                     text => $out,
970                     var => $var,
971                 });
972                 $out = $_;
973             } else {
974                 $out .= $_;
975             }
976             $var = $1;
977             $endLine = $1 if ( /^\s*\$Conf\{[^}]*} *= *<<(.*);/ );
978             $endLine = $1 if ( /^\s*\$Conf\{[^}]*} *= *<<'(.*)';/ );
979         } else {
980             $endLine = undef if ( defined($endLine) && /^\Q$endLine[\n\r]*$/ );
981             $out .= $_;
982         }
983     }
984     if ( $out ne "" ) {
985         $allVars->{$var} = @conf if ( defined($var) );
986         push(@conf, {
987             text => $out,
988             var  => $var,
989         });
990     }
991     close(C);
992     return (\@conf, $allVars);
993 }
994
995 sub ConfigMerge
996 {
997     my($old, $oldVars, $new, $newVars) = @_;
998     my $posn = 0;
999     my($res, $resVars);
1000
1001     #
1002     # Find which config parameters are not needed any longer
1003     #
1004     foreach my $var ( @$old ) {
1005         next if ( !defined($var->{var}) || defined($newVars->{$var->{var}}) );
1006         #print(STDERR "Deleting old config parameter $var->{var}\n");
1007         $var->{delete} = 1;
1008     }
1009     #
1010     # Find which config parameters are new
1011     #
1012     foreach my $var ( @$new ) {
1013         next if ( !defined($var->{var}) );
1014         if ( defined($oldVars->{$var->{var}}) ) {
1015             $posn = $oldVars->{$var->{var}};
1016         } else {
1017             #print(STDERR "New config parameter $var->{var}: $var->{text}\n");
1018             push(@{$old->[$posn]{new}}, $var);
1019         }
1020     }
1021     #
1022     # Create merged config file
1023     #
1024     foreach my $var ( @$old ) {
1025         next if ( $var->{delete} );
1026         push(@$res, $var);
1027         foreach my $new ( @{$var->{new}} ) {
1028             push(@$res, $new);
1029         }
1030     }
1031     for ( my $i = 0 ; $i < @$res ; $i++ ) {
1032         $resVars->{$res->[$i]{var}} = $i;
1033     }
1034     return ($res, $resVars);
1035 }
1036
1037 sub my_chown
1038 {
1039     my($uid, $gid, $file) = @_;
1040
1041     return 1 if ( !$opts{"set-perms"} );
1042     return chown($uid, $gid, $file);
1043 }
1044
1045 sub my_chmod
1046 {
1047     my ($mode, $file) = @_;
1048
1049     return 1 if ( !$opts{"set-perms"} );
1050     return chmod($mode, $file);
1051 }
1052
1053 sub prompt
1054 {
1055     my($question, $default, $option) = @_;
1056
1057     $default = $opts{$option} if ( defined($opts{$option}) );
1058     if ( $opts{batch} ) {
1059         print("$question [$default]\n");
1060         return $default;
1061     }
1062     print("$question [$default]? ");
1063     my $reply = <STDIN>;
1064     $reply =~ s/[\n\r]*//g;
1065     return $reply if ( $reply !~ /^$/ );
1066     return $default;
1067 }
1068
1069 __END__
1070
1071 =head1 SYNOPSIS
1072
1073 configure.pl [options]
1074
1075 =head1 DESCRIPTION
1076
1077 configure.pl is a script that is used to install or upgrade a BackupPC
1078 installation.  It is usually run interactively without arguments.  It
1079 also supports a batch mode where all the options can be specified
1080 via the command-line.
1081
1082 For upgrading BackupPC you need to make sure that BackupPC is not
1083 running prior to running BackupPC.
1084
1085 Typically configure.pl needs to run as the super user (root).
1086
1087 =head1 OPTIONS
1088
1089 =over 8
1090
1091 =item B<--batch>
1092
1093 Run configure.pl in batch mode.  configure.pl will run without
1094 prompting the user.  The other command-line options are used
1095 to specify the settings that the user is usually prompted for.
1096
1097 =item B<--backuppc-user=USER>
1098
1099 Specify the BackupPC user name that owns all the BackupPC
1100 files and runs the BackupPC programs.  Default is backuppc.
1101
1102 =item B<--bin-path PROG=PATH>
1103
1104 Specify the path for various external programs that BackupPC
1105 uses.  Several --bin-path options may be specified.  configure.pl
1106 usually finds sensible defaults based on searching the PATH.
1107 The format is:
1108
1109     --bin-path PROG=PATH
1110
1111 where PROG is one of perl, tar, smbclient, nmblookup, rsync, ping,
1112 df, ssh, sendmail, hostname, split, par2, cat, gzip, bzip2 and
1113 PATH is that full path to that program.
1114
1115 Examples
1116
1117     --bin-path cat=/bin/cat --bin-path bzip2=/home/user/bzip2
1118
1119 =item B<--compress-level=N>
1120
1121 Set the configuration compression level to N.  Default is 3
1122 if Compress::Zlib is installed.
1123
1124 =item B<--config-dir CONFIG_DIR>
1125
1126 Configuration directory for new installations.  Defaults
1127 to /etc/BackupPC with FHS.  Automatically extracted
1128 from --config-path for existing installations.
1129
1130 =item B<--config-path CONFIG_PATH>
1131
1132 Path to the existing config.pl configuration file for BackupPC.
1133 This option should be specified for batch upgrades to an
1134 existing installation.  The option should be omitted when
1135 doing a batch new install.
1136
1137 =item B<--cgi-dir CGI_DIR>
1138
1139 Path to Apache's cgi-bin directory where the BackupPC_Admin
1140 script will be installed.  This option only needs to be
1141 specified for a batch new install.
1142
1143 =item B<--data-dir DATA_DIR>
1144
1145 Path to the BackupPC data directory.  This is where all the backup
1146 data is stored, and it should be on a large file system. This option
1147 only needs to be specified for a batch new install.
1148
1149 Example:
1150
1151     --data-dir /data/BackupPC
1152
1153 =item B<--dest-dir DEST_DIR>
1154
1155 An optional prefix to apply to all installation directories.
1156 Usually this is not needed, but certain auto-installers like
1157 to stage an install in a temporary directory, and then copy
1158 the files to their real destination.  This option can be used
1159 to specify the temporary directory prefix.  Note that if you
1160 specify this option, BackupPC won't run correctly if you try
1161 to run it from below the --dest-dir directory, since all the
1162 paths are set assuming BackupPC is installed in the intended
1163 final locations.
1164
1165 =item B<--fhs>
1166
1167 Use locations specified by the Filesystem Hierarchy Standard
1168 for installing BackupPC.  This is enabled by default for new
1169 installatios.  To use the pre-3.0 installation locations,
1170 specify --no-fhs.
1171
1172 =item B<--help|?>
1173
1174 Print a brief help message and exits.
1175
1176 =item B<--hostname HOSTNAME>
1177
1178 Host name (this machine's name) on which BackupPC is being installed.
1179 This option only needs to be specified for a batch new install.
1180
1181 =item B<--html-dir HTML_DIR>
1182
1183 Path to an Apache html directory where various BackupPC image files
1184 and the CSS files will be installed.  This is typically a directory
1185 below Apache's DocumentRoot directory.  This option only needs to be
1186 specified for a batch new install.
1187
1188 Example:
1189
1190     --html-dir /usr/local/apache/htdocs/BackupPC
1191
1192 =item B<--html-dir-url URL>
1193
1194 The URL (without http://hostname) required to access the BackupPC html
1195 directory specified with the --html-dir option.  This option only needs
1196 to be specified for a batch new install.
1197
1198 Example:
1199
1200     --html-dir-url /BackupPC
1201
1202 =item B<--install-dir INSTALL_DIR>
1203
1204 Installation directory for BackupPC scripts, libraries, and
1205 documentation.  This option only needs to be specified for a
1206 batch new install.
1207
1208 Example:
1209
1210     --install-dir /usr/local/BackupPC
1211
1212 =item B<--log-dir LOG_DIR>
1213
1214 Log directory.  Defaults to /var/log/BackupPC with FHS.
1215
1216 =item B<--man>
1217
1218 Prints the manual page and exits.
1219
1220 =item B<--set-perms>
1221
1222 When installing files and creating directories, chown them to
1223 the BackupPC user and chmod them too.  This is enabled by default.
1224 To disable (for example, if staging a destination directory)
1225 then specify --no-set-perms.
1226
1227 =item B<--uid-ignore>
1228
1229 configure.pl verifies that the script is being run as the super user
1230 (root).  Without the --uid-ignore option, in batch mode the script will
1231 exit with an error if not run as the super user, and in interactive mode
1232 the user will be prompted.  Specifying this option will cause the script
1233 to continue even if the user id is not root.
1234
1235 =head1 EXAMPLES
1236
1237 For a standard interactive install, run without arguments:
1238
1239     configure.pl
1240
1241 For a batch new install you need to specify answers to all the
1242 questions that are normally prompted:
1243
1244     configure.pl                                   \
1245         --batch                                    \
1246         --cgi-dir /var/www/cgi-bin/BackupPC        \
1247         --data-dir /data/BackupPC                  \
1248         --hostname myHost                          \
1249         --html-dir /var/www/html/BackupPC          \
1250         --html-dir-url /BackupPC                   \
1251         --install-dir /usr/local/BackupPC
1252
1253 For a batch upgrade, you only need to specify the path to the
1254 configuration file:
1255         
1256     configure.pl --batch --config-path /data/BackupPC/conf/config.pl
1257
1258 =head1 AUTHOR
1259
1260 Craig Barratt <cbarratt@users.sourceforge.net>
1261
1262 =head1 COPYRIGHT
1263
1264 Copyright (C) 2001-2006  Craig Barratt.
1265
1266 This program is free software; you can redistribute it and/or modify
1267 it under the terms of the GNU General Public License as published by
1268 the Free Software Foundation; either version 2 of the License, or
1269 (at your option) any later version.
1270
1271 =cut