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