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_initial' ) {
155 insert_initial_data();
157 elsif ( $args{'action'} eq 'insert' ) {
158 insert_data( $args{'datafile'} || ($args{'datadir'}."/content"));
160 elsif ($args{'action'} eq 'acl') {
161 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
162 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
163 insert_acl($args{'datadir'});
165 elsif ($args{'action'} eq 'schema') {
166 $dbh = DBI->connect( $Handle->DSN, $args{'dba'}, $args{'dba-password'} )
167 || die "Failed to connect to " . get_system_dsn() . " as $args{'dba'}: $DBI::errstr";
168 insert_schema($args{'datadir'});
172 print STDERR '$0 called with an invalid --action parameter';
176 # {{{ sub insert_schema
178 my $base_path = (shift || $RT::EtcPath);
180 print "Creating database schema.\n";
182 if ( -f $base_path . "/schema." . $RT::DatabaseType ) {
183 no warnings 'unopened';
185 open( SCHEMA, "<" . $base_path . "/schema." . $RT::DatabaseType );
186 open( SCHEMA_LOCAL, "<" . $RT::LocalEtcPath . "/schema." . $RT::DatabaseType );
189 foreach my $line (<SCHEMA>, ($_ = ';;'), <SCHEMA_LOCAL>) {
193 if ( $line =~ /;(\s*)$/ ) {
194 $statement =~ s/;(\s*)$//g;
195 push @schema, $statement;
200 local $SIG{__WARN__} = sub {};
201 my $is_local = 0; # local/etc/schema needs to be nonfatal.
202 foreach my $statement (@schema) {
203 if ($statement =~ /^\s*;$/) { $is_local = 1; next; }
204 print STDERR "SQL: $statement\n" if defined $args{'debug'};
205 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
206 unless ( $sth->execute or $is_local ) {
207 die "Problem with statement:\n $statement\n" . $sth->errstr;
213 die "Couldn't find schema file for " . $RT::DatabaseType . "\n";
215 print "schema sucessfully inserted\n";
223 return if ( $RT::DatabaseType eq 'SQLite' );
224 if ( $RT::DatabaseType eq 'Oracle' ) {
227 To delete the tables and sequences of the RT Oracle database by running
234 unless ( $args{'force'} ) {
237 About to drop $RT::DatabaseType database $RT::DatabaseName on $RT::DatabaseHost.
238 WARNING: This will erase all data in $RT::DatabaseName.
241 exit unless _yesno();
245 print "Dropping $RT::DatabaseType database $RT::DatabaseName.\n";
247 $dbh->do("Drop DATABASE $RT::DatabaseName") or warn $DBI::errstr;
254 print "Creating $RT::DatabaseType database $RT::DatabaseName.\n";
255 if ( $RT::DatabaseType eq 'SQLite' ) {
258 elsif ( $RT::DatabaseType eq 'Pg' ) {
259 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH ENCODING='UNICODE'");
261 $dbh->do("CREATE DATABASE $RT::DatabaseName") || die $DBI::errstr;
264 elsif ($RT::DatabaseType eq 'Oracle') {
267 elsif ( $RT::DatabaseType eq 'Informix' ) {
268 $ENV{DB_LOCALE} = 'en_us.utf8';
269 $dbh->do("CREATE DATABASE $RT::DatabaseName WITH BUFFERED LOG");
272 $dbh->do("CREATE DATABASE $RT::DatabaseName") or die $DBI::errstr;
278 sub get_dba_password {
280 "In order to create a new database and grant RT access to that database,\n";
281 print "this script needs to connect to your "
284 . $RT::DatabaseHost . " as "
285 . $args{'dba'} . ".\n";
287 "Please specify that user's database password below. If the user has no database\n";
288 print "password, just press return.\n\n";
291 my $password = ReadLine(0);
298 print "Proceed [y/N]:";
299 my $x = scalar(<STDIN>);
308 my $base_path = (shift || $RT::EtcPath);
310 if ( $RT::DatabaseType =~ /^oracle$/i ) {
311 do $base_path . "/acl.Oracle"
312 || die "Couldn't find ACLS for Oracle\n" . $@;
314 elsif ( $RT::DatabaseType =~ /^pg$/i ) {
315 do $base_path . "/acl.Pg" || die "Couldn't find ACLS for Pg\n" . $@;
317 elsif ( $RT::DatabaseType =~ /^mysql$/i ) {
318 do $base_path . "/acl.mysql"
319 || die "Couldn't find ACLS for mysql in " . $RT::EtcPath . "\n" . $@;
321 elsif ( $RT::DatabaseType =~ /^Sybase$/i ) {
322 do $base_path . "/acl.Sybase"
323 || die "Couldn't find ACLS for Sybase in " . $RT::EtcPath . "\n" . $@;
325 elsif ( $RT::DatabaseType =~ /^informix$/i ) {
326 do $base_path . "/acl.Informix"
327 || die "Couldn't find ACLS for Informix in " . $RT::EtcPath . "\n" . $@;
329 elsif ( $RT::DatabaseType =~ /^SQLite$/i ) {
333 die "Unknown RT database type";
337 foreach my $statement (@acl) {
338 print STDERR $statement if $args{'debug'};
339 my $sth = $dbh->prepare($statement) or die $dbh->errstr;
340 unless ( $sth->execute ) {
341 die "Problem with statement:\n $statement\n" . $sth->errstr;
348 =head2 get_system_dsn
350 Returns a dsn suitable for database creates and drops
351 and user creates and drops
357 my $dsn = $Handle->DSN;
359 #with mysql, you want to connect sans database to funge things
360 if ( $RT::DatabaseType eq 'mysql' ) {
361 $dsn =~ s/dbname=$RT::DatabaseName//;
363 # with postgres, you want to connect to database1
365 elsif ( $RT::DatabaseType eq 'Pg' ) {
366 $dsn =~ s/dbname=$RT::DatabaseName/dbname=template1/;
368 elsif ( $RT::DatabaseType eq 'Informix' ) {
369 # with Informix, you want to connect sans database:
370 $dsn =~ s/Informix:$RT::DatabaseName/Informix:/;
375 sub insert_initial_data {
379 #connect to the db, for actual RT work
381 $RT::Handle = RT::Handle->new();
382 $RT::Handle->Connect();
384 #Put together a current user object so we can create a User object
385 my $CurrentUser = new RT::CurrentUser();
387 print "Checking for existing system user...";
388 my $test_user = RT::User->new($CurrentUser);
389 $test_user->Load('RT_System');
390 if ( $test_user->id ) {
391 print "found!\n\nYou appear to have a functional RT database.\n"
392 . "Exiting, so as not to clobber your existing data.\n";
397 print "not found. This appears to be a new installation.\n";
400 print "Creating system user...";
401 my $RT_System = new RT::User($CurrentUser);
403 my ( $val, $msg ) = $RT_System->_BootstrapCreate(
405 RealName => 'The RT System itself',
407 'Do not delete or modify this user. It is integral to RT\'s internal database structures',
415 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
419 # load some sort of data into the database
422 my $datafile = shift;
424 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
427 my $CurrentUser = RT::CurrentUser->new();
428 $CurrentUser->LoadByName('RT_System');
430 if ( $datafile eq $RT::EtcPath . "/initialdata" ) {
432 print "Creating Superuser ACL...";
434 my $superuser_ace = RT::ACE->new($CurrentUser);
435 $superuser_ace->_BootstrapCreate(
436 PrincipalId => ACLEquivGroupId( $CurrentUser->Id ),
437 PrincipalType => 'Group',
438 RightName => 'SuperUser',
439 ObjectType => 'RT::System',
444 # Slurp in stuff to insert from the datafile. Possible things to go in here:-
445 # @groups, @users, @acl, @queues, @ScripActions, @ScripConditions, @templates
448 || die "Couldn't find initial data for import\n" . $@;
451 print "Creating groups...";
452 foreach $item (@Groups) {
453 my $new_entry = RT::Group->new($CurrentUser);
454 my ( $return, $msg ) = $new_entry->_Create(%$item);
455 print "(Error: $msg)" unless ($return);
461 print "Creating users...";
462 foreach $item (@Users) {
463 my $new_entry = new RT::User($CurrentUser);
464 my ( $return, $msg ) = $new_entry->Create(%$item);
465 print "(Error: $msg)" unless ($return);
471 print "Creating queues...";
472 for $item (@Queues) {
473 my $new_entry = new RT::Queue($CurrentUser);
474 my ( $return, $msg ) = $new_entry->Create(%$item);
475 print "(Error: $msg)" unless ($return);
481 print "Creating ACL...";
482 for my $item (@ACL) {
484 my ($princ, $object);
486 # Global rights or Queue rights?
487 if ($item->{'Queue'}) {
488 $object = RT::Queue->new($CurrentUser);
489 $object->Load( $item->{'Queue'} );
491 $object = $RT::System;
494 # Group rights or user rights?
495 if ($item->{'GroupDomain'}) {
496 $princ = RT::Group->new($CurrentUser);
497 if ($item->{'GroupDomain'} eq 'UserDefined') {
498 $princ->LoadUserDefinedGroup( $item->{'GroupId'} );
499 } elsif ($item->{'GroupDomain'} eq 'SystemInternal') {
500 $princ->LoadSystemInternalGroup( $item->{'GroupType'} );
501 } elsif ($item->{'GroupDomain'} eq 'RT::System-Role') {
502 $princ->LoadSystemRoleGroup( $item->{'GroupType'} );
503 } elsif ($item->{'GroupDomain'} eq 'RT::Queue-Role' &&
505 $princ->LoadQueueRoleGroup( Type => $item->{'GroupType'},
506 Queue => $object->id);
508 $princ->Load( $item->{'GroupId'} );
511 $princ = RT::User->new($CurrentUser);
512 $princ->Load( $item->{'UserId'} );
516 my ( $return, $msg ) = $princ->PrincipalObj->GrantRight(
517 Right => $item->{'Right'},
532 print "Creating custom fields...";
533 for $item (@CustomFields) {
534 my $new_entry = new RT::CustomField($CurrentUser);
535 my $values = $item->{'Values'};
536 delete $item->{'Values'};
537 my $q = $item->{'Queue'};
538 my $q_obj = RT::Queue->new($CurrentUser);
541 $item->{'Queue'} = $q_obj->Id;
544 $item->{'Queue'} = 0;
547 print "(Error: Could not find queue " . $q . ")\n"
548 unless ( $q_obj->Id );
551 my ( $return, $msg ) = $new_entry->Create(%$item);
553 foreach my $value ( @{$values} ) {
554 my ( $eval, $emsg ) = $new_entry->AddValue(%$value);
555 print "(Error: $emsg)\n" unless ($eval);
558 print "(Error: $msg)\n" unless ($return);
566 print "Creating ScripActions...";
568 for $item (@ScripActions) {
569 my $new_entry = RT::ScripAction->new($CurrentUser);
570 my $return = $new_entry->Create(%$item);
577 if (@ScripConditions) {
578 print "Creating ScripConditions...";
580 for $item (@ScripConditions) {
581 my $new_entry = RT::ScripCondition->new($CurrentUser);
582 my $return = $new_entry->Create(%$item);
590 print "Creating templates...";
592 for $item (@Templates) {
593 my $new_entry = new RT::Template($CurrentUser);
594 my $return = $new_entry->Create(%$item);
600 print "Creating scrips...";
602 for $item (@Scrips) {
603 my $new_entry = new RT::Scrip($CurrentUser);
604 my ( $return, $msg ) = $new_entry->Create(%$item);
609 print "(Error: $msg)\n";
614 $RT::Handle->Disconnect() unless ($RT::DatabaseType eq 'SQLite');
618 =head2 ACLEquivGroupId
620 Given a userid, return that user's acl equivalence group
624 sub ACLEquivGroupId {
625 my $username = shift;
626 my $user = RT::User->new($RT::SystemUser);
627 $user->Load($username);
628 my $equiv_group = RT::Group->new($RT::SystemUser);
629 $equiv_group->LoadACLEquivalenceGroup($user);
630 return ( $equiv_group->Id );
637 $0: Set up RT's database
639 --action init Initialize the database
640 drop Drop the database.
641 This will ERASE ALL YOUR DATA
643 Insert RT's core system objects
644 insert Insert data into RT's database.
645 By default, will use RT's installation data.
646 To use a local or supplementary datafile, specify it
647 using the '--datafile' option below.
649 acl Initialize only the database ACLs
650 To use a local or supplementary datafile, specify it
651 using the '--datadir' option below.
653 schema Initialize only the database schema
654 To use a local or supplementary datafile, specify it
655 using the '--datadir' option below.
657 --datafile /path/to/datafile
658 --datadir /path/to/ Used to specify a path to find the local
659 database schema and acls to be installed.
663 --dba-password dba's password
664 --prompt-for-dba-password Ask for the database administrator's password interactively