root/branches/new-install-process/bin/openguides-install

Revision 1172, 18.2 kB (checked in by kake, 7 months ago)

Standardise slash vs. no slash on ends of directory names in install process (cosmetic change only).

  • Property svn:executable set to *
Line 
1#!/usr/bin/perl -w
2use warnings;
3use strict;
4use Config::Tiny;
5use ExtUtils::MM_Unix; # for fixing the shebang lines
6use FindBin qw($Bin);
7use File::Spec::Functions qw( catfile );
8use File::Copy;
9use Getopt::Long;
10use OpenGuides; # to get it into %INC for splitpath, below.
11use OpenGuides::Config;
12use Term::Prompt;
13
14# little utility function to find a script and make sure it's +x, etc.
15sub find_script {
16  my $name = shift;
17
18  # for preference, we find files in the same dir as us. Really handy
19  # for development.
20  my $file = catfile($Bin, $name);
21 
22  # It's not with me. Look in the path.
23  unless (-f $file) { chomp( $file = `which $name` ) }
24
25  die "I can't find '$name' in $Bin or your path. Stopping.\n"
26    unless (-f $file);
27  die "I found '$name' at $file, but it's not executable. Stopping.\n"
28    unless (-x $file);
29
30  return $file;
31}
32
33sub show_help {
34    print qq(
35To create an OpenGuides install, run
36
37  openguides-install
38
39and answer the questions that it asks you.  If you have an existing wiki.conf
40file, then make sure it's in the current directory before you start.
41);
42}
43
44=head1 NAME
45
46openguides-install
47
48=head1 DESCRIPTION
49
50Creates an L<OpenGuides> install.
51
52=head1 USAGE
53
54To create an L<OpenGuides> install, run
55
56  openguides-install
57
58and answer the questions that it asks you.  If you have an existing
59C<wiki.conf> file, then make sure it's in the current directory before you
60start.
61
62To show this help, run
63
64  openguides-install --help
65
66=cut
67
68my $show_help;
69GetOptions( help => \$show_help );
70if ( $show_help ) {
71    show_help();
72    exit 0;
73}
74
75# Map master copies of scripts to what they should be called.
76my %targets;
77foreach my $label ( qw( newpage preferences search wiki ) ) {
78    $targets{"$label.cgi"} = find_script( "openguides-$label-script" );
79}
80
81my $force;
82GetOptions('force' => \$force);
83unless ($force) {
84    print "Beginning install process...\n";
85    if ( ! -f "wiki.conf" ) {
86        print <<EOF;
87
88No existing configuration file found; assuming this is a new install.  If you
89already have an OpenGuides configuration file and you don't want to have to
90type in all your config parameters over again, abort this process now, copy
91that file to the current directory (and/or make sure you're in the directory
92you intended to be in), and start again.
93
94EOF
95    }
96}
97
98exit 0 unless prompt( "y", "Continue with install?", "y or n", "y" );
99
100my $existing_config;
101
102if (-f "wiki.conf" ) {
103    $existing_config = OpenGuides::Config->new( file => "wiki.conf" );
104} else {
105    $existing_config = OpenGuides::Config->new();
106}
107
108my %yn_vars = map { $_ => 1 }
109   qw(use_plucene enable_page_deletion navbar_on_home_page backlinks_in_title
110      moderation_requires_password enable_node_image enable_common_categories
111      enable_common_locales recent_changes_on_home_page
112      random_page_omits_locales random_page_omits_categories
113      content_above_navbar_in_html show_gmap_in_node_display force_wgs84
114      send_moderation_notifications);
115
116my %blank_ok = map { $_ => 1 }
117   qw( dbpass dbhost );
118
119my $skip_config = $force ? 'y'
120                         : prompt( "y", "Skip OpenGuides configuration?",
121                                   "y or n", "n" );
122
123if ( $skip_config ) {
124    print <<EOF;
125===========================================================================
126Skipping OpenGuides configuration - the wiki.conf in the current directory
127will be used to select default configuration options.  You may tweak your
128configuration later by editing wiki.conf again after this script has had a
129go at it.
130===========================================================================
131EOF
132}
133
134my @answers;
135
136# It is an ancient Configurer, and he chooseth one of three.
137my $dbtype;
138my $dbtype_qu = $existing_config->dbtype__qu;
139if ( $skip_config ) {
140    $dbtype = $existing_config->dbtype;
141} else {
142    my $default;
143    my @options = qw( postgres mysql sqlite );
144
145    foreach my $n ( ( 0 .. $#options ) ) {
146        if ( $options[$n] eq $existing_config->dbtype ) {
147            $default = $n + 1; # The +1 requirement might be a Term::Prompt bug
148            last;
149        }
150    }
151
152    die "Invalid dbtype " . $existing_config->dbtype
153        unless $default;
154
155    my $i = prompt( "m", {
156                           prompt => "$dbtype_qu",
157                           items  => \@options,
158                         },
159                    undef, $default );
160    $dbtype = $options[$i];
161}
162
163# Check they have the relevant DBD driver installed.
164my %drivers = ( postgres => "DBD::Pg",
165                mysql    => "DBD::mysql",
166                sqlite   => "DBD::SQLite",
167              );
168eval "require $drivers{$dbtype}";
169die "\nSorry; $drivers{$dbtype} is needed to run a $dbtype database - please "
170    . "install this\nmodule and try again.\n\n" if $@;
171
172push @answers, { question => $dbtype_qu,
173                 variable => "dbtype",
174                 value    => $dbtype };
175
176my $install_directory; # used to suggest template paths
177my $centre_lat = ''; # contains centre lat derived from Google Maps URL
178my $script_name; # keep track of this for when we install the CGI scripts
179my $template_path; # and this for when we install the templates
180my $static_url; # and this for installing a stylesheet
181my $static_path; # and this for printing a message when doing the above
182my $install_stylesheet; # do we install a basic stylesheet or not
183my $use_gmaps; # do we have a Google Maps API key, basically.
184
185# for setting up the database - shouldn't dbport be in here too?
186my ( $dbname, $dbuser, $dbpass, $dbhost ) = ( "", "", "", "" );
187
188my %is_gmaps_var = map { $_ => 1 } qw( centre_long centre_lat default_gmaps_zoom default_gmaps_search_zoom show_gmap_in_node_display );
189
190foreach my $var ( qw(
191   dbname dbuser dbpass dbhost dbport script_name
192   install_directory template_path custom_template_path script_url
193   custom_lib_path use_plucene indexing_directory enable_page_deletion
194   admin_pass static_path static_url stylesheet_url
195   site_name navbar_on_home_page
196   recent_changes_on_home_page random_page_omits_locales
197   random_page_omits_categories content_above_navbar_in_html home_name
198   site_desc default_city default_country contact_email default_language
199   formatting_rules_node backlinks_in_title gmaps_api_key centre_long
200   centre_lat show_gmap_in_node_display default_gmaps_zoom
201   default_gmaps_search_zoom force_wgs84 google_analytics_key
202   licence_name licence_url licence_info_url moderation_requires_password
203   enable_node_image enable_common_categories enable_common_locales
204   spam_detector_module host_checker_module
205   send_moderation_notifications
206  ) ) {
207    my $q_method = $var . "__qu";
208    my $qu  = $existing_config->$q_method;
209    my $def = $existing_config->$var || "";
210    my $val = $def;
211
212    # Override dbname question for SQLite only.
213    if ( $dbtype eq "sqlite" and $var eq "dbname" ) {
214        $qu = "What's the full filename of the SQLite database this site runs on?";
215    }
216
217    if ( $dbtype eq "sqlite" and
218         ( $var eq "dbuser" or $var eq "dbpass" or $var eq "dbhost" or
219           $var eq "dbport")
220       ) {
221        print "$var not relevant for SQLite... skipping...\n"
222            unless $skip_config;
223        push @answers, { question => $qu,
224                            variable => $var,
225                         value    => "not-used" };
226        next;
227    }
228
229    # Make sensible suggestions for template paths if we don't already
230    # have them stored.  Not really a default, but a useful hint/shortcut.
231    if ( $var eq "template_path" && !defined $existing_config->$var ) {
232        $def = $install_directory;
233        $def .= "/" unless $def =~ m|/$|;
234        $def .= "templates/";
235    }
236    if ( $var eq "custom_template_path" && !defined $existing_config->$var ) {
237        $def = $install_directory;
238        $def .= "/" unless $def =~ m|/$|;
239        $def .= "custom-templates/";
240    }
241
242    # If a Google Maps URL was provided last time we know the centre_lat
243    if ( $var eq 'centre_lat' && $centre_lat ) {
244        $val = $centre_lat;
245        next;
246    }
247
248    # Term::Prompt doesn't like blank answers, so we ask a yes-no question
249    # about munging in a lib path before we actually ask for the path.  Most
250    # people will want to say "no" to this option, anyway.  The $def variable
251    # will either be blank (no munging) or will contain the required path(s).
252    unless ( $skip_config ) {
253        if ( $var eq "custom_lib_path" ) {
254            my $yn_def = $def ? "y" : "n";
255            my $munge = prompt( "y", $qu,
256              "if you don't understand this question, answer \"n\"", $yn_def );
257            if ( $munge ) {
258                $val = prompt( "x", "Please enter your lib path here.  "
259                                    . "Separate path entries with whitespace.",
260                               "", $def );
261            } else {
262                $val = "";
263            }
264            push @answers, { question => $qu,
265                             variable => $var,
266                             value    => $val };
267            next;
268        }
269    }
270
271    # Skip Google Maps questions if we have no API key.
272    if ( $is_gmaps_var{$var} && !$use_gmaps ) {
273        next;
274    }
275
276    # Again, because Term::Prompt doesn't like blank answers.
277    unless ( $skip_config ) {
278        if ( $var eq "google_analytics_key"
279            || $var eq "gmaps_api_key"
280             || $var eq "spam_detector_module"
281             || $var eq "host_checker_module" ) {
282            my $yn_def = $def ? "y" : "n";
283            my $do_it = prompt( "y", $qu,
284                        "if you don't understand this question, answer \"n\"",
285                                $yn_def );
286            if ( $do_it ) {
287                $val = prompt( "x", "Please enter it here", "", $def );
288            } else {
289                $val = "";
290            }
291            # Keep track of whether they have a GMaps API key as if they don't
292            # then we can skip some later questions.
293            if ( $var eq "gmaps_api_key" ) {
294                $use_gmaps = $do_it;
295            }
296            push @answers, { question => $qu,
297                             variable => $var,
298                             value    => $val };
299            next;
300        }
301    }
302
303    # They have the option of using their own stylesheet, or having us install
304    # one for them.  NOTE: we don't reinstall the stylesheet if things change,
305    # this is a one-off.  The $def variable will either be blank (if this is
306    # a fresh install) or will contain the stylesheet URL.
307    if ( $var eq "stylesheet_url" && !$skip_config ) {
308        my $yn_def = $def ? "y" : "n";
309        $install_stylesheet = prompt( "y",
310           "Would you like to have a basic stylesheet installed for you?",
311           "y or n", $yn_def );
312        if ( $install_stylesheet ) {
313            # just install the stylesheet in the static content
314            print "OK, will install style.css in $static_path\n";
315            $val = $static_url . "style.css";
316            push @answers, { question => $qu,
317                             variable => $var,
318                             value    => $val };
319            next;
320        }
321    }
322
323    # Here is where we actually ask the questions.
324    unless ( $skip_config ) {
325print $var . "\n";
326        if ( $yn_vars{$var} ) {
327            # may be stored as true/false integer value
328            if ( $def =~ /^\d+$/ ) {
329                $def = $def ? "y" : "n";
330            }
331            $val = prompt( "y", $qu, "y or n", $def );
332        } elsif ( $blank_ok{$var} ) {
333print "boo!\n";
334             $val = prompt( "s", $qu, "optional", $def,
335                            sub { print "hello!\n"; return 1; } );
336        } else {
337             $val = prompt( "x", $qu, "", $def );
338        }
339    }
340
341    # Plucene is currently the recommended search backend as it seems to have
342    # fewer bugs than the other options.
343    if ( $var eq "use_plucene" && $val ) {
344        eval "require Plucene";
345        if ( $@ ) {
346            print "\n***NOTE*** Plucene not found - you will need to install "
347                  . "it before running this\nOpenGuides instance.\n\n";
348        }
349    }
350
351    # Allow user to use a Google Maps URL rather than enter lat/long by hand.
352    # Assume centre_long is being asked for first; ensure so in big list above.
353    if ( $var eq 'centre_long' ) {
354        if ( $val =~ /ll=([-\d.]+),([-\d.]+)/ ) {
355            print "Got a Google Maps URL with centre long,lat: [$1, $2]\n";
356            $val = $1;
357            $centre_lat = $2;
358        }
359    }
360
361    # Make sure that script_url and static_url both end in a /
362    if ( $var eq "script_url" || $var eq "static_url" ) {
363        if ( $val !~ /\/$/ ) {
364            $val .= "/";
365        }
366    }
367
368    # Store install_directory so we can use it to suggest template paths.
369    $install_directory = $val if $var eq "install_directory";
370
371    # And the static content URL (for stylesheet installation default).
372    $static_url = $val if $var eq "static_url";
373    $static_path = $val if $var eq "static_path";
374
375    # Store the script name and template path for use lower down.
376    $script_name = $val if $var eq "script_name";
377    if ( $var eq "template_path" ) {
378        $template_path = $val;
379    }
380
381    # Store database vars for setting up the DB.
382    if ( $var eq "dbname" ) {
383        $dbname = $val;
384    }
385    if ( $var eq "dbuser" ) {
386        $dbuser = $val;
387    }
388    if ( $var eq "dbpass" ) {
389        $dbpass = $val;
390    }
391    if ( $var eq "dbhost" ) {
392        $dbhost = $val;
393    }
394
395    push @answers, { question => $qu,
396                     variable => $var,
397                     value    => $val };
398}
399
400# Now deal with the geo stuff.
401my $geo_handler;
402my $geo_handler_qu = "How would you like to calculate distances?";
403
404if ( $skip_config ) {
405    # We default to GB National Grid for historical reasons.
406    $geo_handler = $existing_config->geo_handler;
407} else {
408    my $default = $existing_config->geo_handler;
409    my @options = ( "British National Grid",
410                    "Irish National Grid",
411                    "UTM ellipsoid" );
412    $geo_handler = prompt( "m", {
413                                  prompt => $geo_handler_qu,
414                                  items  => \@options,
415                                  return_base => 1,
416                                 },
417                           "1, 2, or 3", $default );
418}
419
420push @answers, {
421                 question => $geo_handler_qu,
422                 variable => "geo_handler",
423                 value    => $geo_handler,
424               };
425
426if ( $geo_handler eq "3" ) {
427    my $qu = $existing_config->ellipsoid__qu;
428    my $ellipsoid;
429    if ( $skip_config ) {
430        $ellipsoid = $existing_config->ellipsoid;
431    } else {
432        my $def = $existing_config->ellipsoid;
433        $ellipsoid = prompt( "x", $qu, undef, $def );
434        $ellipsoid =~ s/^\s*//;
435        $ellipsoid =~ s/\s*$//;
436    }
437    push @answers, {
438                     question => $qu,
439                     variable => "ellipsoid",
440                     value    => $ellipsoid,
441                   };
442}
443
444# Create a user-friendly config file from answers to prompts.
445open FILE, ">wiki.conf" or die "Can't open wiki.conf for writing: $!";
446foreach my $ans (@answers) {
447    print FILE "# $ans->{question}\n";
448    print FILE "$ans->{variable} = $ans->{value}\n\n";
449}
450close FILE or die "Can't close wiki.conf: $!";
451
452# Find the templates from where we stored them when we installed OpenGuides.
453#use Data::Dumper; print Dumper \%INC;
454my (undef, $path, undef) = File::Spec->splitpath($INC{'OpenGuides.pm'});
455my $templates = File::Spec->catfile( $path, "OpenGuides", "templates" );
456die "Can't find OpenGuides templates in '$path'"
457  unless (-d $templates);
458
459# Munge %targets to account for what they want the main script to be called.
460if ( $script_name ne "wiki.cgi" ) {
461    $targets{$script_name} = $targets{"wiki.cgi"};
462    delete $targets{"wiki.cgi"};
463}
464
465# Install!
466print "Installing OpenGuides...\n";
467
468print "  installing cgi scripts\n";
469foreach my $target ( keys %targets ) {
470    my $dest = File::Spec->catfile( $install_directory, $target );
471    copy( $targets{$target}, $dest)
472        or die "Can't install $target to $dest - $!\n";
473    # The below throws spurious warnings (EU::MM 6.44) because our shebang
474    # lines have no options.  I don't understand why this didn't happen with
475    # Module::Build->fix_shebang_line though.
476    ExtUtils::MM_Unix->fixin( $dest );
477    chmod( 0755, $dest )
478        or die "Can't make $dest executable - $!\n";
479}
480
481print "  installing wiki.conf\n";
482copy( "wiki.conf", $install_directory )
483  or die "Can't install wiki.conf in $install_directory - $!\n";
484#chmod( 0600, File::Spec->catfile( $install_directory, "wiki.conf" ) )
485#  or die "Can't read-protect wiki.conf in $install_directory: $!\n";
486
487my $mentionswikidotconf = 0;
488my $htaccess = File::Spec->catfile( $install_directory, ".htaccess" );
489print "Trying to ensure that wiki.conf is protected by .htaccess...\n";
490if ( -f $htaccess ) {
491    if ( open HTACCESS, $htaccess ) {
492        while ( <HTACCESS> ) {
493            if ( /wiki\.conf/ ) {
494                $mentionswikidotconf = 1;
495            }
496        }
497        close HTACCESS;
498     } else {
499        warn "Couldn't open $htaccess for reading: $!";
500     }
501}
502
503if ( $mentionswikidotconf ) {
504    print ".htaccess appears to already mention wiki.conf.\n";
505} else {
506    if ( open HTACCESS, ">>$htaccess" ) {
507        print HTACCESS "# Added by openguides-install script\n";
508        print HTACCESS "<Files wiki.conf>\ndeny from all\n</Files>\n";
509        close HTACCESS;
510        print "apparent success. You should check that this is working!\n";
511    } else {
512        warn "Couldn't open $htaccess for writing: $!";
513    }
514}
515
516print "  Installing templates to $template_path...\n";
517unless ( -d $template_path ) {
518    mkdir $template_path or die "Can't make template dir: $!";
519}
520opendir TEMPLATES, $templates or die "Can't open template source folder: $!\n";
521for (grep { /(\.tt)$/ } readdir(TEMPLATES)) {
522  File::Copy::copy(
523    File::Spec->catfile($templates, $_),
524    File::Spec->catfile($template_path, $_ )
525  ) or die "Error copying $_: $!\n";
526}
527closedir(TEMPLATES);
528
529print "  setting up DB\n";
530
531my %setup_modules = ( postgres => "Wiki::Toolkit::Setup::Pg",
532                      mysql    => "Wiki::Toolkit::Setup::MySQL",
533                      sqlite   => "Wiki::Toolkit::Setup::SQLite",
534                    );
535
536my $class = $setup_modules{$dbtype};
537eval "require $class";
538if ( $@ ) {
539    print "Couldn't 'use' $class: $@\n";
540    exit 1;
541}
542
543{   
544    no strict 'refs';
545    &{$class."::setup"}($dbname, $dbuser, $dbpass, $dbhost);
546}
547
548print "Installation complete.\n";
549
Note: See TracBrowser for help on using the browser.