diff --git a/minecraft/command.cgi b/minecraft/command.cgi new file mode 100644 index 000000000..fde6da9b8 --- /dev/null +++ b/minecraft/command.cgi @@ -0,0 +1,35 @@ +#!/usr/local/bin/perl +# Show a field for running a console command + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%in, %text); +&ReadParse(); + +my @history = &get_command_history(); +$in{'command'} ||= $in{'old'}; +if ($in{'command'}) { + # Run the given command + &send_server_command($in{'command'}); + @history = &unique($in{'command'}, @history); + while(@history > 10) { + pop(@history); + } + &save_command_history(\@history); + } + +&PrintHeader(); +print "
\n"; +print &ui_form_start("command.cgi", "post"); +my @grid = ( "$text{'console_run'}", + &ui_textbox("command", undef, 80)." ". + &ui_submit($text{'console_ok'}) ); +if (@history) { + push(@grid, "$text{'console_old'}", + &ui_select("old", undef, \@history)); + } +print &ui_grid_table(\@grid, 2); +print &ui_form_end(); +print "\n"; + diff --git a/minecraft/console.cgi b/minecraft/console.cgi new file mode 100644 index 000000000..de3db5cdc --- /dev/null +++ b/minecraft/console.cgi @@ -0,0 +1,17 @@ +#!/usr/local/bin/perl +# Show the console, with a command field + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%in, %text, %config); +&ReadParse(); + +&ui_print_header(undef, $text{'console_title'}, ""); + +&is_minecraft_server_running() || &error($text{'console_edown'}); + +print "\n"; +print "\n"; + +&ui_print_footer("", $text{'index_return'}); diff --git a/minecraft/images/conns.gif b/minecraft/images/conns.gif new file mode 100755 index 000000000..b29f9c952 Binary files /dev/null and b/minecraft/images/conns.gif differ diff --git a/minecraft/images/items.png b/minecraft/images/items.png new file mode 100644 index 000000000..f217971df Binary files /dev/null and b/minecraft/images/items.png differ diff --git a/minecraft/images/smallicon.gif b/minecraft/images/smallicon.gif new file mode 100644 index 000000000..bc3412abf Binary files /dev/null and b/minecraft/images/smallicon.gif differ diff --git a/minecraft/index.cgi b/minecraft/index.cgi index 9a47c7adf..7f83f15f1 100644 --- a/minecraft/index.cgi +++ b/minecraft/index.cgi @@ -14,11 +14,14 @@ if ($err) { } my @links = ( "edit_conf.cgi", "edit_users.cgi", - "view_logs.cgi", "edit_manual.gif" ); + "view_logs.cgi", "list_conns.cgi", + "console.cgi", "edit_manual.gif" ); my @titles = ( $text{'conf_title'}, $text{'users_title'}, - $text{'logs_title'}, $text{'manual_title'} ); + $text{'logs_title'}, $text{'conns_title'}, + $text{'console_title'}, $text{'manual_title'} ); my @icons = ( "images/conf.gif", "images/users.gif", - "images/logs.gif", "images/manual.gif" ); + "images/logs.gif", "images/conns.gif", + "images/console.gif", "images/manual.gif" ); &icons_table(\@links, \@titles, \@icons); print &ui_hr(); diff --git a/minecraft/item_chooser.cgi b/minecraft/item_chooser.cgi new file mode 100644 index 000000000..6870953ff --- /dev/null +++ b/minecraft/item_chooser.cgi @@ -0,0 +1,52 @@ +#!/usr/local/bin/perl +# Output a list for choosing a Minecraft item + + +use strict; +use warnings; +our $trust_unknown_referers = 1; +require './minecraft-lib.pl'; +our (%text, %in); +&ReadParse(undef, undef, 2); + +&popup_header($text{'chooser_title'}); + +print "\n"; + +# Show all items +print &ui_form_start("item_chooser.cgi"); +print "$text{'chooser_search'} ", + &ui_textbox("search", $in{'search'}, 20)," ", + &ui_submit($text{'chooser_ok'}); +print &ui_form_end(),"\n"; + } + +&popup_footer(); diff --git a/minecraft/lang/en b/minecraft/lang/en index 2052bbe59..497cf6a06 100644 --- a/minecraft/lang/en +++ b/minecraft/lang/en @@ -18,9 +18,59 @@ conf_title=Server Configuration users_title=Users and Operators -logs_title=View Log File +conns_title=Connected Players +conns_desc=The following players are currently connected to your server. Click on a player name to perform actions on it. +conns_edown=Connected players cannot be managed unless the server is running +conns_none=No players are currently connected to the server. +conns_disc=Disconnect Selected +conns_enter=Manage player named: +conns_ok=OK +conns_return=player list + +logs_title=Search Log File logs_lines=Show last logs_matching=lines matching logs_ok=Search +console_title=Server Console +console_run=Enter command +console_ok=OK +console_old=or select previous +console_edown=The Minecraft server console cannot be used unless the server is running + +conn_title=Manage Player +conn_header=Player details and actions +conn_name=Player name +conn_state=Current state +conn_yes=Connected +conn_no=Disconnected +conn_lastin=Logged in from +conn_lastin2=Last logged in from +conn_lastout=Logged out at +conn_at=$1 at $2 +conn_msg=Send direct message +conn_pos=Position at login +conn_msgb=Send +conn_kill=Terminate player +conn_killb=Kill Now +conn_ename=Missing or invalid player name +conn_give=Grant item with ID +conn_count=count +conn_giveb=Give +conn_never=This player has not logged into the server recently, and may not even exist. +conn_err=Player action failed +conn_etext=Missing message text +conn_msgdone=Sent message to player +conn_killdone=Killed player +conn_eid=Missing or invalid item ID +conn_ecount=Missing or non-numeric item count +conn_givedone=Gave $2 of item $1 to player + +chooser_title=Select Item +chooser_id=Item ID +chooser_name=Item name +chooser_none=No items matched your search +chooser_search=Show items matching +chooser_ok=Search + manual_title=Edit Configuration File diff --git a/minecraft/list_conns.cgi b/minecraft/list_conns.cgi new file mode 100644 index 000000000..b1313aa81 --- /dev/null +++ b/minecraft/list_conns.cgi @@ -0,0 +1,41 @@ +#!/usr/local/bin/perl +# Show all connected players + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%in, %text, %config); + +&ui_print_header(undef, $text{'conns_title'}, ""); + +&is_minecraft_server_running() || &error($text{'conns_edown'}); + +my @conns = &list_connected_players(); + +if (@conns) { + print $text{'conns_desc'},"
\n"; + my @grid; + @grid = map { &ui_checkbox("d", $_)." ". + "". + &html_escape($_)."" } @conns; + print &ui_form_start("mass_conns.cgi", "post"); + my @links = ( &select_all_link("d"), + &select_invert_link("d") ); + print &ui_links_row(\@links); + print &ui_grid_table(\@grid, 8, "100%"); + print &ui_links_row(\@links); + print &ui_form_end([ + [ "disc", $text{'conns_disc'} ], + ]); + } +else { + print "$text{'conns_none'}
\n"; + } + +print &ui_form_start("view_conn.cgi"); +print "$text{'conns_enter'} ", + &ui_textbox("name", undef, 20)." ". + &ui_submit($text{'conns_ok'}); +print &ui_form_end(); + +&ui_print_footer("", $text{'index_return'}); diff --git a/minecraft/minecraft-lib.pl b/minecraft/minecraft-lib.pl index 7f15a6baa..4faf65d76 100644 --- a/minecraft/minecraft-lib.pl +++ b/minecraft/minecraft-lib.pl @@ -8,10 +8,12 @@ BEGIN { push(@INC, ".."); }; use strict; use warnings; use WebminCore; +use Time::Local; &init_config(); our ($module_root_directory, %text, %gconfig, $root_directory, %config, $module_name, $remote_user, $base_remote_user, $gpgpath, $module_config_directory, @lang_order_list, @root_directories); +our $history_file = "$module_config_directory/history.txt"; # check_minecraft_server() # Returns an error message if the Minecraft server is not installed @@ -69,4 +71,204 @@ close($fh); return \@rv; } +# get_start_command() +# Returns a command to start the server +sub get_start_command +{ +my $jar = $config{'minecraft_jar'} || + $config{'minecraft_dir'}."/"."minecraft_server.jar"; +my $ififo = &get_input_fifo(); +my $rv = "(test -e ".$ififo." || mkfifo ".$ififo.") ; ". + "cd ".$config{'minecraft_dir'}." && ". + "(tail -f ".$ififo." | ". + &has_command($config{'java_cmd'})." ". + $config{'java_args'}." ". + " -jar ".$jar." nogui ". + $config{'jar_args'}." ". + ">> server.out 2>&1 )"; +return $rv; +} + +sub get_output_fifo +{ +return $config{'minecraft_dir'}."/output.fifo"; +} + +sub get_input_fifo +{ +return $config{'minecraft_dir'}."/input.fifo"; +} + +# start_minecraft_server() +# Launch the minecraft server in the background +sub start_minecraft_server +{ +my $cmd = &get_start_command(); +&system_logged("$cmd &"); +sleep(1); +if (!&is_minecraft_server_running()) { + my $out = &backquote_command( + "tail -2 ".$config{'minecraft_dir'}."/server.out"); + return $out || "Unknown error - no output produced"; + } +return undef; +} + +# stop_minecraft_server() +# Kill the server, if running +sub stop_minecraft_server +{ +my $pid = &is_minecraft_server_running(); +$pid || return "Not running!"; + +# Try graceful shutdown +&send_server_command("stop"); +for(my $i=0; $i<10; $i++) { + last if (!&is_minecraft_server_running()); + sleep(1); + } + +# Clean kill +if (&is_minecraft_server_running()) { + kill('TERM', $pid); + for(my $i=0; $i<10; $i++) { + last if (!&is_minecraft_server_running()); + sleep(1); + } + } + +# Fatal kill +if (&is_minecraft_server_running()) { + kill('KILL', $pid); + } +return undef; +} + +# send_server_command(command) +# Just sends a command to the server +sub send_server_command +{ +my ($cmd) = @_; +my $ififo = &get_input_fifo(); +my $fh = "FIFO"; +&open_tempfile($fh, ">$ififo", 1, 1, 1); +&print_tempfile($fh, $cmd."\n"); +&close_tempfile($fh); +} + +# execute_minecraft_command(command) +# Run a command, and return output from the server log +sub execute_minecraft_command +{ +my ($cmd) = @_; +my $logfile = $config{'minecraft_dir'}."/server.log"; +my $fh = "LOG"; +&open_readfile($fh, $logfile); +seek($fh, 0, 2); +my $pos = tell($fh); +&send_server_command($cmd); +for(my $i=0; $i<10; $i++) { + sleep(1); + my @st = stat($logfile); + last if ($st[7] > $pos); + } +my $out; +while(<$fh>) { + $out .= $_; + } +close($fh); +return wantarray ? split(/\r?\n/, $out) : $out; +} + +# get_command_history() +# Returns the history of commands run +sub get_command_history +{ +my $lref = &read_file_lines($history_file); +return @$lref; +} + +# save_command_history(&commands) +sub save_command_history +{ +my ($cmds) = @_; +my $lref = &read_file_lines($history_file); +@$lref = @$cmds; +&flush_file_lines($history_file); +} + +# list_connected_players() +# Returns a list of players currently online +sub list_connected_players +{ +my @out = &execute_minecraft_command("/list"); +my @rv; +foreach my $l (@out) { + if ($l =~ /\[INFO\]\s+(\S+)$/) { + push(@rv, $1); + } + } +return @rv; +} + +# get_login_logout_times(player) +# Returns the last login IP, time, X, Y, Z and logout time (if any) +sub get_login_logout_times +{ +my ($name) = @_; +my ($ip, $intime, $xx, $yy, $zz, $outtime); +my $logfile = $config{'minecraft_dir'}."/server.log"; +my $fh = "TAIL"; +&open_execute_command($fh, "tail -10000 $logfile", 1, 1); +while(<$fh>) { + if (/^(\d+)\-(\d+)\-(\d+)\s+(\d+):(\d+):(\d+)\s+\[\S+\]\s+(.*)/) { + my ($y, $mo, $d, $h, $m, $s, $msg) =($1, $2, $3, $4, $5, $6, $7); + if ($msg =~ /^\Q$name\E\[.*\/([0-9\.]+):(\d+)\]\s+logged\s+in.*\((\-?[0-9\.]+),\s+(\-?[0-9\.]+),\s+(\-?[0-9\.]+)\)/) { + # Login message + $ip = $1; + ($xx, $yy, $zz) = ($3, $4, $5); + $intime = &parse_log_time($y, $m, $d, $h, $mo, $s); + } + elsif ($msg =~ /^\Q$name\E\s+lost/) { + # Logout message + $outtime = &parse_log_time($y, $m, $d, $h, $mo, $s); + } + } + } +close($fh); +return ( $ip, $intime, $xx, $yy, $zz, $outtime ); +} + +sub parse_log_time +{ +my ($y, $m, $d, $h, $mo, $s) = @_; +return timelocal($s, $m, $h, $d, $mo-1, $y-1900); +} + +# item_chooser_button(fieldname) +sub item_chooser_button +{ +my ($field) = @_; +return "\n"; +} + +# list_minecraft_items() +# Returns a list of hash refs with id and name keys +# CSV generated with : +# wget -O - http://minecraft-ids.grahamedgecombe.com/ | grep /items/ | perl -ne 's/.*items.([0-9:]+)">([^<]+)<.*/$1,$2/; print ' > items.csv +sub list_minecraft_items +{ +my $fh = "ITEMS"; +&open_readfile($fh, "$module_root_directory/items.csv"); +my @rv; +while(<$fh>) { + s/\r|\n//g; + my ($id, $name) = split(/,/, $_); + push(@rv, { 'id' => $id, + 'name' => $name }); + } +close($fh); +return @rv; +} + 1; diff --git a/minecraft/output.cgi b/minecraft/output.cgi new file mode 100755 index 000000000..acd28cff3 --- /dev/null +++ b/minecraft/output.cgi @@ -0,0 +1,18 @@ +#!/usr/local/bin/perl +# Tail the console log + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%config); +my $logfile = $config{'minecraft_dir'}."/server.log"; + +$| = 1; +print "Content-type: text/plain\n\n"; +my $fh = "OUT"; +&open_execute_command($fh, "tail -20f ".$logfile, 1, 1); +select($fh); $| = 1; select(STDOUT); +while(<$fh>) { + print $_; + } +close($fh); diff --git a/minecraft/save_conn.cgi b/minecraft/save_conn.cgi new file mode 100644 index 000000000..02b197b04 --- /dev/null +++ b/minecraft/save_conn.cgi @@ -0,0 +1,42 @@ +#!/usr/local/bin/perl +# Perform some action on a player + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%in, %text); +&ReadParse(); +&error_setup($text{'conn_err'}); + +my $msg; +if ($in{'msg'}) { + # Send a message + $in{'text'} =~ /\S/ || &error($text{'conn_etext'}); + &send_server_command("/say $in{'name'} $in{'text'}"); + $msg = $text{'conn_msgdone'}; + } +elsif ($in{'kill'}) { + # Kill this player + &send_server_command("/kill $in{'name'}"); + $msg = $text{'conn_killdone'}; + } +elsif ($in{'give'}) { + # Give an item + $in{'id'} =~ /^\d+$/ || &error($text{'conn_eid'}); + $in{'count'} =~ /^\d+$/ || &error($text{'conn_ecount'}); + my ($i) = grep { $_->{'id'} eq $in{'id'} } + &list_minecraft_items(); + my $out = &execute_minecraft_command( + "/give $in{'name'} $in{'id'} $in{'count'}"); + &error($out); + $msg = &text('conn_givedone', $i ? $i->{'name'} : $in{'id'}, + $in{'count'}); + } +else { + # No button clicked! + &error($text{'conn_ebutton'}); + } +&redirect("view_conn.cgi?name=".&urlize($in{'name'})."&msg=". + &urlize($in{'msg'})); + + diff --git a/minecraft/start.cgi b/minecraft/start.cgi new file mode 100644 index 000000000..3650147c4 --- /dev/null +++ b/minecraft/start.cgi @@ -0,0 +1,12 @@ +#!/usr/local/bin/perl +# Start the Minecraft server + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%text); +&error_setup($text{'start_err'}); +my $err = &start_minecraft_server(); +&error("".&html_escape($err)."") if ($err); +&webmin_log("start"); +&redirect(""); diff --git a/minecraft/stop.cgi b/minecraft/stop.cgi new file mode 100755 index 000000000..42765866b --- /dev/null +++ b/minecraft/stop.cgi @@ -0,0 +1,12 @@ +#!/usr/local/bin/perl +# Kill the running minecraft server process + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%text); +&error_setup($text{'stop_err'}); +my $err = &stop_minecraft_server(); +&error($err) if ($err); +&webmin_log("stop"); +&redirect(""); diff --git a/minecraft/view_conn.cgi b/minecraft/view_conn.cgi new file mode 100644 index 000000000..2e45c4ce4 --- /dev/null +++ b/minecraft/view_conn.cgi @@ -0,0 +1,72 @@ +#!/usr/local/bin/perl +# Show details of a player, with buttons to perform actions + +use strict; +use warnings; +require './minecraft-lib.pl'; +our (%in, %text); +&ReadParse(); + +&ui_print_header(undef, $text{'conn_title'}, ""); + +$in{'name'} =~ /^\S+$/ || &error($text{'conn_ename'}); + +print &ui_form_start("save_conn.cgi", "post"); +print &ui_hidden("name", $in{'name'}); +print &ui_table_start($text{'conn_header'}, undef, 2); + +# Player name +print &ui_table_row($text{'conn_name'}, + &html_escape($in{'name'})); + +# Current state +my @conns = &list_connected_players(); +my ($c) = grep { $_ eq $in{'name'} } @conns; +print &ui_table_row($text{'conn_state'}, + $c ? $text{'conn_yes'} : "$text{'conn_no'}"); + +# Last login IP and time +my ($ip, $intime, $x, $y, $z, $outtime) = &get_login_logout_times($in{'name'}); +if ($ip) { + print &ui_table_row($c ? $text{'conn_lastin'} : $text{'conn_lastin2'}, + &text('conn_at', $ip, &make_date($intime))); + print &ui_table_row($text{'conn_pos'}, + "X:$x Y:$y Z:$z"); + } + +if (!$c && $outtime) { + # Logged out at + print &ui_table_row($text{'conn_lastout'}, + &make_date($outtime)); + } + +if ($c || 1) { + print &ui_table_hr(); + + # Send message + print &ui_table_row($text{'conn_msg'}, + &ui_textbox("text", undef, 40)." ". + &ui_submit($text{'conn_msgb'}, 'msg')); + + # Kill player + print &ui_table_row($text{'conn_kill'}, + &ui_submit($text{'conn_killb'}, 'kill')); + + # Grant item + print &ui_table_row($text{'conn_give'}, + &ui_textbox("id", undef, 5). + &item_chooser_button("id")." ". + $text{'conn_count'}." ". + &ui_textbox("count", 1, 5)." ". + &ui_submit($text{'conn_giveb'}, 'give')); + } + +print &ui_table_end(); +print &ui_form_end(); + +if (!$c && !$ip && !$outtime) { + print "$text{'conn_never'}
\n"; + } + +&ui_print_footer("list_conns.cgi", $text{'conns_return'}); + diff --git a/minecraft/view_logs.cgi b/minecraft/view_logs.cgi index 80bd41a72..1256f4ea2 100644 --- a/minecraft/view_logs.cgi +++ b/minecraft/view_logs.cgi @@ -37,6 +37,7 @@ my $fh = "OUT"; while(<$fh>) { print &html_escape($_); } +close($fh); print ""; &ui_print_footer("", $text{'index_return'});