291 lines
8.7 KiB
Perl
291 lines
8.7 KiB
Perl
|
|
#!/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=<check> | --no=<check>
|
||
|
|
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 (<MODULES>) {
|
||
|
|
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";
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|