]> git.uio.no Git - usit-rt.git/blob - sbin/rt-setup-database
b1b9d9a204da94f36a015ff694d8c020fef2a774
[usit-rt.git] / sbin / rt-setup-database
1 #!/usr/bin/perl
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
7 #                                          <sales@bestpractical.com>
8 #
9 # (Except where explicitly superseded by other copyright notices)
10 #
11 #
12 # LICENSE:
13 #
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
17 # from www.gnu.org.
18 #
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.
23 #
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.
29 #
30 #
31 # CONTRIBUTION SUBMISSION POLICY:
32 #
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.)
38 #
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.
47 #
48 # END BPS TAGGED BLOCK }}}
49 use strict;
50 use warnings;
51
52 use vars qw($Nobody $SystemUser $item);
53
54 # fix lib paths, some may be relative
55 BEGIN { # BEGIN RT CMD BOILERPLATE
56     require File::Spec;
57     require Cwd;
58     my @libs = ("lib", "local/lib");
59     my $bin_path;
60
61     for my $lib (@libs) {
62         unless ( File::Spec->file_name_is_absolute($lib) ) {
63             $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1];
64             $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
65         }
66         unshift @INC, $lib;
67     }
68
69 }
70
71 use Term::ReadKey;
72 use Getopt::Long;
73 use Data::GUID;
74
75 $| = 1; # unbuffer all output.
76
77 my %args = (
78     package => 'RT',
79 );
80 GetOptions(
81     \%args,
82     'action=s',
83     'force', 'debug',
84     'dba=s', 'dba-password=s', 'prompt-for-dba-password', 'package=s',
85     'datafile=s', 'datadir=s', 'skip-create', 'root-password-file=s',
86     'package=s', 'ext-version=s',
87     'help|h',
88 );
89
90 no warnings 'once';
91 if ( $args{help} || ! $args{'action'} ) {
92     require Pod::Usage;
93     Pod::Usage::pod2usage({ verbose => 2 });
94     exit;
95 }
96
97 require RT;
98 RT->LoadConfig();
99 RT->InitClasses();
100
101 # Force warnings to be output to STDERR if we're not already logging
102 # them at a higher level
103 RT->Config->Set( LogToSTDERR => 'warning')
104     unless ( RT->Config->Get( 'LogToSTDERR' )
105              && RT->Config->Get( 'LogToSTDERR' ) =~ /^(debug|info|notice)$/ );
106 RT::InitLogging();
107
108 # get customized root password
109 my $root_password;
110 if ( $args{'root-password-file'} ) {
111     open( my $fh, '<', $args{'root-password-file'} )
112       or die "Couldn't open 'args{'root-password-file'}' for reading: $!";
113     $root_password = <$fh>;
114     chomp $root_password;
115     my $min_length = RT->Config->Get('MinimumPasswordLength');
116     if ($min_length) {
117         die
118 "password needs to be at least $min_length long, please check file '$args{'root-password-file'}'"
119           if length $root_password < $min_length;
120     }
121     close $fh;
122 }
123
124
125 # check and setup @actions
126 my @actions = grep $_, split /,/, $args{'action'};
127 if ( @actions > 1 && $args{'datafile'} ) {
128     print STDERR "You can not use --datafile option with multiple actions.\n";
129     exit(-1);
130 }
131 foreach ( @actions ) {
132     unless ( /^(?:init|create|drop|schema|acl|indexes|coredata|insert|upgrade)$/ ) {
133         print STDERR "$0 called with an invalid --action parameter.\n";
134         exit(-1);
135     }
136     if ( /^(?:init|drop|upgrade)$/ && @actions > 1 ) {
137         print STDERR "You can not mix init, drop or upgrade action with any action.\n";
138         exit(-1);
139     }
140 }
141
142 # convert init to multiple actions
143 my $init = 0;
144 if ( $actions[0] eq 'init' ) {
145     if ($args{'skip-create'}) {
146         @actions = qw(schema coredata insert);
147     } else {
148         @actions = qw(create schema acl coredata insert);
149     }
150     $init = 1;
151 }
152
153 # set options from environment
154 foreach my $key(qw(Type Host Name User Password)) {
155     next unless exists $ENV{ 'RT_DB_'. uc $key };
156     print "Using Database$key from RT_DB_". uc($key) ." environment variable.\n";
157     RT->Config->Set( "Database$key", $ENV{ 'RT_DB_'. uc $key });
158 }
159
160 my $db_type = RT->Config->Get('DatabaseType') || '';
161 my $db_host = RT->Config->Get('DatabaseHost') || '';
162 my $db_port = RT->Config->Get('DatabasePort') || '';
163 my $db_name = RT->Config->Get('DatabaseName') || '';
164 my $db_user = RT->Config->Get('DatabaseUser') || '';
165 my $db_pass = RT->Config->Get('DatabasePassword') || '';
166
167 # load it here to get error immidiatly if DB type is not supported
168 require RT::Handle;
169
170 if ( $db_type eq 'SQLite' && !File::Spec->file_name_is_absolute($db_name) ) {
171     $db_name = File::Spec->catfile($RT::VarPath, $db_name);
172     RT->Config->Set( DatabaseName => $db_name );
173 }
174
175 my $dba_user = $args{'dba'} || $ENV{'RT_DBA_USER'} || RT->Config->Get('DatabaseAdmin') || '';
176 my $dba_pass = $args{'dba-password'} || $ENV{'RT_DBA_PASSWORD'};
177
178 if ($args{'skip-create'}) {
179     $dba_user = $db_user;
180     $dba_pass = $db_pass;
181 } else {
182     if ( !$args{force} && ( !defined $dba_pass || $args{'prompt-for-dba-password'} ) ) {
183         $dba_pass = get_dba_password();
184         chomp $dba_pass if defined($dba_pass);
185     }
186 }
187
188 my $version_word_regex = join '|', RT::Handle->version_words;
189 my $version_dir = qr/^\d+\.\d+\.\d+(?:$version_word_regex)?\d*$/;
190
191 print "Working with:\n"
192     ."Type:\t$db_type\nHost:\t$db_host\nPort:\t$db_port\nName:\t$db_name\n"
193     ."User:\t$db_user\nDBA:\t$dba_user" . ($args{'skip-create'} ? ' (No DBA)' : '') . "\n";
194
195 my $package = $args{'package'} || 'RT';
196 my $ext_version = $args{'ext-version'};
197 my $full_id = Data::GUID->new->as_string;
198
199 my $log_actions = 0;
200 if ($args{'package'} ne 'RT') {
201     RT->ConnectToDatabase();
202     RT->InitSystemObjects();
203     $log_actions = 1;
204 }
205
206 foreach my $action ( @actions ) {
207     no strict 'refs';
208     my ($status, $msg) = *{ 'action_'. $action }{'CODE'}->( %args );
209     error($action, $msg) unless $status;
210     print $msg .".\n" if $msg;
211     print "Done.\n";
212 }
213
214 sub action_create {
215     my %args = @_;
216     my $dbh = get_system_dbh();
217     my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'create' );
218     return ($status, $msg) unless $status;
219
220     print "Now creating a $db_type database $db_name for RT.\n";
221     return RT::Handle->CreateDatabase( $dbh );
222 }
223
224 sub action_drop {
225     my %args = @_;
226
227     print "Dropping $db_type database $db_name.\n";
228     unless ( $args{'force'} ) {
229         print <<END;
230
231 About to drop $db_type database $db_name on $db_host (port '$db_port').
232 WARNING: This will erase all data in $db_name.
233
234 END
235         exit(-2) unless _yesno();
236     }
237
238     my $dbh = get_system_dbh();
239     return RT::Handle->DropDatabase( $dbh );
240 }
241
242 sub action_schema {
243     my %args = @_;
244     my $dbh = get_admin_dbh();
245     my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'schema' );
246     return ($status, $msg) unless $status;
247
248     my $individual_id = Data::GUID->new->as_string();
249     my %upgrade_data = (
250         action   => 'schema',
251         filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''),
252         stage    => 'before',
253         full_id  => $full_id,
254         individual_id => $individual_id,
255     );
256     $upgrade_data{'ext_version'} = $ext_version if $ext_version;
257     RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions;
258
259     print "Now populating database schema.\n";
260     my @ret = RT::Handle->InsertSchema( $dbh, $args{'datafile'} || $args{'datadir'} );
261
262     %upgrade_data = (
263         stage         => 'after',
264         individual_id => $individual_id,
265         return_value  => [ @ret ],
266     );
267     RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions;
268
269     return @ret;
270 }
271
272 sub action_acl {
273     my %args = @_;
274     my $dbh = get_admin_dbh();
275     my ($status, $msg) = RT::Handle->CheckCompatibility( $dbh, 'acl' );
276     return ($status, $msg) unless $status;
277
278     my $individual_id = Data::GUID->new->as_string();
279     my %upgrade_data = (
280         action   => 'acl',
281         filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''),
282         stage    => 'before',
283         full_id  => $full_id,
284         individual_id => $individual_id,
285     );
286     $upgrade_data{'ext_version'} = $ext_version if $ext_version;
287     RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions;
288
289     print "Now inserting database ACLs.\n";
290     my @ret = RT::Handle->InsertACL( $dbh, $args{'datafile'} || $args{'datadir'} );
291
292     %upgrade_data = (
293         stage         => 'after',
294         individual_id => $individual_id,
295         return_value  => [ @ret ],
296     );
297     RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions;
298
299     return @ret;
300 }
301
302 sub action_indexes {
303     my %args = @_;
304     RT->ConnectToDatabase;
305     my $individual_id = Data::GUID->new->as_string();
306     my %upgrade_data = (
307         action   => 'indexes',
308         filename => Cwd::abs_path($args{'datafile'} || $args{'datadir'} || ''),
309         stage    => 'before',
310         full_id  => $full_id,
311         individual_id => $individual_id,
312     );
313     $upgrade_data{'ext_version'} = $ext_version if $ext_version;
314     RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions;
315
316     my $dbh = get_admin_dbh();
317     $RT::Handle = RT::Handle->new;
318     $RT::Handle->dbh( $dbh );
319     RT::InitLogging();
320
321     print "Now inserting database indexes.\n";
322     my @ret = RT::Handle->InsertIndexes( $dbh, $args{'datafile'} || $args{'datadir'} );
323
324     $RT::Handle = RT::Handle->new;
325     $RT::Handle->dbh( undef );
326     RT->ConnectToDatabase;
327     %upgrade_data = (
328         stage         => 'after',
329         individual_id => $individual_id,
330         return_value  => [ @ret ],
331     );
332     RT->System->AddUpgradeHistory($package => \%upgrade_data) if $log_actions;
333
334     return @ret;
335 }
336
337 sub action_coredata {
338     my %args = @_;
339     $RT::Handle = RT::Handle->new;
340     $RT::Handle->dbh( undef );
341     RT::ConnectToDatabase();
342     my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'coredata' );
343     return ($status, $msg) unless $status;
344
345     print "Now inserting RT core system objects.\n";
346     return $RT::Handle->InsertInitialData;
347 }
348
349 sub action_insert {
350     my %args = @_;
351     $RT::Handle = RT::Handle->new;
352     RT::Init();
353     $log_actions = 1;
354
355     my ($status, $msg) = RT::Handle->CheckCompatibility( $RT::Handle->dbh, 'insert' );
356     return ($status, $msg) unless $status;
357
358     print "Now inserting data.\n";
359     my $file = $args{'datafile'};
360     $file = $RT::EtcPath . "/initialdata" if $init && !$file;
361     $file ||= $args{'datadir'}."/content";
362
363     my $individual_id = Data::GUID->new->as_string();
364     my %upgrade_data = (
365         action   => 'insert',
366         filename => Cwd::abs_path($file),
367         stage    => 'before',
368         full_id  => $full_id,
369         individual_id => $individual_id
370     );
371     $upgrade_data{'ext_version'} = $ext_version if $ext_version;
372
373     open my $handle, '<', $file or warn "Unable to open $file: $!";
374     $upgrade_data{content} = do {local $/; <$handle>} if $handle;
375
376     RT->System->AddUpgradeHistory($package => \%upgrade_data);
377
378     my @ret;
379
380     my $upgrade = sub { @ret = $RT::Handle->InsertData( $file, $root_password ) };
381
382     for my $file (@{$args{backcompat} || []}) {
383         my $lines = do {local $/; local @ARGV = ($file); <>};
384         my $sub = eval "sub {\n# line 1 $file\n$lines\n}";
385         unless ($sub) {
386             warn "Failed to load backcompat $file: $@";
387             next;
388         }
389         my $current = $upgrade;
390         $upgrade = sub { $sub->($current) };
391     }
392
393     $upgrade->();
394
395     # XXX Reconnecting to insert the history entry
396     # until we can sort out removing
397     # the disconnect at the end of InsertData.
398     RT->ConnectToDatabase();
399
400     %upgrade_data = (
401         stage         => 'after',
402         individual_id => $individual_id,
403         return_value  => [ @ret ],
404     );
405
406     RT->System->AddUpgradeHistory($package => \%upgrade_data);
407
408     my $db_type = RT->Config->Get('DatabaseType');
409     $RT::Handle->Disconnect() unless $db_type eq 'SQLite';
410
411     return @ret;
412 }
413
414 sub action_upgrade {
415     my %args = @_;
416     my $base_dir = $args{'datadir'} || "./etc/upgrade";
417     return (0, "Couldn't read dir '$base_dir' with upgrade data")
418         unless -d $base_dir || -r _;
419
420     my $upgrading_from = undef;
421     do {
422         if ( defined $upgrading_from ) {
423             print "Doesn't match #.#.#: ";
424         } else {
425             print "Enter $args{package} version you're upgrading from: ";
426         }
427         $upgrading_from = scalar <STDIN>;
428         chomp $upgrading_from;
429         $upgrading_from =~ s/\s+//g;
430     } while $upgrading_from !~ /$version_dir/;
431
432     my $upgrading_to = $RT::VERSION;
433     return (0, "The current version $upgrading_to is lower than $upgrading_from")
434         if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) > 0;
435
436     return (1, "The version $upgrading_to you're upgrading to is up to date")
437         if RT::Handle::cmp_version( $upgrading_from, $upgrading_to ) == 0;
438
439     my @versions = get_versions_from_to($base_dir, $upgrading_from, undef);
440     return (1, "No DB changes since $upgrading_from")
441         unless @versions;
442
443     if (RT::Handle::cmp_version($versions[-1], $upgrading_to) > 0) {
444         print "\n***** There are upgrades for $versions[-1], which is later than $upgrading_to,\n";
445         print   "***** which you are nominally upgrading to.  Upgrading to $versions[-1] instead.\n";
446         $upgrading_to = $versions[-1];
447     }
448
449     print "\nGoing to apply following upgrades:\n";
450     print map "* $_\n", @versions;
451
452     {
453         my $custom_upgrading_to = undef;
454         do {
455             if ( defined $custom_upgrading_to ) {
456                 print "Doesn't match #.#.#: ";
457             } else {
458                 print "\nEnter $args{package} version if you want to stop upgrade at some point,\n";
459                 print "  or leave it blank if you want apply above upgrades: ";
460             }
461             $custom_upgrading_to = scalar <STDIN>;
462             chomp $custom_upgrading_to;
463             $custom_upgrading_to =~ s/\s+//g;
464             last unless $custom_upgrading_to;
465         } while $custom_upgrading_to !~ /$version_dir/;
466
467         if ( $custom_upgrading_to ) {
468             return (
469                 0, "The version you entered ($custom_upgrading_to) is lower than\n"
470                 ."version you're upgrading from ($upgrading_from)"
471             ) if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) > 0;
472
473             return (1, "The version you're upgrading to is up to date")
474                 if RT::Handle::cmp_version( $upgrading_from, $custom_upgrading_to ) == 0;
475
476             if ( RT::Handle::cmp_version( $RT::VERSION, $custom_upgrading_to ) < 0 ) {
477                 print "Version you entered is greater than installed ($RT::VERSION).\n";
478                 _yesno() or exit(-2);
479             }
480             # ok, checked everything no let's refresh list
481             $upgrading_to = $custom_upgrading_to;
482             @versions = get_versions_from_to($base_dir, $upgrading_from, $upgrading_to);
483
484             return (1, "No DB changes between $upgrading_from and $upgrading_to")
485                 unless @versions;
486
487             print "\nGoing to apply following upgrades:\n";
488             print map "* $_\n", @versions;
489         }
490     }
491
492     print "\nIT'S VERY IMPORTANT TO BACK UP BEFORE THIS STEP\n\n";
493     _yesno() or exit(-2) unless $args{'force'};
494
495     RT->ConnectToDatabase();
496     RT->InitSystemObjects();
497     $log_actions = 1;
498
499     RT->System->AddUpgradeHistory($package => {
500         type      => 'full upgrade',
501         action    => 'upgrade',
502         stage     => 'before',
503         from      => $upgrading_from,
504         to        => $upgrading_to,
505         versions  => [@versions],
506         full_id => $full_id,
507         individual_id => $full_id
508     });
509
510     # Ensure that the Attributes column is big enough to hold the
511     # upgrade steps we're going to add; this step exists in 4.0.6 for
512     # mysql, but that may be too late.  Run it as soon as possible.
513     if (RT->Config->Get('DatabaseType') eq 'mysql'
514             and RT::Handle::cmp_version( $upgrading_from, '4.0.6') < 0) {
515         my $dbh = get_admin_dbh();
516         # Before the binary switch in 3.7.87, we want to alter text ->
517         # longtext, not blob -> longblob
518         if (RT::Handle::cmp_version( $upgrading_from, '3.7.87') < 0) {
519             $dbh->do("ALTER TABLE Attributes MODIFY Content LONGTEXT")
520         } else {
521             $dbh->do("ALTER TABLE Attributes MODIFY Content LONGBLOB")
522         }
523     }
524
525     my $previous = $upgrading_from;
526     my ( $ret, $msg );
527     foreach my $n ( 0..$#versions ) {
528         my $v = $versions[$n];
529         my $individual_id = Data::GUID->new->as_string();
530
531         my @back = grep {-e $_} map {"$base_dir/$versions[$_]/backcompat"} $n+1..$#versions;
532         print "Processing $v\n";
533
534         RT->System->AddUpgradeHistory($package => {
535             action => 'upgrade',
536             type   => 'individual upgrade',
537             stage  => 'before',
538             from   => $previous,
539             to     => $v,
540             full_id => $full_id,
541             individual_id => $individual_id,
542         });
543
544         my %tmp = (%args, datadir => "$base_dir/$v", datafile => undef, backcompat => \@back);
545
546         if ( -e "$base_dir/$v/schema.$db_type" ) {
547             ( $ret, $msg ) = action_schema( %tmp );
548             return ( $ret, $msg ) unless $ret;
549         }
550         if ( -e "$base_dir/$v/acl.$db_type" ) {
551             ( $ret, $msg ) = action_acl( %tmp );
552             return ( $ret, $msg ) unless $ret;
553         }
554         if ( -e "$base_dir/$v/indexes" ) {
555             ( $ret, $msg ) = action_indexes( %tmp );
556             return ( $ret, $msg ) unless $ret;
557         }
558         if ( -e "$base_dir/$v/content" ) {
559             ( $ret, $msg ) = action_insert( %tmp );
560             return ( $ret, $msg ) unless $ret;
561         }
562
563         # XXX: Another connect since the insert called
564         # previous to this step will disconnect.
565
566         RT->ConnectToDatabase();
567
568         RT->System->AddUpgradeHistory($package => {
569             stage         => 'after',
570             individual_id => $individual_id,
571         });
572
573         $previous = $v;
574     }
575
576     RT->System->AddUpgradeHistory($package => {
577         stage         => 'after',
578         individual_id => $full_id,
579     });
580
581     return 1;
582 }
583
584 sub get_versions_from_to {
585     my ($base_dir, $from, $to) = @_;
586
587     opendir( my $dh, $base_dir ) or die "couldn't open dir: $!";
588     my @versions = grep -d "$base_dir/$_" && /$version_dir/, readdir $dh;
589     closedir $dh;
590
591     die "\nERROR: No upgrade data found in '$base_dir'!  Perhaps you specified the wrong --datadir?\n"
592         unless @versions;
593
594     return
595         grep defined $to ? RT::Handle::cmp_version($_, $to) <= 0 : 1,
596         grep RT::Handle::cmp_version($_, $from) > 0,
597         sort RT::Handle::cmp_version @versions;
598 }
599
600 sub error {
601     my ($action, $msg) = @_;
602     print STDERR "Couldn't finish '$action' step.\n\n";
603     print STDERR "ERROR: $msg\n\n";
604     exit(-1);
605 }
606
607 sub get_dba_password {
608     print "In order to create or update your RT database,"
609         . " this script needs to connect to your "
610         . " $db_type instance on $db_host (port '$db_port') as $dba_user\n";
611     print "Please specify that user's database password below. If the user has no database\n";
612     print "password, just press return.\n\n";
613     print "Password: ";
614     ReadMode('noecho');
615     my $password = ReadLine(0);
616     ReadMode('normal');
617     print "\n";
618     return ($password);
619 }
620
621 #   get_system_dbh
622 #   Returns L<DBI> database handle connected to B<system> with DBA credentials.
623 #   See also L<RT::Handle/SystemDSN>.
624
625
626 sub get_system_dbh {
627     return _get_dbh( RT::Handle->SystemDSN, $dba_user, $dba_pass );
628 }
629
630 sub get_admin_dbh {
631     return _get_dbh( RT::Handle->DSN, $dba_user, $dba_pass );
632 }
633
634 # get_rt_dbh [USER, PASSWORD]
635
636 # Returns L<DBI> database handle connected to RT database,
637 # you may specify credentials(USER and PASSWORD) to connect
638 # with. By default connects with credentials from RT config.
639
640 sub get_rt_dbh {
641     return _get_dbh( RT::Handle->DSN, $db_user, $db_pass );
642 }
643
644 sub _get_dbh {
645     my ($dsn, $user, $pass) = @_;
646     my $dbh = DBI->connect(
647         $dsn, $user, $pass,
648         { RaiseError => 0, PrintError => 0 },
649     );
650     unless ( $dbh ) {
651         my $msg = "Failed to connect to $dsn as user '$user': ". $DBI::errstr;
652         if ( $args{'debug'} ) {
653             require Carp; Carp::confess( $msg );
654         } else {
655             print STDERR $msg; exit -1;
656         }
657     }
658     return $dbh;
659 }
660
661 sub _yesno {
662     print "Proceed [y/N]:";
663     my $x = scalar(<STDIN>);
664     $x =~ /^y/i;
665 }
666
667 1;
668
669 __END__
670
671 =head1 NAME
672
673 rt-setup-database - Set up RT's database
674
675 =head1 SYNOPSIS
676
677     rt-setup-database --action ... 
678
679 =head1 OPTIONS
680
681 =over
682
683 =item action
684
685 Several actions can be combined using comma separated list.
686
687 =over
688
689 =item init
690
691 Initialize the database. This is combination of multiple actions listed below.
692 Create DB, schema, setup acl, insert core data and initial data.
693
694 =item upgrade
695
696 Apply all needed schema/acl/content updates (will ask for version to upgrade
697 from)
698
699 =item create
700
701 Create the database.
702
703 =item drop
704
705 Drop the database.  This will B<ERASE ALL YOUR DATA>.
706
707 =item schema
708
709 Initialize only the database schema
710
711 To use a local or supplementary datafile, specify it using the '--datadir'
712 option below.
713
714 =item acl
715
716 Initialize only the database ACLs
717
718 To use a local or supplementary datafile, specify it using the '--datadir'
719 option below.
720
721 =item coredata 
722
723 Insert data into RT's database. This data is required for normal functioning of
724 any RT instance.
725
726 =item insert
727
728 Insert data into RT's database.  By default, will use RT's installation data.
729 To use a local or supplementary datafile, specify it using the '--datafile'
730 option below.
731
732 =back
733
734 =item datafile
735
736 file path of the data you want to action on
737
738 e.g. C<--datafile /path/to/datafile>
739
740 =item datadir
741
742 Used to specify a path to find the local database schema and acls to be
743 installed.
744
745 e.g. C<--datadir /path/to/>
746
747 =item dba
748
749 dba's username
750
751 =item dba-password
752
753 dba's password
754
755 =item prompt-for-dba-password
756
757 Ask for the database administrator's password interactively
758
759 =item skip-create
760
761 for 'init': skip creating the database and the user account, so we don't need
762 administrator privileges
763
764 =item root-password-file
765
766 for 'init' and 'insert': rather than using the default administrative password
767 for RT's "root" user, use the password in this file.
768
769 =item package 
770
771 the name of the entity performing a create or upgrade. Used for logging changes
772 in the DB. Defaults to RT, otherwise it should be the fully qualified package name
773 of the extension or plugin making changes to the DB.
774
775 =item ext-version
776
777 current version of extension making a change. Not needed for RT since RT has a
778 more elaborate system to track upgrades across multiple versions.
779
780 =back
781
782 =cut