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