zservrotation.pl
Post date: May 21, 2010 5:57:45 PM
A perl utility to create Zdaemon's zserv configuration files. Useful only on Unix/Linux servers.
You need to have Config::IniFiles installed. Just do it with the help of CPAN (or PPM if you have ActiveState Perl).
#!perl
#
# zservrotation.pl by shinobi.cl@gmail.com
#
use strict;
use Data::Dumper;
use Config::IniFiles;
use Switch;
print '--- Starting zservrotation.pl version 0.7 ---'.$/;
print 'Local time : ';
system ("date");
print 'UTC time : ';
system ("date -u");
my $Config_File = shift;
$Config_File = 'zservrotation.conf' unless defined $Config_File;
die 'ERROR: Configuration file "'.$Config_File.'" not found'.$/ if (!(-e $Config_File));
my $conf = new Config::IniFiles -file => $Config_File;
my %Servers = ();
my %Configs = ();
#--- First, we fill the server data
my @ServerList = $conf->GroupMembers("SERVER");
foreach my $Server (@ServerList) {
my ($dummy, $Server_Name) = split /\s+/, $Server;
my @Parameters = $conf->Parameters($Server);
foreach my $Parameter (@Parameters) {
$Servers{$Server_Name}{$Parameter} = unquote(trim($conf->val($Server, $Parameter)));
}
}
my @ConfigList = $conf->GroupMembers("DETAILS");
my @General_Parameters = $conf->Parameters("GENERAL");
foreach my $Detail (@ConfigList) {
my ($dummy, $Detail_Id) = split /\s+/, $Detail;
my @Parameters = $conf->Parameters($Detail);
#For each detailed config, we add all the "general" settings.
foreach my $General_Parameter (@General_Parameters) {
next if ($General_Parameter eq '!currentiteration'); # special parameter
my $Value;
if ($General_Parameter eq '!verbatim') {
my @Content = $conf->val("GENERAL", '!verbatim');
$Value = \@Content;
} else {
$Value = unquote(trim($conf->val("GENERAL", $General_Parameter)));
}
$Configs{$Detail_Id}{$General_Parameter} = $Value;
}
foreach my $Parameter (@Parameters) {
# Only !extrawads, hostname and motd are added to the general settings.
# The others are overrided by the 'detailed' property.
my $Value;
if ($Parameter eq '!verbatim') {
my @Content = $conf->val($Detail, '!verbatim');
$Value = \@Content;
} else {
$Value = unquote(trim($conf->val($Detail, $Parameter)));
}
switch ($Parameter) {
case '!verbatim' {
push @{$Configs{$Detail_Id}{'!verbatim'}}, @{$Value};
}
case /^!extrawads$|^hostname$|^motd$/ {
$Configs{$Detail_Id}{$Parameter} .= ' '.$Value;
}
else { # Property is overrided
$Configs{$Detail_Id}{$Parameter} = $Value;
}
}
}
}
my @Rotation_States = split /,/, $conf->val("GENERAL", "!currentiteration");
foreach (@Rotation_States) {
my ($Server, $Turn) = split /:/;
$Turn = trim($Turn);
$Turn = 1 if ($Turn !~ /^\d+$/); # Turn defaults to 1 if not a Natural
$Servers{trim($Server)}{'#Iteration'} = $Turn;
}
my @Rotation_Parameters = $conf->Parameters("ROTATION");
foreach my $Server (@Rotation_Parameters) {
# Depending on the current rotation, we assign a DETAIL config to a server.
my @Rotation_List = split /,/, $conf->val("ROTATION", $Server);
$Servers{$Server}{'#Config'} = trim($Rotation_List[$Servers{$Server}{'#Iteration'}-1]);
my $Next = $Servers{$Server}{'#Iteration'}+1;
$Next = 1 if $Next > scalar @Rotation_List;
$Servers{$Server}{'#Next'} = $Next;
}
#--- validate minimum parameters ----------------------------------------------#
# TODO: do it..
#--- zserv.cfg file generation ------------------------------------------------#
my @New_Iteration = ();
my @Server_Paths = ();
foreach my $Server (keys %Servers) {
if (!(-e $Servers{$Server}{'!path'})) {
die 'ERROR: Path "'.$Servers{$Server}{'!path'}.'" for server "'.$Server.'"" not found.'
}
my @Z = (); # zserv.cfg contents
# Some stuff that should be there just because.
push @Z, '/////////////////////////////////////////////////';
push @Z, '// ZDAEMON ROTATING SERVER '.$Server;
push @Z, '/////////////////////////////////////////////////';
push @Z, 'set cfg_activated "1"';
my %Properties = %{$Configs{ $Servers{$Server}{'#Config'} }};
foreach my $Property (keys %Properties) {
my $Value = $Properties{$Property};
# Some properties are not part of the zserv file. Those start with '!'
next if ($Property =~ /^!currentiteration$|^!iwad$|^cfg_activated$/);
switch ($Property) {
case '!rotationexecutable' {
if ($Value eq 'zserv') {
warn 'WARN: You have set !rotationexecutable=zserv. Be aware that all !rotationexecutables are killed at the end of each rotation.'.$/;
last;
}
}
case /^!basepath$|^!waddir$/ {
if (!(-e $Value)) {
die 'ERROR: Path "'.$Value.'" not found.'
}
}
case /^!extrawads$|^!mainwad$/ {
my @Wads = split /\s+/, $Value;
foreach (@Wads) {
my $Wad_File = $Properties{'!waddir'}.'/'.$_;
$Wad_File =~ s/\/+/\//g;
if (!(wadfile_exists($Wad_File))) {
die 'ERROR: Wadfile "'.$Wad_File.'" not found.'.$/;
}
}
}
case '!verbatim' {
my @Content = @{$Value};
push @Z, $_ foreach (@Content);
}
else {
push @Z, 'set '.$Property.' "'.$Value.'"';
}
}
}
my $Launcher =
$Servers{$Server}{'!path'}.'/'.$Properties{'!rotationexecutable'}.
' -waddir '.$conf->val("GENERAL", '!waddir').
' -iwad '.$Properties{'!iwad'}.
' -file '.$Properties{'!mainwad'}.' '.$Properties{'!extrawads'}.
' -port '.$Servers{$Server}{'!port'}.
' >> '.$conf->val("GENERAL", '!basepath').'/'.$Server.'.log &';
$Launcher =~ s/\/+/\//g;
# Launcher is written to runserver.sh
# @Z is written to zserv.cfg
open S_LAUNCHER, '>'.$Servers{$Server}{'!path'}.'/runserver.sh' or die 'Can not create launcher for server '.$Server.' : '.$!.$/;
open ZSERVCFG, '>'.$Servers{$Server}{'!path'}.'/zserv.cfg' or die 'Can not create zserv.cfg for server '.$Server.' : '.$!.$/;
print S_LAUNCHER $Launcher.$/;
close S_LAUNCHER;
my $CurServ_Message = 'INFO: Server '.$Server.' is going to run '.$Properties{'!mainwad'}.$/;
system ("echo ".$CurServ_Message);
# print 'INFO: Server launcher "'.$Servers{$Server}{'!path'}.'/runserver.sh" created.'.$/;
print ZSERVCFG $_.$/ foreach (@Z);
close ZSERVCFG;
print 'INFO: Configuration "'.$Servers{$Server}{'!path'}.'/zserv.cfg" created.'.$/;
# We need to update the rotation info.
push @New_Iteration, $Server.':'.$Servers{$Server}{'#Next'};
push @Server_Paths, $Servers{$Server}{'!path'};
}
# We need to update the !currentiteration value
$conf->setval("GENERAL", '!currentiteration', (join ',', @New_Iteration) );
$conf->RewriteConfig;
print 'INFO: '.$Config_File.' updated.'.$/;
# Creating a launcher for all rotating servers
my $Launcher_File = $conf->val("GENERAL", '!basepath').'/start_zservrota.sh';
$Launcher_File =~ s/\/+/\//g;
open LAUNCHER, '>'.$Launcher_File or die 'Can not create launcher : '.$!.$/;
print LAUNCHER 'killall -15 '.$conf->val("GENERAL", '!rotationexecutable').$/;
print LAUNCHER 'sleep 5'.$/;
my $Executable = $conf->val("GENERAL", '!rotationexecutable');
foreach (@Server_Paths) {
print LAUNCHER 'cd '.$_.$/;
print LAUNCHER 'test -e '.$Executable.' || cp zserv "'.$Executable.'"'.$/;
print LAUNCHER 'sh runserver.sh'.$/;
}
close LAUNCHER;
print 'INFO: Launcher "'.$Launcher_File.'" created.'.$/;
print '--- All done. ---'.$/;
#--- utility functions --------------------------------------------------------#
sub trim {
my $String = shift;
$String =~ s/^\s+//;
$String =~ s/\s+$//;
return $String;
}
sub unquote {
my $String = shift;
$String =~ s/^'/"/;
$String =~ s/'$/"/;
$String =~ s/^"|"$//g;
return $String;
}
sub wadfile_exists {
my $Wad_File = shift;
$Wad_File = trim($Wad_File);
$Wad_File .= '.wad' unless ($Wad_File =~ /\.wad$/i);
my @Parts = split /\//, $Wad_File;
my $Filename = pop @Parts;
my $Path = join '/', @Parts;
# We need only the filename without the last extension (should be 'wad')
my @Filename_Parts = split /\./, $Filename;
pop @Filename_Parts;
my $Wad_Name = join '.', @Filename_Parts;
my $Exists = 0;
# Since Unix is case sensitive, we need to check for all extensions
foreach ('WAD', 'wad', 'Wad', 'wAd', 'waD', 'WAd', 'wAD', 'WaD') {
if (-e $Path.'/'.$Wad_Name.'.'.$_) {
$Exists = 1;
last;
};
}
return ($Exists > 0) ? 1 : 0;
}