2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2007 Best Practical Solutions, LLC
7 # <jesse@bestpractical.com>
9 # (Except where explicitly superseded by other copyright notices)
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
22 # General Public License for more details.
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/copyleft/gpl.html.
31 # CONTRIBUTION SUBMISSION POLICY:
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
48 # END BPS TAGGED BLOCK }}}
50 use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
52 qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips @Attributes);
54 use lib "@LOCAL_LIB_PATH@";
55 use lib "@RT_LIB_PATH@";
57 #This drags in RT's config.pm
58 # We do it in a begin block because RT::Handle needs to know the type to do its
70 use RT::ScripCondition;
82 'prompt-for-dba-password', 'force', 'debug',
83 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
87 unless ( $args{'action'} ) {
92 $| = 1; #unbuffer that output.
95 my $Handle = RT::Handle->new($RT::DatabaseType);
99 if ( $args{'prompt-for-dba-password'} ) {
100 $args{'dba-password'} = get_dba_password();
101 chomp( $args{'dba-password'} );
104 if ( $args{'action'} eq 'init' ) {
105 $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
106 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
107 print "Now creating a database for RT.\n";
108 if ( $RT::DatabaseType ne 'Oracle' || $args{'dba'} ne $RT::DatabaseUser ) {
111 print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
114 if ( $RT::DatabaseType eq "mysql" ) {
115 # Check which version we're running
116 my ($version) = $dbh->selectrow_hashref("show variables like 'version'")->{Value} =~ /^(\d\.\d+)/;
117 print "*** Warning: RT is unsupported on MySQL versions before 4.0.x\n" if $version < 4;
119 # MySQL must have InnoDB support
120 my $innodb = $dbh->selectrow_hashref("show variables like 'have_innodb'")->{Value};
121 if ( $innodb eq "NO" ) {
122 die "RT requires that MySQL be compiled with InnoDB table support.\n".
123 "See http://dev.mysql.com/doc/mysql/en/InnoDB.html\n";
124 } elsif ( $innodb eq "DISABLED" ) {
125 die "RT requires that MySQL InnoDB table support be enabled.\n".
127 ? "Add 'innodb_data_file_path=ibdata1:10M:autoextend' to the [mysqld] section of my.cnf\n"
128 : "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
132 # SQLite can't deal with the disconnect/reconnect
133 unless ( $RT::DatabaseType eq 'SQLite' ) {
137 if ( $RT::DatabaseType eq "Oracle" ) {
138 $RT::DatabasePassword = $RT::DatabasePassword; #Warning avidance
139 $dbh = DBI->connect( $Handle->DSN, ${RT::DatabaseUser}, ${RT::DatabasePassword} ) || die $DBI::errstr;
141 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr;
144 print "Now populating database schema.\n";
146 print "Now inserting database ACLs\n";
147 insert_acl() unless $RT::DatabaseType eq 'Oracle';
148 print "Now inserting RT core system objects\n";
149 insert_initial_data();
150 print "Now inserting RT data\n";
151 insert_data( $RT::EtcPath . "/initialdata" );
153 elsif ( $args{'action'} eq 'drop' ) {
155 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
158 warn "Database doesn't appear to exist. Aborting database drop.";
163 elsif ( $args{'action'} eq 'insert_initial' ) {
164 insert_initial_data();
166 elsif ( $args{'action'} eq 'insert' ) {
167 insert_data( $args{'datafile'} || ($args{'datadir'}."/content") );
169 elsif ( $args{'action'} eq 'acl' ) {
170 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
171 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
172 insert_acl($args{'datadir'});
174 elsif ( $args{'action'} eq 'schema' ) {
175 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
176 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
177 insert_schema($args{'datadir'});
180 print STDERR "$0 called with an invalid --action parameter\n";
184 # {{{ sub insert_schema
186 my $base_path = (shift || $RT::EtcPath);
188 print "Creating database schema.\n";
190 my $schema_file = $base_path . "/schema." . $RT::DatabaseType;
191 if ( -f $schema_file ) {
192 open( SCHEMA, "<$schema_file" ) or die "Can't open $schema_file: $!";
193 my @lines = <SCHEMA>;
195 my $local_schema_file = $RT::LocalEtcPath . "/schema." . $RT::DatabaseType;
196 if (-f $local_schema_file) {
197 open( SCHEMA_LOCAL, "<$local_schema_file" )
198 or die "Can't open $local_schema_file: $!";
199 push @lines, ';;', <SCHEMA_LOCAL>;
203 foreach my $line (@lines) {
207 if ( $line =~ /;(\s*)$/ ) {
208 $statement =~ s/;(\s*)$//g;
209 push @schema, $statement;
214 local $SIG{__WARN__} = sub {};
215 my $is_local = 0; # local/etc/schema needs to be nonfatal.
216 $dbh->begin_work or die $dbh->errstr;
217 foreach my $statement (@schema) {
218 if ( $statement =~ /^\s*;$/ ) { $is_local = 1; next; }
220 print STDERR "SQL: $statement\n" if defined $args{'debug'};
221 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
222 unless ( $sth->execute or $is_local ) {
223 die "Problem with statement:\n $statement\n" . $sth->errstr;
226 $dbh->commit or die $dbh->errstr;
229 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
231 print "Done setting up database schema.\n";
238 if ( $RT::DatabaseType eq 'Oracle' ) {
241 To delete the tables and sequences of the RT Oracle database by running
248 unless ( $args{'force'} ) {
251 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
252 WARNING: This will erase all data in $RT::DatabaseName.
255 exit unless _yesno();
259 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
261 if ( $RT::DatabaseType eq 'SQLite' ) {
262 unlink $RT::DatabaseName or warn $!;
265 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
272 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
273 if ( $RT::DatabaseType eq 'SQLite' ) {
276 elsif ( $RT::DatabaseType eq 'Pg' ) {
277 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
278 if ( $DBI::errstr ) {
279 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
282 elsif ( $RT::DatabaseType eq 'Oracle' ) {
285 elsif ( $RT::DatabaseType eq 'Informix' ) {
286 $ENV{DB_LOCALE} = 'en_us.utf8';
287 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
290 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
296 sub get_dba_password {
297 print "In order to create or update your RT database,";
298 print "this script needs to connect to your "
301 . $RT::DatabaseHost . " as "
302 . $args{'dba'} . ".\n";
303 print "Please specify that user's database password below. If the user has no database\n";
304 print "password, just press return.\n\n";
307 my $password = ReadLine(0);
315 print "Proceed [y/N]:";
316 my $x = scalar(<STDIN>);
324 my $base_path = (shift || $RT::EtcPath);
326 if ( $RT::DatabaseType =~ /^oracle$/i ) {
327 do $base_path . "/acl.Oracle"
328 || die "Couldn't find ACLS for Oracle\n" . $@;
330 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
331 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
333 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
334 do $base_path . "/acl.mysql"
335 || die "Couldn't find ACLS for mysql in $base_path\n" . $@;
337 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
338 do $base_path . "/acl.Sybase"
339 || die "Couldn't find ACLS for Sybase in $base_path\n" . $@;
341 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
342 do $base_path . "/acl.Informix"
343 || die "Couldn't find ACLS for Informix in $base_path\n" . $@;
345 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
349 die "Unknown RT database type";
353 foreach my $statement (@acl) {
354 print STDERR $statement if $args{'debug'};
355 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
356 unless ( $sth->execute ) {
357 die "Problem with statement:\n $statement\n" . $sth->errstr;
360 print "Done setting up database ACLs.\n";
365 =head2 get_system_dsn
367 Returns a dsn suitable for database creates and drops
368 and user creates and drops
374 my $dsn = $Handle->DSN;
376 #with mysql, you want to connect sans database to funge things
377 if ( $RT::DatabaseType eq 'mysql' ) {
378 $dsn =~ s/dbname=$RT::DatabaseName//;
380 # with postgres, you want to connect to database1
382 elsif ( $RT::DatabaseType eq 'Pg' ) {
383 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
385 elsif ( $RT::DatabaseType eq 'Informix' ) {
386 # with Informix, you want to connect sans database:
387 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
392 sub insert_initial_data {
396 #connect to the db, for actual RT work
398 $RT::Handle = RT::Handle->new();
399 $RT::Handle->Connect();
401 #Put together a current user object so we can create a User object
402 my $CurrentUser = new RT::CurrentUser();
404 print "Checking for existing system user...";
405 my $test_user = RT::User->new($CurrentUser);
406 $test_user->Load('RT_System');
407 if ( $test_user->id ) {
408 print "found!\n\nYou appear to have a functional RT database.\n"
409 . "Exiting, so as not to clobber your existing data.\n";
414 print "not found. This appears to be a new installation.\n";
417 print "Creating system user...";
418 my $RT_System = new RT::User($CurrentUser);
420 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
422 RealName => 'The RT System itself',
424 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
426 LastUpdatedBy => '1',
434 $RT::Handle->Disconnect() unless $RT::DatabaseType eq 'SQLite';
438 # load some sort of data into the database
441 my $datafile = shift;
443 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
446 my $CurrentUser = RT::CurrentUser->new();
447 $CurrentUser->LoadByName('RT_System');
449 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
451 print "Creating Superuser ACL...";
453 my $superuser_ace = RT::ACE->new($CurrentUser);
454 $superuser_ace->_BootstrapCreate(
455 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
456 PrincipalType => 'Group',
457 RightName => 'SuperUser',
458 ObjectType => 'RT::System',
464 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
465 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
468 || die "Couldn't find initial data for import\n" . $@;
471 print "Creating groups...";
472 foreach $item (@Groups) {
473 my $new_entry = RT::Group->new($CurrentUser);
474 my ( $return, $msg ) = $new_entry->_Create(%$item);
475 print "(Error: $msg)" unless $return;
481 print "Creating users...";
482 foreach $item (@Users) {
483 my $new_entry = new RT::User($CurrentUser);
484 my ( $return, $msg ) = $new_entry->Create(%$item);
485 print "(Error: $msg)" unless $return;
491 print "Creating queues...";
492 for $item (@Queues) {
493 my $new_entry = new RT::Queue($CurrentUser);
494 my ( $return, $msg ) = $new_entry->Create(%$item);
495 print "(Error: $msg)" unless $return;
501 print "Creating ACL...";
502 for my $item (@ACL) {
504 my ($princ, $object);
506 # Global rights or Queue rights?
507 if ( $item->{'Queue'} ) {
508 $object = RT::Queue->new($CurrentUser);
509 $object->Load( $item->{'Queue'} );
511 $object = $RT::System;
514 # Group rights or user rights?
515 if ( $item->{'GroupDomain'} ) {
516 $princ = RT::Group->new($CurrentUser);
517 if ( $item->{'GroupDomain'} eq 'UserDefined' ) {
518 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
519 } elsif ( $item->{'GroupDomain'} eq 'SystemInternal' ) {
520 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
521 } elsif ( $item->{'GroupDomain'} eq 'RT::System-Role' ) {
522 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
523 } elsif ( $item->{'GroupDomain'} eq 'RT::Queue-Role' &&
526 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
527 Queue => $object->id);
529 $princ->Load( $item->{'GroupId'} );
532 $princ = RT::User->new($CurrentUser);
533 $princ->Load( $item->{'UserId'} );
537 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
538 Right => $item->{'Right'},
552 if ( @CustomFields ) {
553 print "Creating custom fields...";
554 for $item (@CustomFields) {
555 my $new_entry = new RT::CustomField($CurrentUser);
556 my $values = $item->{'Values'};
557 delete $item->{'Values'};
558 my ( $return, $msg ) = $new_entry->Create(%$item);
560 print "(Error: $msg)\n";
564 foreach my $value ( @{$values} ) {
565 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
566 print "(Error: $emsg)\n" unless $eval;
569 if ( $item->{LookupType} && !exists $item->{'Queue'} ) { # enable by default
570 my $ocf = RT::ObjectCustomField->new($CurrentUser);
571 $ocf->Create( CustomField => $new_entry->Id );
574 print "(Error: $msg)\n" unless $return;
581 if ( @ScripActions ) {
582 print "Creating ScripActions...";
584 for $item (@ScripActions) {
585 my $new_entry = RT::ScripAction->new($CurrentUser);
586 my ($return,$msg) = $new_entry->Create(%$item);
588 print "(Error: $msg)\n";
597 if ( @ScripConditions ) {
598 print "Creating ScripConditions...";
600 for $item (@ScripConditions) {
601 my $new_entry = RT::ScripCondition->new($CurrentUser);
602 my ($return,$msg) = $new_entry->Create(%$item);
604 print "(Error: $msg)\n";
614 print "Creating templates...";
616 for $item (@Templates) {
617 my $new_entry = new RT::Template($CurrentUser);
618 my $return = $new_entry->Create(%$item);
624 print "Creating scrips...";
626 for $item (@Scrips) {
627 my $new_entry = new RT::Scrip($CurrentUser);
628 my ( $return, $msg ) = $new_entry->Create(%$item);
633 print "(Error: $msg)\n";
639 print "Creating predefined searches...";
640 my $sys = RT::System->new($CurrentUser);
642 for $item (@Attributes) {
643 my $obj = delete $item->{Object}; # XXX: make this something loadable
645 my ( $return, $msg ) = $obj->AddAttribute (%$item);
650 print "(Error: $msg)\n";
655 $RT::Handle->Disconnect() unless $RT::DatabaseType eq 'SQLite';
656 print "Done setting up database content.\n";
659 =head2 ACLEquivGroupId
661 Given a userid, return that user's acl equivalence group
665 sub ACLEquivGroupId {
666 my $username = shift;
667 my $user = RT::User->new($RT::SystemUser);
668 $user->Load($username);
669 my $equiv_group = RT::Group->new($RT::SystemUser);
670 $equiv_group->LoadACLEquivalenceGroup($user);
671 return ( $equiv_group->Id );
678 $0: Set up RT's database
680 --action init Initialize the database
681 drop Drop the database.
682 This will ERASE ALL YOUR DATA
684 Insert RT's core system objects
685 insert Insert data into RT's database.
686 By default, will use RT's installation data.
687 To use a local or supplementary datafile, specify it
688 using the '--datafile' option below.
690 acl Initialize only the database ACLs
691 To use a local or supplementary datafile, specify it
692 using the '--datadir' option below.
694 schema Initialize only the database schema
695 To use a local or supplementary datafile, specify it
696 using the '--datadir' option below.
698 --datafile /path/to/datafile
699 --datadir /path/to/ Used to specify a path to find the local
700 database schema and acls to be installed.
704 --dba-password dba's password
705 --prompt-for-dba-password Ask for the database administrator's password interactively