#!/usr/local/bin/perl

################################################################################
# WeBWorK
# 
# Copyright (c) 1995-2001 WeBWorK Team, University of Rochester
# All rights reserved
# 
# $Id$
################################################################################

#################### initialization

require 5.000;
use Cwd;
use File::Copy;

# define built-in defaults
my $DEFAULT_PERL_PATH = '/usr/local/bin/perl';
my $DEFAULT_CGI_URL = '/cgi-bin/webwork/system/';
my $DEFAULT_HTML_URL = '/webwork_system_html/';
my $DEFAULT_ADMIN_GROUP = 'wwadmin';

# define code strings
my $CGI_DEBUG_TAG = 'WeBWorKCGIDebugURL';
my $CGI_NODEBUG_TAG = 'WeBWorKCGINoDebugURL';
my $LIB_INIT_LINE_TAG = 'WeBWorKInitLine';

# scope and undefine setup variables
$no_prompts              = undef;
$system_setup_mode       = undef;
$mainDir                 = undef;
$perlPath                = undef;
$cgiURL                  = undef;
$htmlURL                 = undef;
$groupName               = undef;
$update_stuff_in_courses = undef;
$chgrp_files_and_dirs    = undef;
$chmod_files_and_dirs    = undef;
$local_preprocessor      = undef;
$local_postprocessor     = undef;

# read defaults in from defaults file
my $DEFAULTS_FILE;
if($ARGV[0] and $ARGV[0] eq '-d') {
	if($ARGV[1]) { $DEFAULTS_FILE = $ARGV[1]; }
	else { $DEFAULTS_FILE = ((getpwuid $<)[7]) . '/system_webwork_setup.defaults'; }
	if(-e $DEFAULTS_FILE) {
		print "Reading defaults file...";
		require $DEFAULTS_FILE;
		print " done.\n";
	}
}

#################### text strings

my $INTRO_TEXT = q{
+----------------------+
| System Webwork Setup |
+----------------------+

This script is used to setup the main WeBWorK system. It will create 
initialization files, set groups and permissions for files and directories, 
and modify files. A "demo" or "working" version of the system can be setup.

You will need the following information:

1) The location of your WeBWorK system directory.
   Example: /usr/local/webwork/system/
   Example: /var/www/webwork/system/

2) The path to perl on your system.
   Example: /usr/bin/perl
   Example: /usr/local/bin/perl

3) The group containing the users who have the authority to modify the
   webwork system files. Who ever runs this script must be a member of
   this group. (Note: This is not required for the demo mode.)
   Example: wwadmin

4) The URL to the WeBWorK CGI scripts.
   Example: /cgi-bin/webwork/system/ (preferred)

5) The URL to the static system_html files.
   Example: /webwork_system_html/

};

my $MODE_TEXT = q{

You can set up a "working" or a "demo" WeBWorK system.  A "demo" system 
should only be used as a sample system, never for a system that will be used 
with actual courses with real students.  The main difference between a 
"working" version and a "demo" version is that in a "working" version you 
will be promped to enter a group (e.g. wwadmin) where as in a "demo" 
version, the group will be set yo your own default group (e.g. fac). Anyone 
in the group will have permission to modify all webwork files. You can set 
up "working" and "demo" courses under either a "working" or a "demo" system, 
but normally you would not set up a "working" course under a "demo" system.

};

my $MAIN_DIR_TEXT = q{

The directory containing the WeBWorK system files (as well as this
script) is known as the system directory. In order to modify system
files, I need to know the location of the system directory for this
installation.

};

my $PERL_TEXT = q{

WeBWorK needs to know the path of your perl binary, so that this
information can be used in the headers of cgi scripts. Please make sure
that the version of perl you specify is 5.004 or later.

};

my $CGI_URL_TEXT = q{

In order for generated HTML to be able to invoke CGI scripts, WeBWorK
needs to know the URL which points to the main WeBWorK system cgi
subdirectory. If both static HTML and CGIs reside on the same host
(which is true in most cases), you can omit the http://hostname section
of the URL, but make sure the leading slash is present.

};

