2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
7 # <sales@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/licenses/old-licenses/gpl-2.0.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 }}}
52 use vars qw($Nobody $SystemUser $item);
54 # fix lib paths, some may be relative
57 my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
61 unless ( File::Spec->file_name_is_absolute($lib) ) {
63 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
64 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
69 $bin_path = $FindBin::Bin;
72 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
79 #This drags in RT's config.pm
80 # We do it in a begin block because RT::Handle needs to know the type to do its
91 $| = 1; # unbuffer all output.
98 'dba=s', 'dba-password=s', 'prompt-for-dba-password',
99 'datafile=s', 'datadir=s'
102 unless ( $args{'action'} ) {
107 # check and setup @actions
108 my @actions = grep $_, split /,/, $args{'action'};
109 if ( @actions > 1 && $args{'datafile'} ) {
110 print STDERR "You can not use --datafile option with multiple actions.\n";
113 foreach ( @actions ) {
114 unless ( /^(?:init|create|drop|schema|acl|coredata|insert|upgrade)$/ ) {
115 print STDERR "$0 called with an invalid --action parameter.\n";
118 if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) {
119 print STDERR "You can not mix init, drop or upgrade action with any action.\n";
124 # convert init to multiple actions
126 if ( $actions[0] eq 'init' ) {
127 @actions = qw(create schema acl coredata insert);
131 # set options from environment
132 foreach my $key(qw(Type Host Name User Password)) {
133 next unless exists $ENV{ 'RT_DB_'. uc $key };
134 print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n";
135 RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key });
138 my $db_type = RT->Config->Get('DatabaseType') || '';
139 my $db_host = RT->Config->Get('DatabaseHost') || '';
140 my $db_name = RT->Config->Get('DatabaseName') || '';
141 my $db_user = RT->Config->Get('DatabaseUser') || '';
142 my $db_pass = RT->Config->Get('DatabasePassword') || '';
144 # load it here to get error immidiatly if DB type is not supported
147 if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) {
148 $db_name = File::Spec->catfile($RT::VarPath, $db_name);
149 RT->Config->Set( DatabaseName => $db_name );
152 my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || $db_user || '';
153 my $dba_pass = exists($args{'dba-password'})
154 ? $args{'dba-password'}
155 : $ENV{'RT_DBA_PASSWORD'};
157 if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) {
158 $dba_pass = get_dba_password();
159 chomp $dba_pass if defined($dba_pass);
162 print "Working with:\n"
163 ."Type:\t$db_type\nHost:\t$db_host\nName:\t$db_name\n"
164 ."User:\t$db_user\nDBA:\t$dba_user\n";
166 foreach my $action ( @actions ) {
168 my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args );
169 error($action, $msg) unless $status;
170 print $msg ."\n" if $msg;
176 my $dbh = get_system_dbh();
177 my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
178 return ($status, $msg) unless $status;
180 print "Now creating a $db_type database $db_name for RT.\n";
181 return RT::Handle->CreateDatabase( $dbh );
187 print "Dropping $db_type database $db_name.\n";
188 unless ( $args{'force'} ) {
191 About to drop $db_type database $db_name on $db_host.
192 WARNING: This will erase all data in $db_name.
195 exit(-2) unless _yesno();
198 my $dbh = get_system_dbh();
199 return RT::Handle->DropDatabase( $dbh );
204 my $dbh = get_admin_dbh();
205 my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
206 return ($status, $msg) unless $status;
208 print "Now populating database schema.\n";
209 return RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} );
214 my $dbh = get_admin_dbh();
215 my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'pre' );
216 return ($status, $msg) unless $status;
218 print "Now inserting database ACLs\n";
219 return RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} );
222 sub action_coredata {
224 $RT::Handle = new RT::Handle;
225 $RT::Handle->dbh( undef );
226 RT::ConnectToDatabase();
228 my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' );
229 return ($status, $msg) unless $status;
231 print "Now inserting RT core system objects\n";
232 return $RT::Handle->InsertInitialData;
237 $RT::Handle = new RT::Handle;
239 my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'pre' );
240 return ($status, $msg) unless $status;
242 print "Now inserting data\n";
243 my $file = $args{'datafile'};
244 $file = $RT::EtcPath . "/initialdata" if $init && !$file;
245 $file ||= $args{'datadir'}."/content";
246 return $RT::Handle->InsertData( $file );
251 my $base_dir = $args{'datadir'} || "./etc/upgrade";
252 return (0, "Couldn't read dir '$base_dir' with upgrade data")
253 unless -d $base_dir || -r _;
255 my $upgrading_from = undef;
257 if ( defined $upgrading_from ) {
258 print "Doesn't match #.#.#: ";
260 print "Enter RT version you're upgrading from: ";
262 $upgrading_from = scalar <STDIN>;
263 chomp $upgrading_from;
264 $upgrading_from =~ s/\s+//g;
265 } while $upgrading_from !~ /^\d+\.\d+\.\d+$/;
267 my $upgrading_to = $RT::VERSION;
268 return (0, "The current version $upgrading_to is lower than $upgrading_from")
269 if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0;
271 return (1, "The version $upgrading_to you're upgrading to is up to date")
272 if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0;
274 my @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
276 return (1, "No DB changes between $upgrading_from and $upgrading_to")
279 print "\nGoing to apply following upgrades:\n";
280 print map "* $_\n", @versions;
283 my $custom_upgrading_to = undef;
285 if ( defined $custom_upgrading_to ) {
286 print "Doesn't match #.#.#: ";
288 print "\nEnter RT version if you want to stop upgrade at some point,\n";
289 print " or leave it blank if you want apply above upgrades: ";
291 $custom_upgrading_to = scalar <STDIN>;
292 chomp $custom_upgrading_to;
293 $custom_upgrading_to =~ s/\s+//g;
294 last unless $custom_upgrading_to;
295 } while $custom_upgrading_to !~ /^\d+\.\d+\.\d+$/;
297 if ( $custom_upgrading_to ) {
299 0, "The version you entered ($custom_upgrading_to) is lower than\n"
300 ."version you're upgrading from ($upgrading_from)"
301 ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0;
303 return (1, "The version you're upgrading to is up to date")
304 if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0;
306 if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) {
307 print "Version you entered is greater than installed ($RT::VERSION).\n";
308 _yesno() or exit(-2);
310 # ok, checked everything no let's refresh list
311 $upgrading_to = $custom_upgrading_to;
312 @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
314 return (1, "No DB changes between $upgrading_from and $upgrading_to")
317 print "\nGoing to apply following upgrades:\n";
318 print map "* $_\n", @versions;
322 print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n";
323 _yesno() or exit(-2) unless $args{'force'};
325 foreach my $v ( @versions ) {
326 print "Processing $v\n";
327 my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef);
328 if ( -e "$base_dir/$v/schema.$db_type" ) {
329 action_schema( %tmp );
331 if ( -e "$base_dir/$v/acl.$db_type" ) {
334 if ( -e "$base_dir/$v/content" ) {
335 action_insert( %tmp );
341 sub get_versions_from_to {
342 my ($base_dir, $from, $to) = @_;
344 opendir( my $dh, $base_dir ) or die "couldn't open dir: $!";
345 my @versions = grep -d "$base_dir/$_" && /\d+\.\d+\.\d+/, readdir $dh;
349 grep RT::Handle::cmp_version($_, $to) <= 0,
350 grep RT::Handle::cmp_version($_, $from) > 0,
351 sort RT::Handle::cmp_version @versions;
355 my ($action, $msg) = @_;
356 print STDERR "Couldn't finish '$action' step.\n\n";
357 print STDERR "ERROR: $msg\n\n";
361 sub get_dba_password {
362 print "In order to create or update your RT database,"
363 . " this script needs to connect to your "
364 . " $db_type instance on $db_host as $dba_user\n";
365 print "Please specify that user's database password below. If the user has no database\n";
366 print "password, just press return.\n\n";
369 my $password = ReadLine(0);
375 =head2 get_system_dbh
377 Returns L<DBI> database handle connected to B<system> with DBA credentials.
379 See also L<RT::Handle/SystemDSN>.
384 return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass );
388 return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass );
391 =head2 get_rt_dbh [USER, PASSWORD]
393 Returns L<DBI> database handle connected to RT database,
394 you may specify credentials(USER and PASSWORD) to connect
395 with. By default connects with credentials from RT config.
400 return _get_dbh( RT::Handle->DSN, $db_user, $db_pass );
404 my ($dsn, $user, $pass) = @_;
405 my $dbh = DBI->connect(
407 { RaiseError => 0, PrintError => 0 },
410 my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr;
411 if ( $args{'debug'} ) {
412 require Carp; Carp::confess( $msg );
414 print STDERR $msg; exit -1;
421 print "Proceed [y/N]:";
422 my $x = scalar(<STDIN>);
430 $0: Set up RT's database
432 --action init Initialize the database. This is combination of
433 multiple actions listed below. Create DB, schema,
434 setup acl, insert core data and initial data.
436 upgrade Apply all needed schema/acl/content updates (will ask
437 for version to upgrade from)
439 create Create the database.
441 drop Drop the database.
442 This will ERASE ALL YOUR DATA
444 schema Initialize only the database schema
445 To use a local or supplementary datafile, specify it
446 using the '--datadir' option below.
448 acl Initialize only the database ACLs
449 To use a local or supplementary datafile, specify it
450 using the '--datadir' option below.
452 coredata Insert data into RT's database. This data is required
453 for normal functioning of any RT instance.
455 insert Insert data into RT's database.
456 By default, will use RT's installation data.
457 To use a local or supplementary datafile, specify it
458 using the '--datafile' option below.
460 Several actions can be combined using comma separated list.
462 --datafile /path/to/datafile
463 --datadir /path/to/ Used to specify a path to find the local
464 database schema and acls to be installed.
468 --dba-password dba's password
469 --prompt-for-dba-password Ask for the database administrator's password interactively