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