my $HTML_URL_TEXT = q{

WeBWorK also needs to know the URL of the main HTML directory, the
system_html subdirectory. If both static HTML and CGIs reside on the
same host (which is true in most cases), you can omit the
http://hostname section of the URL, but make sure the leading slash is
present.

};

my $GROUP_TEXT = q{

WeBWorK needs to know what the admin group is. This group should have been 
set up by your system administrator and must contain at least your user ID. 
All files and directories created will have this as their group.

};

my $COURSE_PERMS_TEXT = q{

You have the option to set permissions for the courses directory. If this is 
an inital setup, you should probably do so. If this is not an initial setup, 
the courses directory is already set up, or the courses directory is shared 
between WeBWorK installations, your probably shouldn't.

};

my $CHGRP_TEXT = q{

You have to option to set the group for all system files and directories. If 
this is an initial setup, you should probably do this. If this is not an 
initial setup, or you have customized the way system files should be 
grouped, you probably shoudn't.

};

my $CHMOD_TEXT = q{

You have to option to set the permissions for all system files and 
directories. If this is an initial setup, you should probably do this. If 
this is not an initial setup, or you have customized the permissions for the 
system files, you probably shoudn't.

};

my $CONFIRM_TEXT = q{

Now that I have the necessary information, I can begin modifying the WeBWorK 
system files.

};

my $DONE_TEXT = q{

The system setup script is done. Please read the above messages
carefully to determine if there have been any errors. If so, correct the
problem (usually a permissions problem) and run this script again.

};





################################################################################
########## Ask some questions, perform some logic. #############################
################################################################################





#################### introduction

my $temp;

unless($no_prompts) {
	page($INTRO_TEXT);
	$temp = questionChar("Do you want to continue with setup?", 'y', 'y', 'n');
	exit unless $temp eq 'y';
}
print "Okay, here we go...\n";

#################### working or demo?

unless(defined $system_setup_mode) {
	page($MODE_TEXT);
	$temp = questionChar("Shall we set up a working version or a demo version?", 'w', 'w', 'd');
	$system_setup_mode = "working" if $temp eq 'w';
	$system_setup_mode = "demo" if $temp eq 'd';
}
print "System setup mode is: $system_setup_mode\n";

#################### main directory

unless(defined $mainDir) {
	page($MAIN_DIR_TEXT);
	$mainDir = questionString("Where is the WeBWorK system directory?", cwd());
	$mainDir .= '/' unless $mainDir =~ m|/$|;  # ensure trailing slash
}
print "We'll use $mainDir as WeBWorK's system directory.\n";

#################### perl path

unless(defined $perlPath) {
	page($PERL_TEXT);
	$perlPath = questionString("What is the full path to PERL?", $DEFAULT_PERL_PATH);
}
print "Path to PERL binary is: $perlPath\n";

#################### CGI URL

