diff options
Diffstat (limited to 'checksetup.pl')
-rwxr-xr-x | checksetup.pl | 931 |
1 files changed, 931 insertions, 0 deletions
diff --git a/checksetup.pl b/checksetup.pl new file mode 100755 index 000000000..5374212b0 --- /dev/null +++ b/checksetup.pl @@ -0,0 +1,931 @@ +#!/usr/bonsaitools/bin/perl -w +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public License +# Version 1.0 (the "License"); you may not use this file except in +# compliance with the License. You may obtain a copy of the License at +# http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS IS" +# basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the +# License for the specific language governing rights and limitations +# under the License. +# +# Contributor(s): Holger Schurig <holgerschurig@nikocity.de> +# Terry Weissman <terry@mozilla.org> +# +# +# Direct any questions on this source code to +# +# Holger Schurig <holgerschurig@nikocity.de> +# +# +# +# Hey, what's this? +# +# 'checksetup.pl' is a script that is supposed to run during installation +# time and also after every upgrade. +# +# The goal of this script is to make the installation even more easy. +# It does so by doing things for you as well as testing for problems +# early. +# +# And you can re-run it whenever you want. Especially after Bugzilla +# get's updated you SHOULD rerun it. Because then it may update your +# SQL table definitions so that they are again in sync with the code. +# +# So, currently this module does: +# +# - check for required perl modules +# - set defaults for local configuration variables +# - create and populate the data directory after installation +# - set the proper rights for the *.cgi, *.html ... etc files +# - check if the code can access MySQL +# - creates the database 'bugs' if the database does not exist +# - creates the tables inside the database if they don't exist +# - automatically changes the table definitions of older BugZilla +# installations +# - populates the groups +# - changes already existing SQL tables if you change your local +# settings, e.g. when you add a new platform +# +# People that install this module locally are not supposed to modify +# this script. This is done by shifting the user settable stuff intp +# a local configuration file 'localconfig'. When this file get's +# changed and 'checkconfig.pl' will be re-run, then the user changes +# will be reflected back into the database. +# +# Developers however have to modify this file at various places. To +# make this easier, I have added some special comments that one can +# search for. +# +# To Search for +# +# add/delete local configuration variables --LOCAL-- +# check for more prerequired modules --MODULES-- +# change the defaults for local configuration vars --LOCAL-- +# update the assigned file permissions --CHMOD-- +# add more MySQL-related checks --MYSQL-- +# change table definitions --TABLE-- +# add more groups --GROUPS-- +# +# Note: sometimes those special comments occur more then once. For +# example, --LOCAL-- is at least 3 times in this code! --TABLE-- +# also is used more than once. So search for every occurence! +# + + + + + + +########################################################################### +# Global definitions +########################################################################### + +use diagnostics; +use strict; + + + +# +# This are the --LOCAL-- variables defined in 'localconfig' +# + +use vars qw( + $webservergroup + $db_host $db_port $db_name $db_user + @severities @priorities @opsys @platforms +); + + + + + +########################################################################### +# Check required module +########################################################################### + +# +# Here we check for --MODULES-- +# + +print "Checking perl modules ...\n"; +unless (eval "require 5.004") { + die "Sorry, you need at least Perl 5.004\n"; +} + +unless (eval "require DBI") { + die "Please install the DBI module. You can do this by running (as root)\n\n", + " perl -MCPAN -eshell\n", + " install DBI\n"; +} + +unless (eval "require Data::Dumper") { + die "Please install the Data::Dumper module. You can do this by running (as root)\n\n", + " perl -MCPAN -eshell\n", + " install Data::Dumper\n"; +} + +unless (eval "require Mysql") { + die "Please install the Mysql database driver. You can do this by running (as root)\n\n", + " perl -MCPAN -eshell\n", + " install Msql-Mysql\n\n", + "Be sure to enable the Mysql emulation!"; +} + +unless (eval "require Date::Parse") { + die "Please install the Date::Parse module. You can do this by running (as root)\n\n", + " perl -MCPAN -eshell\n", + " install Date::Parse\n"; +} + +# The following two modules are optional: +my $charts = 0; +$charts++ if eval "require GD"; +$charts++ if eval "require Chart::Base"; +if ($charts != 2) { + print "If you you want to see graphical bug dependency charts, you may install\n", + "the optional libgd and the Perl modules GD and Chart::Base, e.g. by\n", + "running (as root)\n\n", + " perl -MCPAN -eshell\n", + " install GD\n", + " install Chart::Base\n"; +} + + + + + +########################################################################### +# Check and update local configuration +########################################################################### + +# +# This is quite tricky. But fun! +# +# First we read the file 'localconfig'. And then we check if the variables +# we need to be defined are defined. If not, localconfig will be amended by +# the new settings and the user informed to check this. The program then +# stops. +# +# Why do it this way around? +# +# Assume we will enhance Bugzilla and eventually more local configuration +# stuff arises on the horizon. +# +# But the file 'localconfig' is not in the Bugzilla CVS or tarfile. It should +# not be there so that we never overwrite user's local setups accidentally. +# Now, we need a new variable. We simply add the necessary stuff to checksetup. +# The user get's the new version of Bugzilla from the CVS, runs checksetup +# and checksetup finds out "Oh, there is something new". Then it adds some +# default value to the user's local setup and informs the user to check that +# to see if that is what the user wants. +# +# Cute, ey? +# + +print "Checking user setup ...\n"; +do 'localconfig'; +my $newstuff = ""; +sub LocalVar ($$) +{ + my ($name, $definition) = @_; + + # Is there a cleaner way to test if the variable defined in scalar $name + # is defined or not? + my $defined = 0; + $_ = "\$defined = 1 if defined $name;"; + eval $_; + return if $defined; + + $newstuff .= " " . $name; + open FILE, '>>localconfig'; + print FILE $definition, "\n\n"; + close FILE; +} + + + +# +# Set up the defaults for the --LOCAL-- variables below: +# + + +LocalVar('$webservergroup', ' +# +# This is the group your web server runs on. +# If you have a windows box, ignore this setting. +# If you do not wish for checksetup to adjust the permissions of anything, +# set this to "". +# If you set this to anything besides "", you will need to run checksetup.pl +# as root. +$webservergroup = "nobody"; +'); + + + +LocalVar('$db_host', ' +# +# How to access the SQL database: +# +$db_host = "localhost"; # where is the database? +$db_port = 3306; # which port to use +$db_name = "bugs"; # name of the MySQL database +$db_user = "bugs"; # user to attach to the MySQL database +'); + + + +LocalVar('@severities', ' +# +# Which bug and feature-request severities do you want? +# +@severities = ( + "blocker", + "critical", + "major", + "normal", + "minor", + "trivial", + "enhancement" +); +'); + + + +LocalVar('@priorities', ' +# +# Which priorities do you want to assign to bugs and feature-request? +# +@priorities = ( + "P1", + "P2", + "P3", + "P4", + "P5" +); +'); + + + +LocalVar('@opsys', ' +# +# What operatings systems may your products run on? +# +@opsys = ( + "All", + "Windows 3.1", + "Windows 95", + "Windows 98", + "Windows NT", + "Mac System 7", + "Mac System 7.5", + "Mac System 7.6.1", + "Mac System 8.0", + "Mac System 8.5", + "Mac System 8.6", + "AIX", + "BSDI", + "HP-UX", + "IRIX", + "Linux", + "FreeBSD", + "OSF/1", + "Solaris", + "SunOS", + "Neutrino", + "OS/2", + "BeOS", + "OpenVMS", + "other" +); +'); + + + +LocalVar('@platforms', ' +# +# What hardware platforms may your products run on? +# +@platforms = ( + "All", + "DEC", + "HP", + "Macintosh", + "PC", + "SGI", + "Sun", + "Other" +); +'); + + + + +if ($newstuff ne "") { + print "This version of Bugzilla contains some variables that you may \n", + "to change and adapt to your local settings. Please edit the file\n", + "'localconfig' and return checksetup.pl\n\n", + "The following variables are new to localconfig since you last ran\n", + "checksetup.pl: $newstuff\n"; + exit; +} + + + + + +########################################################################### +# Check data directory +########################################################################### + +# +# Create initial --DATA-- directory and make the initial empty files there: +# + +unless (-d 'data') { + print "Creating data directory ...\n"; + mkdir 'data', 0770; + if ($webservergroup eq "") { + chmod 0777, 'data'; + } + open FILE, '>>data/comments'; close FILE; + open FILE, '>>data/nomail'; close FILE; + open FILE, '>>data/mail'; close FILE; + chmod 0666, glob('data/*'); +} + +# Just to be sure ... +unlink "data/versioncache"; + + + + + +########################################################################### +# Set proper rights +########################################################################### + +# +# Here we use --CHMOD-- and friends to set the file permissions +# +# The rationale is that the web server generally runs as nobody and so the cgi +# scripts should not be writable for nobody, otherwise someone may be possible +# to change the cgi's when exploiting some security flaw somewhere (not +# necessarily in Bugzilla!) +# +# Also, some *.pl files are executable, some are not. +# +# +++ Can anybody tell me what a Windows Perl would do with this code? +# + + +# Funny! getgrname returns the GID if fed with NAME ... + +if ($webservergroup ne "") { + my $webservergid = getgrnam($webservergroup); + chown 0, $webservergid, glob('*'); + chmod 0640, glob('*'); + + chmod 0750, glob('*.cgi'), + 'processmail', + 'whineatnews.pl', + 'collectstats.pl', + 'checksetup.pl'; +} + + + + + +########################################################################### +# Check MySQL setup +########################################################################### + +# +# Check if we have access to --MYSQL-- +# + +# This settings are not yet changeable, because other code depends on it: + +my $db_base = 'mysql'; +my $db_pass = ''; # Password to attach to the MySQL database + +use DBI; + +# get a handle to the low-level DBD driver +my $drh = DBI->install_driver($db_base) + or die "Can't connect to the $db_base. Is the database installed and up and running?\n"; + +# Do we have the database itself? +my @databases = $drh->func($db_host, $db_port, '_ListDBs'); +unless (grep /^$db_name$/, @databases) { + print "Creating database $db_name ...\n"; + $drh->func('createdb', $db_name, 'admin') + or die "The '$db_name' database does not exist. I tried to create the database,\n", + "but that didn't work, probably because of access rigths. Read the README\n", + "file and the documentation of $db_base to make sure that everything is\n", + "set up correctly.\n"; +} + + + +# now get a handle to the database: +my $connectstring = "dbi:$db_base:$db_name:host=$db_host:port=$db_port"; +my $dbh = DBI->connect($connectstring, $db_user, $db_pass) + or die "Can't connect to the table '$connectstring'.\n", + "Have you read Bugzilla's README? Have you read the doc of '$db_name'?\n"; + +END { $dbh->disconnect if $dbh } + + + + + +########################################################################### +# Table definitions +########################################################################### + +# +# The following hash stores all --TABLE-- definitions. This will be used +# to automatically create those tables that don't exist. The code is +# safer than the make*.sh shell scripts used to be, because they won't +# delete existing tables. +# +# If you want intentionally do this, yon can always drop a table and re-run +# checksetup, e.g. like this: +# +# $ mysql bugs +# mysql> drop table votes; +# mysql> exit; +# $ ./checksetup.pl +# +# If you change one of those field definitions, then also go below to the +# next occurence of the string --TABLE-- (near the end of this file) to +# add the code that updates older installations automatically. +# + + +my %table; + +$table{bugs_activity} = + 'bug_id mediumint not null, + who mediumint not null, + bug_when datetime not null, + field varchar(64) not null, + oldvalue tinytext, + newvalue tinytext, + + index (bug_id), + index (bug_when), + index (field)'; + + +$table{attachments} = + 'attach_id mediumint not null auto_increment primary key, + bug_id mediumint not null, + creation_ts timestamp, + description mediumtext not null, + mimetype mediumtext not null, + ispatch tinyint, + filename mediumtext not null, + thedata longblob not null, + submitter_id mediumint not null, + + index(bug_id), + index(creation_ts)'; + + +$table{bugs} = + 'bug_id mediumint not null auto_increment primary key, + groupset bigint not null, + assigned_to mediumint not null, # This is a comment. + bug_file_loc text, + bug_severity enum($severities) not null, + bug_status enum("NEW", "ASSIGNED", "REOPENED", "RESOLVED", "VERIFIED", "CLOSED") not null, + creation_ts datetime not null, + delta_ts timestamp, + short_desc mediumtext, + long_desc mediumtext, + op_sys enum($opsys) not null, + priority enum($priorities) not null, + product varchar(64) not null, + rep_platform enum($platforms), + reporter mediumint not null, + version varchar(16) not null, + component varchar(50) not null, + resolution enum("", "FIXED", "INVALID", "WONTFIX", "LATER", "REMIND", "DUPLICATE", "WORKSFORME") not null, + target_milestone varchar(20) not null, + qa_contact mediumint not null, + status_whiteboard mediumtext not null, + votes mediumint not null, + + index (assigned_to), + index (creation_ts), + index (delta_ts), + index (bug_severity), + index (bug_status), + index (op_sys), + index (priority), + index (product), + index (reporter), + index (version), + index (component), + index (resolution), + index (target_milestone), + index (qa_contact), + index (votes)'; + + +$table{cc} = + 'bug_id mediumint not null, + who mediumint not null, + + index(bug_id), + index(who)'; + + +$table{components} = + 'value tinytext, + program varchar(64), + initialowner tinytext not null, # Should arguably be a mediumint! + initialqacontact tinytext not null, # Should arguably be a mediumint! + description mediumtext not null'; + + +$table{dependencies} = + 'blocked mediumint not null, + dependson mediumint not null, + + index(blocked), + index(dependson)'; + + +# Group bits must be a power of two. Groups are identified by a bit; sets of +# groups are indicated by or-ing these values together. +# +# isbuggroup is nonzero if this is a group that controls access to a set +# of bugs. In otherword, the groupset field in the bugs table should only +# have this group's bit set if isbuggroup is nonzero. +# +# User regexp is which email addresses are initially put into this group. +# This is only used when an email account is created; otherwise, profiles +# may be individually tweaked to add them in and out of groups. + +$table{groups} = + 'bit bigint not null, + name varchar(255) not null, + description text not null, + isbuggroup tinyint not null, + userregexp tinytext not null, + + unique(bit), + unique(name)'; + + +$table{logincookies} = + 'cookie mediumint not null auto_increment primary key, + userid mediumint not null, + cryptpassword varchar(64), + hostname varchar(128), + lastused timestamp, + + index(lastused)'; + + +$table{products} = + 'product varchar(64), + description mediumtext, + milestoneurl tinytext not null, + disallownew tinyint not null, + votesperuser smallint not null'; + +$table{profiles} = + 'userid mediumint not null auto_increment primary key, + login_name varchar(255) not null, + password varchar(16), + cryptpassword varchar(64), + realname varchar(255), + groupset bigint not null, + emailnotification enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges", + + index(login_name)'; + + +$table{versions} = + 'value tinytext, + program varchar(64)'; + + +$table{votes} = + 'who mediumint not null, + bug_id mediumint not null, + count smallint not null, + + index(who), + index(bug_id)'; + + + + + +########################################################################### +# Create tables +########################################################################### + +# The current DBI::mysql tells me to use this: +#my @tables = map { $_ =~ s/.*\.//; $_ } $dbh->tables(); +# but that doesn't work on a freshly created database, so I still use +my @tables = $dbh->func('_ListTables'); +#print 'Tables: ', join " ", @tables, "\n"; + +my $severities = '"' . join('", "', @severities) . '"'; +my $priorities = '"' . join('", "', @priorities) . '"'; +my $opsys = '"' . join('", "', @opsys) . '"'; +my $platforms = '"' . join('", "', @platforms) . '"'; + +# go throught our %table hash and create missing tables +while (my ($tabname, $fielddef) = each %table) { + next if grep /^$tabname$/, @tables; + print "Creating table $tabname ...\n"; + $fielddef =~ s/\$severities/$severities/; + $fielddef =~ s/\$priorities/$priorities/; + $fielddef =~ s/\$opsys/$opsys/; + $fielddef =~ s/\$platforms/$platforms/; + + $dbh->do("CREATE TABLE $tabname (\n$fielddef\n)") + or die "Could not create table '$tabname'. Please check your '$db_base' access.\n"; +} + + + + + +########################################################################### +# Populate groups table +########################################################################### + +# +# This subroutine checks if a group exist. If not, it will be automatically +# created with the next available bit set +# + +sub AddGroup ($$) +{ + my ($name, $desc) = @_; + + # does the group exist? + my $sth = $dbh->prepare("SELECT name FROM groups WHERE name='$name'"); + $sth->execute; + return if $sth->rows; + + # get highest bit number + $sth = $dbh->prepare("SELECT bit FROM groups ORDER BY bit DESC"); + $sth->execute; + my @row = $sth->fetchrow_array; + + # normalize bits + my $bit; + if (defined $row[0]) { + $bit = $row[0] << 1; + } else { + $bit = 1; + } + + + print "Adding group $name ...\n"; + $sth = $dbh->prepare('INSERT INTO groups + (bit, name, description, userregexp) + VALUES (?, ?, ?, ?)'); + $sth->execute($bit, $name, $desc, ""); +} + + +# +# BugZilla uses --GROUPS-- to assign various rights to it's users. +# + +AddGroup 'tweakparams', 'Can tweak operating parameters'; +AddGroup 'editgroupmembers', 'Can put people in and out of groups that they are members of.'; +AddGroup 'creategroups', 'Can create and destroy groups.'; +AddGroup 'editcomponents', 'Can create, destroy, and edit components.'; +#AddGroup 'editproducts', 'Can create, destroy, and edit products.'; + + +########################################################################### +# Create initial test product if there are no products present. +########################################################################### + +my $sth = $dbh->prepare("SELECT product FROM products"); +$sth->execute; +if ($sth->rows == 0) { + print "Creating initial dummy product 'TestProduct' ...\n"; + $sth = $dbh->prepare('INSERT INTO products(product, description) VALUES ("TestProduct", "This is a test product. This ought to be blown away and replaced with real stuff in a finished installation of bugzilla.")'); + $sth->execute(); + $sth = $dbh->prepare('INSERT INTO versions (value, program) VALUES ("other", "TestProduct")'); + $sth->execute(); + $sth = $dbh->prepare('INSERT INTO components (value, program, description) VALUES ("TestComponent", "TestProduct", "This is a test component in the test product database. This ought to be blown away and replaced with real stuff in a finished installation of bugzilla.")'); + $sth->execute(); +} + + + + + + +########################################################################### +# Detect changed local settings +########################################################################### + +# +# Check if the enums in the bugs table return the same values that are defined +# in the various locally changeable variables. If this is true, then alter the +# table definition. +# + +sub GetFieldDef ($$) +{ + my ($table, $field) = @_; + my $sth = $dbh->prepare("SHOW COLUMNS FROM $table"); + $sth->execute; + + while (my $ref = $sth->fetchrow_arrayref) { + next if $$ref[0] ne $field; + return $ref; + } +} + +sub CheckEnumField ($$@) +{ + my ($table, $field, @against) = @_; + + my $ref = GetFieldDef($table, $field); + #print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n"; + + $_ = "enum('" . join("','", @against) . "')"; + if ($$ref[1] ne $_) { + print "Updating field $field in table $table ...\n"; + $_ .= " NOT NULL" if $$ref[3]; + $dbh->do("ALTER TABLE $table + CHANGE $field + $field $_"); + } +} + + + +# +# This code changes the enum types of some SQL tables whenever you change +# some --LOCAL-- variables +# + +CheckEnumField('bugs', 'bug_severity', @severities); +CheckEnumField('bugs', 'priority', @priorities); +CheckEnumField('bugs', 'op_sys', @opsys); +CheckEnumField('bugs', 'rep_platform', @platforms); + + + + + +########################################################################### +# Update the tables to the current definition +########################################################################### + +# +# As time passes, fields in tables get deleted, added, changed and so on. +# So we need some helper subroutines to make this possible: +# + +sub ChangeFieldType ($$$) +{ + my ($table, $field, $newtype) = @_; + + my $ref = GetFieldDef($table, $field); + #print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n"; + + if ($$ref[1] ne $newtype) { + print "Updating field type $field in table $table ...\n"; + $newtype .= " NOT NULL" if $$ref[3]; + $dbh->do("ALTER TABLE $table + CHANGE $field + $field $newtype"); + } +} + +sub RenameField ($$$) +{ + my ($table, $field, $newname) = @_; + + my $ref = GetFieldDef($table, $field); + return unless $ref; # already fixed? + #print "0: $$ref[0] 1: $$ref[1] 2: $$ref[2] 3: $$ref[3] 4: $$ref[4]\n"; + + if ($$ref[1] ne $newname) { + print "Updating field $field in table $table ...\n"; + my $type = $$ref[1]; + $type .= " NOT NULL" if $$ref[3]; + $dbh->do("ALTER TABLE $table + CHANGE $field + $newname $type"); + } +} + +sub AddField ($$$) +{ + my ($table, $field, $definition) = @_; + + my $ref = GetFieldDef($table, $field); + return if $ref; # already added? + + print "Adding new field $field to table $table ...\n"; + $dbh->do("ALTER TABLE $table + ADD COLUMN $field $definition"); +} + +sub DropField ($$) +{ + my ($table, $field) = @_; + + my $ref = GetFieldDef($table, $field); + return unless $ref; # already dropped? + + print "Deleting unused field $field from table $table ...\n"; + $dbh->do("ALTER TABLE $table + DROP COLUMN $field"); +} + + + +# 5/12/99 Added a pref to control how much email you get. This needs a new +# column in the profiles table, so feed the following to mysql: + +AddField('profiles', 'emailnotification', 'enum("ExcludeSelfChanges", "CConly", "All") not null default "ExcludeSelfChanges"'); + + + +# 6/22/99 Added an entry to the attachments table to record who the +# submitter was. Nothing uses this yet, but it still should be recorded. + +AddField('attachments', 'submitter_id', 'mediumint not null'); + +# +# One could even populate this field automatically, e.g. with +# +# unless (GetField('attachments', 'submitter_id') { +# AddField ... +# populate +# } +# +# For now I was too lazy, so you should read the README :-) + + + +# 9/15/99 Apparently, newer alphas of MySQL won't allow you to have "when" +# as a column name. So, I have had to rename a column in the bugs_activity +# table. You must feed the below to mysql or you won't work at all. + +RenameField ('bugs', 'when', 'bug_when'); + + + +# 10/11/99 Restructured voting database to add a cached value in each bug +# recording how many total votes that bug has. While I'm at it, I removed +# the unused "area" field from the bugs database. It is distressing to +# realize that the bugs table has reached the maximum number of indices +# allowed by MySQL (16), which may make future enhancements awkward. + +DropField('bugs', 'area'); +AddField('bugs', 'votes', 'mediumint not null, add index (votes)'); +AddField('products', 'votesperuser', 'mediumint not null'); + + + +# The product name used to be very different in various tables. +# +# It was varchar(16) in bugs +# tinytext in components +# tinytext in products +# tinytext in versions +# +# tinytext is equivalent to varchar(255), which is quite huge, so I change +# them all to varchar(64). + +ChangeFieldType ('bugs', 'product', 'varchar(64)'); +ChangeFieldType ('components', 'program', 'varchar(64)'); +ChangeFieldType ('products', 'product', 'varchar(64)'); +ChangeFieldType ('versions', 'program', 'varchar(64)'); + + + +# +# If you had to change the --TABLE-- definition in any way, then add your +# differential change code *** A B O V E *** this comment. +# +# That is: if you add a new field, you first search for the first occurence +# of --TABLE-- and add your field to into the table hash. This new setting +# would be honored for every new installation. Then add your +# AddField/DropField/ChangeFieldType/RenameField code above. This would then +# be honored by everyone who updates his Bugzilla installation. +# |