#!/usr/bin/perl # # This code was forked from the LiveJournal project owned and operated # by Live Journal, Inc. The code has been modified and expanded by # Dreamwidth Studios, LLC. These files were originally licensed under # the terms of the license supplied by Live Journal, Inc, which can # currently be found at: # # http://code.livejournal.org/trac/livejournal/browser/trunk/LICENSE-LiveJournal.txt # # In accordance with the original license, this code and all its # modifications are provided under the GNU General Public License. # A copy of that license can be found in the LICENSE file included as # part of this distribution. use strict; use lib "$ENV{LJHOME}/extlib/lib/perl5"; use Getopt::Long; use version; use File::Temp; my $debs_only = 0; my ( $only_check, $no_check, $opt_nolocal, $opt_install ); my %dochecks; # these are the ones we'll actually do my @checks = ( # put these in the order they should be checked in "timezone", "modules", "env", "database", "secrets", ); foreach my $check (@checks) { $dochecks{$check} = 1; } sub usage { die "Usage: checkconfig.pl checkconfig.pl --needed-debs checkconfig.pl --only= | --no= checkconfig.pl --install Checks are: " . join( ', ', @checks ); } usage() unless GetOptions( 'needed-debs' => \$debs_only, 'only=s' => \$only_check, 'no=s' => \$no_check, 'nolocal' => \$opt_nolocal, 'install' => \$opt_install, ); if ($debs_only) { $dochecks{database} = 0; $dochecks{timezone} = 0; $dochecks{secrets} = 0; } usage() if $only_check && $no_check; %dochecks = ( $only_check => 1 ) if $only_check; $dochecks{$no_check} = 0 if $no_check; my @errors; my $err = sub { return unless @_; die "\nProblem:\n" . join( '', map { " * $_\n" } @_ ); }; my %modules; open MODULES, "<$ENV{LJHOME}/doc/dependencies-cpanm" or die; foreach my $module_line () { my ( $module, $ver, $opt ) = ( $1, $2, $3 ) if $module_line =~ /^(.+?)(?:@(.+))?(\?)?$/; if ($module) { $modules{$module} = { ver => $ver, opt => $opt eq '?' ? 1 : 0 }; } } close MODULES; sub check_modules { print "[Checking for Perl Modules....]\n" unless $debs_only; my ( @debs, @mods ); foreach my $mod ( sort keys %modules ) { my $rv = eval "use $mod ();"; if ($@) { my $dt = $modules{$mod}; unless ($debs_only) { if ( $dt->{opt} ) { print STDERR "Missing optional module $mod: $dt->{'opt'}\n"; } else { push @errors, "Missing perl module: $mod"; } } push @mods, $dt->{ver} ? "$mod\@$dt->{ver}" : $mod; next; } my $ver_want = $modules{$mod}{ver}; my $ver_got = $mod->VERSION; # handle version strings with multiple decimal points # assumes there will never be a version part prepended # only appended if ( $ver_want && $ver_got ) { if ( version->parse($ver_want) > version->parse($ver_got) ) { if ( $modules{$mod}->{opt} ) { print STDERR "Out of date optional module: $mod (need $ver_want, $ver_got installed)\n"; } else { push @errors, "Out of date module: $mod (need $ver_want, $ver_got installed)"; } } } } if ( @debs && -e '/etc/debian_version' ) { if ($debs_only) { print join( ' ', @debs ); } else { print STDERR "\n# apt-get install ", join( ' ', @debs ), "\n\n"; } } if (@mods) { print "\n# curl -L http://cpanmin.us | sudo perl - --self-upgrade\n"; print "# cpanm -L \$LJHOME/extlib/ " . join( ' ', @mods ) . "\n\n"; if ($opt_install) { system( "cpanm", "-n", "-L", "$ENV{LJHOME}/extlib/", @mods ); } } $err->(@errors); } sub check_env { print "[Checking LJ Environment...]\n" unless $debs_only; $err->("\$LJHOME environment variable not set.") unless $ENV{'LJHOME'}; $err->("\$LJHOME directory doesn't exist ($ENV{'LJHOME'})") unless -d $ENV{'LJHOME'}; # before config.pl is called, we want to call the site-local checkconfig, # otherwise config.pl might load config-local.pl, which could load # new modules to implement site-specific hooks. my $local_config = "$ENV{'LJHOME'}/bin/checkconfig-local.pl"; $local_config .= ' --needed-debs' if $debs_only; if ( !$opt_nolocal && -e $local_config ) { my $good = eval { require $local_config; }; exit 1 unless $good; } eval { require "$ENV{'LJHOME'}/cgi-bin/ljlib.pl"; }; $err->("Failed to load ljlib.pl: $@") if $@; # this is copied from similar logic in 00-compile.t my $tempdir = File::Temp::tempdir( CLEANUP => 1 ); my $slurp = sub { my $file = $_[0]; open my $fh, '<', $file or die $!; local $/ = undef; return <$fh>; }; my $test_syntax = sub { my $file = $_[0]; my $out = "$tempdir/out"; my $err = "$tempdir/err"; system qq($^X -I"$ENV{LJHOME}/extlib/lib/perl5" -c $file > $out 2>$err); my $err_data = $slurp->($err); return 1 if $err_data && $err_data eq "$file syntax OK\n"; }; my @configs_to_test = qw( etc/config-private.pl etc/config-local.pl etc/config.pl ); push @configs_to_test, qw( t/config-test-private.pl t/config-test.pl ) if $LJ::IS_DEV_SERVER; foreach my $testfile (@configs_to_test) { if ( my $config = LJ::resolve_file($testfile) ) { my $fn = $config; $fn =~ s=^\Q$ENV{LJHOME}\E/==; my $parse_config = $test_syntax->($config); $err->("Failed to parse $fn -- check syntax") unless $parse_config; } else { $err->("No file found for $testfile"); } } } sub check_database { require "$ENV{'LJHOME'}/cgi-bin/ljlib.pl"; my $dbh = LJ::get_dbh("master"); unless ($dbh) { $err->("Couldn't get master database handle."); } foreach my $c (@LJ::CLUSTERS) { my $dbc = LJ::get_cluster_master($c); next if $dbc; $err->("Couldn't get db handle for cluster \#$c"); } } foreach my $check (@checks) { next unless $dochecks{$check}; my $cn = "check_" . $check; no strict 'refs'; &$cn; } unless ($debs_only) { print "All good.\n"; print "NOTE: checkconfig.pl doesn't check everything yet\n"; } sub check_timezone { print "[Checking Timezone...]\n"; my $rv = eval "use DateTime::TimeZone;"; if ($@) { $err->("Missing required perl module: DateTime::TimeZone"); } my $timezone = DateTime::TimeZone->new( name => 'local' ); $err->("Timezone must be UTC.") unless $timezone->is_utc; } sub check_secrets { print "[Checking Secrets...]\n"; foreach my $secret ( keys %LJ::Secrets::secret ) { my $def = $LJ::Secrets::secret{$secret}; my $req_len = exists $def->{len} || exists $def->{min_len} || exists $def->{max_len}; my $rec_len = exists $def->{rec_len} || exists $def->{rec_min_len} || exists $def->{rec_max_len}; my $req_min = $def->{len} || $def->{min_len} || 0; my $req_max = $def->{len} || $def->{max_len} || 0; my $rec_min = $def->{rec_len} || $def->{rec_min_len} || 0; my $rec_max = $def->{rec_len} || $def->{rec_max_len} || 0; my $val = $LJ::SECRETS{$secret} || ''; my $len = length($val); if ( !defined( $LJ::SECRETS{$secret} ) || !$LJ::SECRETS{$secret} ) { if ( $def->{required} ) { $err->("Missing requred secret '$secret': $def->{desc}"); } else { print STDERR "Missing optional secret '$secret': $def->{desc}\n"; } } elsif ( $req_len && ( $len < $req_min || $len > $req_max ) ) { if ( $req_min == $req_max ) { $err->("Secret '$secret' not of required length: is $len, must be $req_min"); } else { $err->( "Secret '$secret' not of required length: is $len, must be between $req_min and $req_max" ); } } elsif ( $rec_len && ( $len < $rec_min || $len > $rec_max ) ) { if ( $rec_min == $rec_max ) { print STDERR "Secret '$secret' not of recommended length: is $len, should be $rec_min\n"; } else { print STDERR "Secret '$secret' not of recommended length: is $len, should be between $rec_min and $rec_max\n"; } } } }