unless(defined $cgiURL) {
	page($CGI_URL_TEXT);
	while (1) {
		$cgiURL = questionString("What is the CGI URL?", $DEFAULT_CGI_URL);
		if( ($cgiURL =~ m|^/|) or ($cgiURL =~ m|^http://|) ) {
			last;
		} else {
			$temp = questionChar("That doesn't look like a valid URL. Would you like to use it anyway?", 'n', 'y', 'n');
			last if $temp eq 'y';
		}
	}
	$cgiURL .= "/" unless $cgiURL =~ m"/$"; # ensure trailing slash
}
print "CGI URL is: $cgiURL\n";

#################### HTML URL

unless(defined $htmlURL) {
	page($HTML_URL_TEXT);
	while (1) {
		$htmlURL = questionString("What is the HTML URL?", $DEFAULT_HTML_URL);
		if( ($htmlURL =~ m|^/|) or ($htmlURL =~ m|^http://|) ) {
			last;
		} else {
			$temp = questionChar("That doesn't look like a valid URL. Would you like to use it anyway?", 'n', 'y', 'n');
			last if $temp eq 'y';
		}
	}
	$htmlURL .= "/" unless $htmlURL =~ m"/$" ;
}
print "HTML URL is: $htmlURL\n";

#################### admin group

unless(defined $groupName) {
	my ($userName, $userGID) = (getpwuid $<)[0,3];
	my $userGroupName = (getgrgid $userGID)[0];
	
	if ($system_setup_mode eq 'demo')  {
		# in demo mode, the group is set to the user's primary group
		$groupName = $userGroupName;
	} else {
		# in working mode, we get to chose
		page($GROUP_TEXT);
		my $validGroup = 0;
		while(1) {
			$groupName = questionString("What is the admin group name?", $DEFAULT_ADMIN_GROUP);
			my @members = split / /, (getgrnam $groupName)[3];
			if($groupName eq $userGroupName) {
				print "$groupName is ${userName}'s primary group. Good.\n";
			} elsif(grep /$userName/, @members) {
				print "$userName is a member of $groupName. Good.\n";
				last;
			} elsif($< == 0) { # we're root!
				print "$userName isn't a member of $groupName, but you're root, so who cares?\n";
				last;
			} else {
				print "That group is not valid. Please make sure the group exists and you are a member.\n";
			}
		}
	}
}
print "Admin group is: $groupName\n";

#################### chmod courses directory

unless(defined $update_stuff_in_courses) {
	page($COURSE_PERMS_TEXT);
	$temp = questionChar("Do you want to set default $system_setup_mode permissions for the courses directory?", 'y', 'y', 'n');
	$update_stuff_in_courses = ($temp eq 'y');
}
print "Permissions ", ($update_stuff_in_courses ? "will" : "will not"), " be set for the courses directory.\n";


#################### chgrp files/directories

$system_setup_mode eq "demo" and $chgrp_files_and_dirs = 1;
unless(defined $chgrp_files_and_dirs) {
	page($CHGRP_TEXT);
	$temp = questionChar("Do you want to set the group for system files and directories?", 'y', 'y', 'n');
	$chgrp_files_and_dirs = ($temp eq 'y');
}
print "Group ", ($chgrp_files_and_dirs ? "will" : "will not"), " be set for system files and directories.\n";

#################### chmod files/directories

$system_setup_mode eq "demo" and $chmod_files_and_dirs = 1;
unless(defined $chmod_files_and_dirs) {
	page($CHMOD_TEXT);
	$temp = questionChar("Do you want to set the permissions for system files and directories?", 'y', 'y', 'n');
	$chmod_files_and_dirs = ($temp eq 'y');
}
print "Permissions ", ($chmod_files_and_dirs ? "will" : "will not"), " be set for system files and directories.\n";

#################### make sure we want to actually do this

unless($no_prompts) {
	print $CONFIRM_TEXT;
	$temp = questionChar("Do you want to continue with setup?", 'y', 'y', 'n');
	exit unless $temp eq 'y';
}
print "\Going to make changes now...\n\n";





################################################################################
########## Now we start changing things... #####################################
################################################################################





#################### run local preprocessor

if(defined $local_preprocessor) {
	print "Executing local preprocessor...\n";
	&$local_preprocessor;
	print "Done with local preprocessor.\n";
}

#################### update #! and use lines
# uses: $mainDir, $perlPath

print "Fixing #! lines...\n";

foreach my $dir ("${mainDir}cgi/cgi-scripts", "${mainDir}scripts", "${mainDir}courseScripts") {
	foreach my $file (<${dir}/*.pl>) {
		fixFile($file);
	}
}

sub fixFile
{
	my ($file) = @_;
	
	# read the file
	open FILE, $file || die "Couldn't open $file for reading.";
	my @lines = <FILE>;
	close FILE || die "Couldn't close $file after reading.";
	
	# fix perl path
	$lines[0] =~ m/^#!(\S*)/;
	if($1 ne $perlPath) {
		$lines[0] =~ s/^#!\S*/#!$perlPath/;
		open FILE, ">$file" || die "Couldn't open $file for writing.";
		print FILE @lines;
		close FILE || die "Couldn't close $file for writing.";
	}
}

print "done fixing #! and \"use\" lines.\n\n";

#################### write webworkConfig.pm file
# uses: $mainDir, $cgiURL, $htmlURL

print "Writing lib/webworkConfig.pm file...\n";
open CONFIG_FILE, ">${mainDir}lib/webworkConfig.pm";
print CONFIG_FILE<<END_OF_CONFIG_FILE;
package Global;

################################################################################
# WeBWorK
# 
# Copyright (c) 1995-2001 WeBWorK Team, University of Rochester
# All rights reserved
################################################################################

# $cgiDebugMode, if enabled, will call the debug wrapper scripts instead of the
# cgi scripts themselves, allowing for header output, etc. In addition to
# setting $cgiDebugMode =1, you will also need to enable debugging in the
# wrapper scripts that you wish to debug, by setting $debug = 1. Wrapper
# scripts are found in the directory referred to by $cgiWebworkURL, which is
# usually webwork/system/cgi.
\$cgiDebugMode = 0;

\$mainDirectory = "$mainDir";
\$htmlWebworkURL = "$htmlURL";
\$cgiWebworkURL = "$cgiURL";
\$cgiWebworkURL .= "cgi-scripts/" unless \$cgiDebugMode;

1;
END_OF_CONFIG_FILE
close CONFIG_FILE;
print "Done writing lib/webworkConfig.pm file.\n\n";

#################### write webworkInit.pm files
# uses: $mainDir

print "Writing webworkInit.pm files...\n";
foreach my $dir ('cgi/', 'cgi/cgi-scripts/', 'scripts/', 'courseScripts/') {
	open INIT_FILE, ">$mainDir${dir}webworkInit.pm";
	print INIT_FILE<<END_OF_INIT_FILE;
################################################################################
# WeBWorK
# 
# Copyright (c) 1995-2001 WeBWorK Team, University of Rochester
# All rights reserved
################################################################################

use lib '${mainDir}lib/';

1;
END_OF_INIT_FILE
	close INIT_FILE;
}
print "Done writing webworkInit.pm files.\n\n";

#################### chgrp system stuff
# uses: $chgrp_files_and_dirs, $groupName

if($chgrp_files_and_dirs) {
	print "Setting group on system files and directories...\n";
	system "chgrp -R $groupName $mainDir";
	system "chgrp -R $groupName $mainDir/../system_html";
	print "Done setting group.\n\n";
}

#################### chmod system stuff
# uses: $chmod_files_and_dirs

if($chmod_files_and_dirs) {
	print "Setting permissions on system files and directories for $system_setup_mode mode...\n";
	if ($system_setup_mode eq "demo") {
		# get some general permissions for files and directories
		system "find $mainDir $mainDir/../system_html -type d -print0 | xargs -0 chmod 0755";
		system "find $mainDir $mainDir/../system_html -type f -print0 | xargs -0 chmod 0644";
		# add executable privs to scripts
		system "find ${mainDir}cgi ${mainDir}scripts -type f -print0 | xargs -0 chmod 0755";
	} else {
		# get some general permissions for files and directories
		system "find $mainDir $mainDir/../system_html -type d -print0 | xargs -0 chmod 0775";
		system "find $mainDir $mainDir/../system_html -type f -print0 | xargs -0 chmod 0664";
		# add executable privs to scripts
		system "find ${mainDir}cgi ${mainDir}scripts -type f -print0 | xargs -0 chmod 0775";
	}
	# make the log files group writable
	system "find $mainDir/logs -type f -print0 | xargs -0 chmod 0664";	
	# make the error_log file world writable
	system "find $mainDir/logs/error_log  -print0 | xargs -0 chmod 0666";

	print "done setting permissions.\n\n";
}

#################### update couses stuff
# uses: $update_stuff_in_courses

if($update_stuff_in_courses) {
	print "Setting permissions for ../courses directory...\n";
	if ($system_setup_mode eq "demo") {
		chmod(0755, "$mainDir/../courses") or warn "Warning: I can't set permissions for ../courses directory. It's possible that the directory doesn't exist or you don't have permission to change it.\n";
	} else {
		chmod(0775, "$mainDir/../courses") or warn "Warning: I can't set permissions for ../courses directory. It's possible that the directory doesn't exist or you don't have permission to change it.\n";
	}
	print "done permissions.\n\n";
}

#################### run local postprocessor

if(defined $local_postprocessor) {
	print "Executing local postprocessor...\n";
	&$local_postprocessor;
	print "Done with local postprocessor.\n";
}

#################### finish up

page($DONE_TEXT);

################################################################################

sub page
{
	my @string_lines = split /^/, shift; #/
	# not really optimal, but we're going to assume a constant screen height.
	my $SCREEN_HEIGHT = 20;
	while(@string_lines) {
		print join "", @string_lines[0..($SCREEN_HEIGHT>scalar @string_lines ? (scalar @string_lines)-1 : $SCREEN_HEIGHT-1)];
		if(scalar @string_lines >= $SCREEN_HEIGHT) {
			print "\n[Press ENTER to continues...]";
			<STDIN>;
			print "\n";
		}
			@string_lines = @string_lines[$SCREEN_HEIGHT..$#string_lines];
	}
}

sub questionChar
{
	my ($question, $default, @valid) = @_;
	my $answer;
	do {
		print $question, " ";
		foreach (@valid) {
			$_ eq $default and print "[";
			print $_;
			$_ eq $default and print "]";
		}
		print " ";
		$answer = <STDIN>;
		$answer =~ s/^\s*//;
		$answer = substr $answer, 0, 1;
		$answer = lc $answer;
		$answer or $answer = $default;
	} while (not grep(/$answer/, @valid));
	return $answer;
}

sub questionString
{
	my ($question, $default, $emptyOK) = @_;
	my $answer;
	print $question, " [", $default, "] ";
	$answer = <STDIN>;
	chomp $answer;
	$answer =~ s/^\s*//;
	$answer or $answer = $default;
	return $answer;
}

__END__

=head1 NAME

system_webwork_setup.pl - set up the WeBWorK system

=head1 SYNOPSIS

system_webwork_setup.pl [B<-d> [defaults-file]]

=head1 DESCRIPTION

B<system_webwork_setup.pl> gathers the information necessary for configuration of the WeBWorK system. It then edits several WeBWorK system files, and creates F<webworkInit.pm> modules and the F<webworkConfig.pm> module based on the information gathered. It can optionally read defaults from a file specified on the command line or from the file F<$HOME/system_webwork_setup.defaults>.

=head1 OPTIONS

=over 4

=item B<-d>

enables defaults-file processing. If no defaults-file is specified, the file F<$HOME/system_webwork_setup.defaults> will be used.

=head1 DEFAULTS

The F<system_webwork_setup.defaults> file is intended to be used in a situation where B<system_webwork_setup.pl> must be executed frequently, such as in the case of use with CVS, in which the it must be executed after a checkout and some updates. The defaults file is a perl script that will be require'd by B<system_webwork_setup.pl>. It can set any of the following variables in the usual way:

=over 4

=item $no_prompts

Refrain from prompting at the beginning of the script and before making changes. Also supresses the introductory text.

=item $system_setup_mode

Can be set to "working" or "demo". This affects how system file permissions and group ownership are set. In "working" mode, system files are group owned by a webwork admin group (see I<$groupName>) and group writeable while in "demo" mode files are group owned by the current user's default group and are not group writeable.

=item $mainDir

Specify WeBWorK's main directory, which should contain the directories cgi/ scripts/ courseScripts/ and lib/.

=item $perlPath

Specify the path to the perl interpreter which should be used. This is used to set the shebang line in cgi-scripts and scripts.

=item $cgiURL

Specify the externally visible URL to the CGI directory (typically cgi/cgi-scripts/). This will be used when scripts call other scripts.

=item $htmlURL

Specify the externally visible URL to the HTML directory (typically system_html/). This will be used when scripts refer to static graphics and text.

=item $groupName

When in "working" mode, I<$groupName> is the name of the WeBWorK admin group, members of which should have write access to the system files. (The web server should not be in this group!)

=item $update_stuff_in_courses

If true, B<system_webwork_setup.pl> will set permissions in the courses directory and prepare the B<course_webwork_setup.pl> script for execution.

=item $chgrp_files_and_dirs, $chmod_files_and_dirs

If true, the group and/or permissions on system files will be set according to I<$system_setup_mode>.

=item $local_preprocessor, $local_postprocessor

If set, these variables will be called as subroutine references. I<$local_preprocessor> is called before any changes take place, while I<$local_postprocessor> is called after all changes have taken place.

=back

=head1 FILES

$HOME/system_webwork_setup.defaults

=head1 AUTHOR

Samuel Hathaway <sh002i@math.rochester.edu>
