2 # {{{ BEGIN BPS TAGGED BLOCK
6 # This software is Copyright (c) 1996-2004 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., 675 Mass Ave, Cambridge, MA 02139, USA.
29 # CONTRIBUTION SUBMISSION POLICY:
31 # (The following paragraph is not intended to limit the rights granted
32 # to you to modify and distribute this software under the terms of
33 # the GNU General Public License and is only of importance to you if
34 # you choose to contribute your changes and enhancements to the
35 # community by submitting them to Best Practical Solutions, LLC.)
37 # By intentionally submitting any modifications, corrections or
38 # derivatives to this work, or any other work intended for use with
39 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
40 # you are the copyright holder for those contributions and you grant
41 # Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
42 # royalty-free, perpetual, license to use, copy, create derivative
43 # works based on those contributions, and sublicense and distribute
44 # those contributions and any derivatives thereof.
46 # }}} END BPS TAGGED BLOCK
48 use vars qw($PROMPT $VERSION $Handle $Nobody $SystemUser $item);
50 qw(@Groups @Users @ACL @Queues @ScripActions @ScripConditions @Templates @CustomFields @Scrips);
52 use lib "@RT_LIB_PATH@";
54 #This drags in RT's config.pm
55 # We do it in a begin block because RT::Handle needs to know the type to do its
67 use RT::ScripCondition;
79 'prompt-for-dba-password', 'force', 'debug',
80 'action=s', 'dba=s', 'dba-password=s', 'datafile=s',
84 $| = 1; #unbuffer that output.
87 my $Handle = RT::Handle->new($RT::DatabaseType);
91 if ( $args{'prompt-for-dba-password'} ) {
92 $args{'dba-password'} = get_dba_password();
93 chomp( $args{'dba-password'} );
96 unless ( $args{'action'} ) {
100 if ( $args{'action'} eq 'init' ) {
101 $dbh = DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} )
102 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
103 print "Now creating a database for RT.\n";
104 if ($RT::DatabaseType ne 'Oracle' ||
105 $args{'dba'} ne $RT::DatabaseUser) {
108 print "...skipped as ".$args{'dba'} ." is not " . $RT::DatabaseUser . " or we're working with Oracle.\n";
111 if ($RT::DatabaseType eq "mysql") {
112 # Check which version we're running
113 my ($version) = $dbh->selectrow_hashref("show variables like 'version'")->{Value} =~ /^(\d\.\d+)/;
114 print "*** Warning: RT is unsupported on MySQL versions before 4.0.x\n" if $version < 4;
116 # MySQL must have InnoDB support
117 my $innodb = $dbh->selectrow_hashref("show variables like 'have_innodb'")->{Value};
118 if ($innodb eq "NO") {
119 die "RT requires that MySQL be compiled with InnoDB table support.\n".
120 "See http://dev.mysql.com/doc/mysql/en/InnoDB.html\n";
121 } elsif ($innodb eq "DISABLED") {
122 die "RT requires that MySQL InnoDB table support be enabled.\n".
124 ? "Add 'innodb_data_file_path=ibdata1:10M:autoextend' to the [mysqld] section of my.cnf\n"
125 : "Remove the 'skip-innodb' line from your my.cnf file, restart MySQL, and try again.\n");
129 # SQLite can't deal with the disconnect/reconnect
130 unless ($RT::DatabaseType eq 'SQLite') {
133 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} ) || die $DBI::errstr;
135 print "Now populating database schema.\n";
137 print "Now inserting database ACLs\n";
138 insert_acl() unless ($RT::DatabaseType eq 'Oracle');
139 print "Now inserting RT core system objects\n";
140 insert_initial_data();
141 print "Now inserting RT data\n";
142 insert_data( $RT::EtcPath . "/initialdata" );
144 elsif ( $args{'action'} eq 'drop' ) {
146 DBI->connect( get_system_dsn(), $args{'dba'}, $args{'dba-password'} ) )
149 warn "Database doesn't appear to exist. Aborting database drop.";
154 elsif ( $args{'action'} eq 'insert' ) {
155 insert_data( $args{'datafile'} || ($args{'datadir'}."/content"));
157 elsif ($args{'action'} eq 'acl') {
158 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
159 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
160 insert_acl($args{'datadir'});
162 elsif ($args{'action'} eq 'schema') {
163 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
164 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
165 insert_schema($args{'datadir'});
169 print STDERR '$0 called with an invalid --action parameter';
173 # {{{ sub insert_schema
175 my $base_path = (shift || $RT::EtcPath);
177 print "Creating database schema.\n";
179 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
180 no warnings 'unopened';
182 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
183 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
186 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
190 if ( $line =~ /;(\s*)$/ ) {
191 $statement =~ s/;(\s*)$//g;
192 push @schema, $statement;
197 local $SIG{__WARN__} = sub {};
198 my $is_local = 0; # local/etc/schema needs to be nonfatal.
199 foreach my $statement (@schema) {
200 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
201 print STDERR "SQL: $statement\n" if defined $args{'debug'};
202 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
203 unless ( $sth->execute or $is_local ) {
204 die "Problem with statement:\n $statement\n" . $sth->errstr;
210 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
212 print "schema sucessfully inserted\n";
220 return if ( $RT::DatabaseType eq 'SQLite' );
221 if ( $RT::DatabaseType eq 'Oracle' ) {
224 To delete the tables and sequences of the RT Oracle database by running
231 unless ( $args{'force'} ) {
234 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
235 WARNING: This will erase all data in $RT::DatabaseName.
238 exit unless _yesno();
242 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
244 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
251 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
252 if ( $RT::DatabaseType eq 'SQLite' ) {
255 elsif ( $RT::DatabaseType eq 'Pg' ) {
256 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
258 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
261 elsif ($RT::DatabaseType eq 'Oracle') {
264 elsif ( $RT::DatabaseType eq 'Informix' ) {
265 $ENV{DB_LOCALE} = 'en_us.utf8';
266 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
269 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
275 sub get_dba_password {
277 "In order to create a new database and grant RT access to that database,\n";
278 print "this script needs to connect to your "
281 . $RT::DatabaseHost . " as "
282 . $args{'dba'} . ".\n";
284 "Please specify that user's database password below. If the user has no database\n";
285 print "password, just press return.\n\n";
288 my $password = ReadLine(0);
295 print "Proceed [y/N]:";
296 my $x = scalar(<STDIN>);
305 my $base_path = (shift || $RT::EtcPath);
307 if ( $RT::DatabaseType =~ /^oracle$/i ) {
308 do $base_path . "/acl.Oracle"
309 || die "Couldn't find ACLS for Oracle\n" . $@;
311 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
312 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
314 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
315 do $base_path . "/acl.mysql"
316 || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
318 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
319 do $base_path . "/acl.Sybase"
320 || die "Couldn't find ACLS for Sybase in " . $RT::EtcPath . "\n" . $@;
322 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
323 do $base_path . "/acl.Informix"
324 || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
326 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
330 die "Unknown RT database type";
334 foreach my $statement (@acl) {
335 print STDERR $statement if $args{'debug'};
336 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
337 unless ( $sth->execute ) {
338 die "Problem with statement:\n $statement\n" . $sth->errstr;
345 =head2 get_system_dsn
347 Returns a dsn suitable for database creates and drops
348 and user creates and drops
354 my $dsn = $Handle->DSN;
356 #with mysql, you want to connect sans database to funge things
357 if ( $RT::DatabaseType eq 'mysql' ) {
358 $dsn =~ s/dbname=$RT::DatabaseName//;
360 # with postgres, you want to connect to database1
362 elsif ( $RT::DatabaseType eq 'Pg' ) {
363 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
365 elsif ( $RT::DatabaseType eq 'Informix' ) {
366 # with Informix, you want to connect sans database:
367 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
372 sub insert_initial_data {
376 #connect to the db, for actual RT work
378 $RT::Handle = RT::Handle->new();
379 $RT::Handle->Connect();
381 #Put together a current user object so we can create a User object
382 my $CurrentUser = new RT::CurrentUser();
384 print "Checking for existing system user...";
385 my $test_user = RT::User->new($CurrentUser);
386 $test_user->Load('RT_System');
387 if ( $test_user->id ) {
388 print "found!\n\nYou appear to have a functional RT database.\n"
389 . "Exiting, so as not to clobber your existing data.\n";
394 print "not found. This appears to be a new installation.\n";
397 print "Creating system user...";
398 my $RT_System = new RT::User($CurrentUser);
400 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
402 RealName => 'The RT System itself',
404 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
412 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
416 # load some sort of data into the database
419 my $datafile = shift;
421 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
424 my $CurrentUser = RT::CurrentUser->new();
425 $CurrentUser->LoadByName('RT_System');
427 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
429 print "Creating Superuser ACL...";
431 my $superuser_ace = RT::ACE->new($CurrentUser);
432 $superuser_ace->_BootstrapCreate(
433 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
434 PrincipalType => 'Group',
435 RightName => 'SuperUser',
436 ObjectType => 'RT::System',
441 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
442 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
445 || die "Couldn't find initial data for import\n" . $@;
448 print "Creating groups...";
449 foreach $item (@Groups) {
450 my $new_entry = RT::Group->new($CurrentUser);
451 my ( $return, $msg ) = $new_entry->_Create(%$item);
452 print "(Error: $msg)" unless ($return);
458 print "Creating users...";
459 foreach $item (@Users) {
460 my $new_entry = new RT::User($CurrentUser);
461 my ( $return, $msg ) = $new_entry->Create(%$item);
462 print "(Error: $msg)" unless ($return);
468 print "Creating queues...";
469 for $item (@Queues) {
470 my $new_entry = new RT::Queue($CurrentUser);
471 my ( $return, $msg ) = $new_entry->Create(%$item);
472 print "(Error: $msg)" unless ($return);
478 print "Creating ACL...";
479 for my $item (@ACL) {
481 my ($princ, $object);
483 # Global rights or Queue rights?
484 if ($item->{'Queue'}) {
485 $object = RT::Queue->new($CurrentUser);
486 $object->Load( $item->{'Queue'} );
488 $object = $RT::System;
491 # Group rights or user rights?
492 if ($item->{'GroupDomain'}) {
493 $princ = RT::Group->new($CurrentUser);
494 if ($item->{'GroupDomain'} eq 'UserDefined') {
495 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
496 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
497 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
498 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
499 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
500 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
502 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
503 Queue => $object->id);
505 $princ->Load( $item->{'GroupId'} );
508 $princ = RT::User->new($CurrentUser);
509 $princ->Load( $item->{'UserId'} );
513 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
514 Right => $item->{'Right'},
529 print "Creating custom fields...";
530 for $item (@CustomFields) {
531 my $new_entry = new RT::CustomField($CurrentUser);
532 my $values = $item->{'Values'};
533 delete $item->{'Values'};
534 my $q = $item->{'Queue'};
535 my $q_obj = RT::Queue->new($CurrentUser);
538 $item->{'Queue'} = $q_obj->Id;
541 $item->{'Queue'} = 0;
544 print "(Error: Could not find queue " . $q . ")\n"
545 unless ( $q_obj->Id );
548 my ( $return, $msg ) = $new_entry->Create(%$item);
550 foreach my $value ( @{$values} ) {
551 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
552 print "(Error: $emsg)\n" unless ($eval);
555 print "(Error: $msg)\n" unless ($return);
563 print "Creating ScripActions...";
565 for $item (@ScripActions) {
566 my $new_entry = RT::ScripAction->new($CurrentUser);
567 my $return = $new_entry->Create(%$item);
574 if (@ScripConditions) {
575 print "Creating ScripConditions...";
577 for $item (@ScripConditions) {
578 my $new_entry = RT::ScripCondition->new($CurrentUser);
579 my $return = $new_entry->Create(%$item);
587 print "Creating templates...";
589 for $item (@Templates) {
590 my $new_entry = new RT::Template($CurrentUser);
591 my $return = $new_entry->Create(%$item);
597 print "Creating scrips...";
599 for $item (@Scrips) {
600 my $new_entry = new RT::Scrip($CurrentUser);
601 my ( $return, $msg ) = $new_entry->Create(%$item);
606 print "(Error: $msg)\n";
611 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
615 =head2 ACLEquivGroupId
617 Given a userid, return that user's acl equivalence group
621 sub ACLEquivGroupId {
622 my $username = shift;
623 my $user = RT::User->new($RT::SystemUser);
624 $user->Load($username);
625 my $equiv_group = RT::Group->new($RT::SystemUser);
626 $equiv_group->LoadACLEquivalenceGroup($user);
627 return ( $equiv_group->Id );
634 $0: Set up RT's database
636 --action init Initialize the database
637 drop Drop the database.
638 This will ERASE ALL YOUR DATA
639 insert Insert data into RT's database.
640 By default, will use RT's installation data.
641 To use a local or supplementary datafile, specify it
642 using the '--datafile' option below.
644 acl Initialize only the database ACLs
645 To use a local or supplementary datafile, specify it
646 using the '--datadir' option below.
648 schema Initialize only the database schema
649 To use a local or supplementary datafile, specify it
650 using the '--datadir' option below.
652 --datafile /path/to/datafile
653 --datadir /path/to/ Used to specify a path to find the local
654 database schema and acls to be installed.
658 --dba-password dba's password
659 --prompt-for-dba-password Ask for the database administrator's password interactively