mscs/minecraft_server
2013-12-01 10:57:09 -07:00

1564 lines
53 KiB
Bash
Executable File

#!/bin/sh
### BEGIN INIT INFO
# Provides: minecraft_server
# Required-Start: $remote_fs $syslog
# Required-Stop: $remote_fs $syslog
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# chkconfig: 345 50 50
# Description: Minecraft Server Control Script
### END INIT INFO
# ---------------------------------------------------------------------------
# Copyright (c) 2013, Jason M. Wood <sandain@hotmail.com>
#
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Minecraft Server Control Script
#
# A powerful command-line control script for Linux-powered Minecraft servers.
# ---------------------------------------------------------------------------
# Script Usage
# ---------------------------------------------------------------------------
USAGE=$(cat <<EOF
Usage: $0 <option>
Options:
start <world>
Start the Minecraft world server. Start all world servers by default.
stop <world>
Stop the Minecraft world server. Stop all world servers by default.
force-stop <world>
Forcibly stop the Minecraft world server. Forcibly stop all world
servers by default.
restart <world>
Restart the Minecraft world server. Restart all world servers by default.
force-restart <world>
Forcibly restart the Minecraft world server. Forcibly restart all world
servers by default.
create <world> <port> <ip>
Create a Minecraft world server. The world name and port must be
provided, the IP addressis usually blank.
delete <world>
Delete a Minecraft world server.
disable <world>
Temporarily disable a world server.
enable <world>
Enable a disabled world server.
status <world>
Display the status of the Minecraft world server. Display the status of
all world servers by default.
sync <world>
Synchronize the data stored in the mirror images of the Minecraft world
server. Synchronizes all of the world servers by default. This option
is only available when the mirror image option is enabled.
send <world> <command>
Send a command to a Minecraft world server.
screen <world>
Display the Screen for the Minecraft world server.
watch <world>
Watch the log file for the Minecraft world server.
logrotate <world>
Rotate the latest.log file. Rotate the latest.log file for all worlds by
default.
backup <world>
Backup the Minecraft world. Backup all worlds by default.
map <world>
Run the Minecraft Overviewer mapping software on the Minecraft world.
Map all worlds by default.
update
Update the client and server software.
EOF
)
# User Account & Server Location
# ---------------------------------------------------------------------------
# Who we run as and where we run from.
# User name used to run all commands. Be sure to create this user if it
# doesn't already exist (sudo adduser minecraft).
USER_NAME="minecraft"
# The location of server software and data.
LOCATION="/home/$USER_NAME"
# Required Software
# ---------------------------------------------------------------------------
# Detect its presence and location for later.
JAVA=$(which java)
PERL=$(which perl)
PYTHON=$(which python)
RSYNC=$(which rsync)
SCREEN=$(which screen)
WGET=$(which wget)
RDIFF_BACKUP=$(which rdiff-backup)
SOCAT=$(which socat)
# Global Server Configuration
# ---------------------------------------------------------------------------
# Mojang Versions information
# ---------------------------------------------------------------------------
# Detect the latest version.
CURRENT_VERSION=$(
wget -q -O - https://s3.amazonaws.com/Minecraft.Download/versions/versions.json |
$PERL -ne 'if ($_ =~ /^\s+\"release\": \"([0-9\.]+)\"/) { print $1; }'
)
# Minecraft Server Settings
# ---------------------------------------------------------------------------
# Choose only one server distribution, leave the other commented out.
# Default Mojang server distribution.
SERVER_JAR="minecraft_server.jar"
SERVER_URL="https://s3.amazonaws.com/Minecraft.Download/versions/$CURRENT_VERSION/minecraft_server.$CURRENT_VERSION.jar"
SERVER_ARGS="nogui"
# CraftBukkit server distribution.
# SERVER_URL="http://dl.bukkit.org/latest-rb/craftbukkit.jar"
# SERVER_JAR="craftbukkit.jar"
# SERVER_ARGS=""
# Generic server options.
INITIAL_MEMORY="128M"
MAXIMUM_MEMORY="2048M"
SERVER_LOCATION="$LOCATION/minecraft_server"
SERVER_COMMAND="$JAVA -Xms$INITIAL_MEMORY -Xmx$MAXIMUM_MEMORY -jar $SERVER_LOCATION/$SERVER_JAR $SERVER_ARGS"
# Minecraft Client Settings
# ---------------------------------------------------------------------------
# Used by Minecraft Overviewer mapping software.
CLIENT_JAR="$CURRENT_VERSION.jar"
CLIENT_URL="https://s3.amazonaws.com/Minecraft.Download/versions/$CURRENT_VERSION/$CURRENT_VERSION.jar"
CLIENT_LOCATION="$LOCATION/.minecraft/versions/$CURRENT_VERSION"
# World (Server Instance) Configuration
# ---------------------------------------------------------------------------
# The location to store files for each world server.
WORLDS_LOCATION="$LOCATION/worlds"
# The location to store disabled world server files.
DISABLED_WORLDS_LOCATION="$LOCATION/worlds-disabled"
# Default world name, port, and IP address if the worlds.conf file is
# missing.
DEFAULT_WORLD="world"
DEFAULT_PORT="25565"
DEFAULT_IP=""
# Global Message Of The Day file (MOTD)
# ---------------------------------------------------------------------------
# Location of the file to display to users on login. Nothing will be done if
# this file does not exist.
MOTD="$LOCATION/motd.txt"
# NOTE: MOTD can contain color codes as follows:
# §0 - black
# §1 - blue
# §2 - dark green
# §3 - aqua
# §4 - dark red
# §5 - purple
# §6 - gold
# §7 - gray
# §8 - dark gray
# §9 - light blue
# §a - green
# §b - teal
# §c - red
# §d - magenta
# §e - yellow
# §f - white
# Backup Configuration
# ---------------------------------------------------------------------------
# Location to store backups.
BACKUP_LOCATION="$LOCATION/backups"
# Location of the backup log file.
BACKUP_LOG="$BACKUP_LOCATION/backup.log"
# Length in days that backups survive.
BACKUP_DURATION=15
# Server Log Configuration
# ---------------------------------------------------------------------------
# How many rotations of latest.log to keep
LOG_COUNT=10
# Mirror Image Options
# ---------------------------------------------------------------------------
# Create a mirror image of the world data on system startup, and
# update that mirror image on system shutdown.
#
# IMPORTANT: If using this option, the admin should schedule
# periodic synchronizations of the mirror image using cron
# to avoid data loss. To do this, add a cron task to call
# the "sync" option on a VERY regular basis (e.g.,
# every 5-10 minutes).
#
# 0 - Do not use a mirror image, default.
# 1 - Use a mirror image.
ENABLE_MIRROR=0
# The location to store the mirror image.
#
# NOTE: This is usually a ramdisk, e.g. /dev/shm on Debian/Ubuntu.
MIRROR_PATH="/dev/shm/minecraft"
# Mincecraft Overviewer Mapping Software Options
# ---------------------------------------------------------------------------
OVERVIEWER_BIN=$(which overviewer.py)
MAPS_URL="http://minecraft.server.com/maps"
MAPS_LOCATION="$LOCATION/maps"
# Lib-Notify Configuration
# ---------------------------------------------------------------------------
# Use lib-notify to print a message on your desktop of important server
# events.
# 0 - Do not use lib-notify.
# 1 - Display server events using lib-notify.
USE_LIBNOTIFY=0
# The username and display that messages will be routed to.
LIBNOTIFY_USER_NAME=$USER_NAME
LIBNOTIFY_DISPLAY=":0.0"
# ---------------------------------------------------------------------------
# Internal Methods
# ---------------------------------------------------------------------------
#
# NOTE: Nothing below this point should need to be edited directly.
# ---------------------------------------------------------------------------
# ---------------------------------------------------------------------------
# Execute the given command.
#
# @param 1 The command to execute.
# @param 2 The user name to execute the command with.
# ---------------------------------------------------------------------------
execute() {
if [ $(id -u) -eq 0 ]; then
# Script is running as root, switch user and execute
# the command.
su -c "$1" $2
else
# Script is running as a user, just execute the command.
sh -c "$1"
fi
}
# ---------------------------------------------------------------------------
# Get the PIDs of the Screen and Java process for the world server.
#
# @param 1 The world server of interest.
# @return The Screen and Java PIDs.
# ---------------------------------------------------------------------------
getProcessIDs() {
local SCREEN_PID JAVA_PID
SCREEN_PID=$(execute "$SCREEN -ls" $USER_NAME | $PERL -ne 'if ($_ =~ /^\t(\d+)\.minecraft-'$1'\s+/) { print $1; }')
JAVA_PID=$(ps -a -u $USER_NAME -o pid,ppid,comm | $PERL -ne 'if ($_ =~ /^\s*(\d+)\s+'$SCREEN_PID'\s+java/) { print $1; }')
echo "$SCREEN_PID $JAVA_PID"
}
# ---------------------------------------------------------------------------
# Check to see if the world server is running.
#
# @param 1 The world server of interest.
# @return A 1 if the server is thought to be running, a 0 otherwise.
# ---------------------------------------------------------------------------
serverRunning() {
local PIDS SCREEN_PID JAVA_PID
PIDS=$(getProcessIDs $1)
SCREEN_PID=$(echo "$PIDS" | cut -d ' ' -f1)
JAVA_PID=$(echo "$PIDS" | cut -d ' ' -f2)
# Try to determine if the world is running.
if [ -n "$SCREEN_PID" ] && [ $SCREEN_PID -gt 0 ] &&
[ -n "$JAVA_PID" ] && [ $JAVA_PID -gt 0 ]; then
echo 1
else
echo 0
fi
}
# ---------------------------------------------------------------------------
# Send a command to the world server.
#
# @param 1 The world server of interest.
# @param 2 The command to send.
# ---------------------------------------------------------------------------
sendCommand() {
local COMMAND PID
COMMAND=$(printf "$2\r")
PID=$(echo $(getProcessIDs $1) | cut -d ' ' -f1)
execute "$SCREEN -S $PID.minecraft-$1 -p 0 -X stuff \"$COMMAND\"" $USER_NAME
if [ $? -ne 0 ]; then
printf "Error sending command to server $1.\n"
exit 1
fi
}
# ---------------------------------------------------------------------------
# Connect to the Screen of a world server.
#
# @param 1 The world server of interest.
# ---------------------------------------------------------------------------
displayScreen() {
local PID TTY_PERMISSIONS
PID=$(echo $(getProcessIDs $1) | cut -d ' ' -f1)
TTY_PERMISSIONS=$($PERL -e 'printf "%04o", ((stat(shift))[2] & 07777);' $(tty))
# Make sure that we have read/write access to the tty.
execute "chmod o+rw $(tty)" > /dev/null 2>&1
if [ $? -ne 0 ]; then
printf "Error changing the permissions of the tty.\n"
printf "Try giving user '$USER_NAME' access to the tty with:\n"
printf " chmod o+rw $(tty)\n"
printf "\n"
printf "Attempting to load the screen anyway.\n"
fi
# Connect to the screen of the world server.
execute "$SCREEN -x $PID.minecraft-$1" $USER_NAME
if [ $? -ne 0 ]; then
printf "Error connecting to Screen.\n"
execute "chmod $TTY_PERMISSIONS $(tty)" > /dev/null 2>&1
exit 1
fi
execute "chmod $TTY_PERMISSIONS $(tty)" > /dev/null 2>&1
}
# ---------------------------------------------------------------------------
# Check whether the item is in the list.
#
# @param 1 The item being searched for.
# @param 2 The list being searched.
# @return A 1 if the list contains the item, a 0 otherwise.
# ---------------------------------------------------------------------------
listContains() {
local MATCH ITEM
MATCH=0
for ITEM in $2; do
if [ "$ITEM" = "$1" ]; then
MATCH=1
fi
done
echo $MATCH
}
# ---------------------------------------------------------------------------
# Create a world.
#
# @param 1 The world server to create.
# @param 2 The port of the world server.
# @param 3 The IP address of the world server.
# ---------------------------------------------------------------------------
createWorld() {
# Create a basic server.properties file. Values not supplied here
# will use default values when the world server is first started.
execute "mkdir -p $WORLDS_LOCATION/$1" $USER_NAME
setPropertiesValue "$1" "level-name" "$1"
setPropertiesValue "$1" "server-port" "$2"
setPropertiesValue "$1" "server-ip" "$3"
setPropertiesValue "$1" "enable-query" "true"
setPropertiesValue "$1" "query.port" "$2"
}
# ---------------------------------------------------------------------------
# Delete a world.
#
# @param 1 The world server to delete.
# ---------------------------------------------------------------------------
deleteWorld() {
# Delete the world directory.
execute "rm -Rf $WORLDS_LOCATION/$1" $USER_NAME
}
# ---------------------------------------------------------------------------
# Disable a world.
#
# @param 1 The world server to disable.
# ---------------------------------------------------------------------------
disableWorld() {
# Disable the world.
execute "mv $WORLDS_LOCATION/$1 $DISABLED_WORLDS_LOCATION/$1" $USER_NAME
}
# ---------------------------------------------------------------------------
# Enable a world.
#
# @param 1 The world server to enable.
# ---------------------------------------------------------------------------
enableWorld() {
# Enable the world.
execute "mv $DISABLED_WORLDS_LOCATION/$1 $WORLDS_LOCATION/$1" $USER_NAME
}
# ---------------------------------------------------------------------------
# Grab the first line of the Message of the Day file as a summary, and strip
# any color codes from it.
#
# @param 1 The world server of interest.
# @return The global message of the day or the specifued world
# ---------------------------------------------------------------------------
getMOTD() {
local MOTD_SUMMARY WORLD_MOTD
MOTD_SUMMARY=""
WORLD_MOTD="$WORLDS_LOCATION/$1.motd"
if [ -e $WORLD_MOTD ]; then
MOTD_SUMMARY=$(head -n 1 $WORLD_MOTD | $PERL -ne '$_ =~ s/§[0-9a-fA-F]//g; print;')
elif [ -e $MOTD ]; then
MOTD_SUMMARY=$(head -n 1 $MOTD | $PERL -ne '$_ =~ s/§[0-9a-fA-F]//g; print;')
fi
echo $MOTD_SUMMARY
}
# ---------------------------------------------------------------------------
# Grab the list of worlds.
#
# @return The list of worlds.
# ---------------------------------------------------------------------------
getWorlds() {
local WORLD WORLDS
WORLDS=""
for WORLD in $(ls $WORLDS_LOCATION); do
if [ -f $WORLDS_LOCATION/$WORLD/server.properties ]; then
WORLDS="$WORLDS $WORLD"
fi
done
echo $WORLDS
}
# ---------------------------------------------------------------------------
# Get the value of a key in a world properties file.
#
# @param 1 The world server of interest.
# @param 2 The key to get.
# @param 3 The default value.
# ---------------------------------------------------------------------------
getPropertiesValue() {
local PROPERTY_FILE KEY VALUE
PROPERTY_FILE=$WORLDS_LOCATION/$1/server.properties
# Make sure the properties file exists
if [ -e "$PROPERTY_FILE" ]; then
# Find the key/value combo.
KEY=$($PERL -ne 'if ($_ =~ /^('$2')=.*$/) { print "$1"; }' $PROPERTY_FILE)
VALUE=$($PERL -ne 'if ($_ =~ /^'$2'=(.*)$/) { print "$1"; }' $PROPERTY_FILE)
if [ -n "$KEY" ] && [ -n "$VALUE" ]; then
echo "$VALUE"
else
echo "$3"
fi
fi
}
# ---------------------------------------------------------------------------
# Modify the value of a key/value combo in a world properties file.
#
# @param 1 The world server of interest.
# @param 2 The key to modify.
# @param 3 The value to assign to the key.
# ---------------------------------------------------------------------------
setPropertiesValue() {
local PROPERTY_FILE KEY_VALUE
PROPERTY_FILE=$WORLDS_LOCATION/$1/server.properties
# Make sure that the properties file exists.
execute "touch $PROPERTY_FILE" $USER_NAME
# Replace the key/value combo if it already exists, otherwise just
# append it to the end of the file.
KEY_VALUE=$($PERL -ne 'if ($_ =~ /^('$2'=.*)$/) { print "$1"; }' $PROPERTY_FILE)
if [ -n "$KEY_VALUE" ]; then
execute "$PERL -i -ne 'if (\$_ =~ /^$2=.*$/) { print \"$2=$3\\n\"; } else { print; }' $PROPERTY_FILE" $USER_NAME
else
execute "printf \"$2=$3\\n\" >> $PROPERTY_FILE" $USER_NAME
fi
}
# ---------------------------------------------------------------------------
# Send a message to the desktop using lib-notify, if it is available.
#
# @param 1 The summary of the message to send.
# @param 2 The body of the message to send.
# ---------------------------------------------------------------------------
libNotify() {
local NOTIFY
NOTIFY=$(which notify-send)
if [ -e "$NOTIFY" ]; then
execute "DISPLAY=$LIBNOTIFY_DISPLAY $NOTIFY \"$1\" \"$2\"" $LIBNOTIFY_USER_NAME > /dev/null 2>&1
fi
}
# ---------------------------------------------------------------------------
# Send the contents of the Message Of The Day (MOTD) to the user.
#
# @param 1 The world server of interest.
# @param 2 The user being told the contents of the motd file.
# ---------------------------------------------------------------------------
tellMOTD() {
local LINE WORLD_MOTD
WORLD_MOTD="$WORLDS_LOCATION/$1.motd"
if [ -e $WORLD_MOTD ]; then
while read LINE; do
sendCommand $1 "tell $2 $LINE"
done < $WORLD_MOTD
elif [ -e $MOTD ]; then
while read LINE; do
sendCommand $1 "tell $2 $LINE"
done < $MOTD
fi
}
# ---------------------------------------------------------------------------
# Check to see if the user is in the ops.txt file of the specified world.
#
# @param 1 The world server of interest.
# @param 2 The user being checked.
# ---------------------------------------------------------------------------
checkUserIsAdmin() {
local IS_ADMIN
IS_ADMIN=$(cat $WORLDS_LOCATION/$1/ops.txt | $PERL -ne 'if ($_ =~ /^'$2'$/i) { print "1"; }')
echo $IS_ADMIN
}
# ---------------------------------------------------------------------------
# Rotates the world latest.log file.
#
# @param 1 The world server generating the log to rotate.
# ---------------------------------------------------------------------------
rotateLog() {
local WORLD_DIR LOG_LIST LOG_LINES LOG_NUMBER
WORLD_DIR="$WORLDS_LOCATION/$1"
# Make sure that the latest.log file exists.
execute "touch $WORLD_DIR/logs/latest.log" $USER_NAME
# Scan the log for entires and skip rotate is none are found.
LOG_LINES="$(cat "$WORLD_DIR/logs/latest.log" | wc -l )"
if [ $LOG_LINES -le 1 ]; then
printf "\nNo new log entries to rotate. No changes made.\n"
return 0
fi
# Server logfiles in chronological order.
LOGLIST=$(ls -r $WORLD_DIR/logs/latest.log* | grep -v lck)
# Look at all the logfiles
for i in $LOGLIST; do
LOG_NUMBER=$(ls $i | cut -d "." -f 3)
# If we're working with latest.log, append .1 then compress
# it.
if [ -z $LOG_NUMBER ]; then
LOG_NUMBER="1"
execute "cp $WORLD_DIR/logs/latest.log $WORLD_DIR/logs/latest.log.$LOG_NUMBER" $USER_NAME
execute "gzip $WORLD_DIR/logs/latest.log.$LOG_NUMBER" $USER_NAME
# Otherwise, check if the file number is under $LOG_COUNT.
elif [ $LOG_NUMBER -ge $LOG_COUNT ]; then
# If so, delete it.
execute "rm -f $i" $USER_NAME
else
# Otherwise, add one to the number.
LOG_NUMBER=$(($LOG_NUMBER+1))
execute "mv -f $i $WORLD_DIR/logs/latest.log.$LOG_NUMBER.gz" $USER_NAME
fi
done
# Blank the existing logfile to renew it.
execute "cp /dev/null $WORLD_DIR/logs/latest.log" $USER_NAME
}
# ---------------------------------------------------------------------------
# Watch the world latest.log file.
#
# @param 1 The world server generating the log to watch.
# ---------------------------------------------------------------------------
watchLog() {
local PID WORLD_DIR
WORLD_DIR="$WORLDS_LOCATION/$1"
# Make sure that the latest.log file exists.
if [ -e "$WORLD_DIR/logs/latest.log" ]; then
# Watch the log.
PID=$(echo $(getProcessIDs $1) | cut -d ' ' -f2)
tail -n0 -f --pid=$PID $WORLD_DIR/logs/latest.log
fi
}
# ---------------------------------------------------------------------------
# Synchronizes the data stored in the mirror images.
#
# @param 1 The world server to sync.
# ---------------------------------------------------------------------------
syncMirrorImage() {
# Sync the world server.
execute "cp -Ru $WORLDS_LOCATION/$1/$1/* $WORLDS_LOCATION/$1/$1-original" $USER_NAME
if [ $? -ne 0 ]; then
printf "Error synchronizing mirror images for world $1.\n"
exit 1
fi
}
# ---------------------------------------------------------------------------
# Start the world server. Generate the appropriate environment for the
# server if it doesn't already exist.
#
# @param 1 The world server to start.
# ---------------------------------------------------------------------------
start() {
local PID WORLD_DIR
# Make sure that the world's directory exists.
WORLD_DIR="$WORLDS_LOCATION/$1"
execute "mkdir -p $WORLD_DIR" $USER_NAME
# Make a mirror image of the world directory if requested.
if [ $ENABLE_MIRROR -eq 1 ]; then
execute "mkdir -p $MIRROR_PATH/$1" $USER_NAME
if [ $? -ne 0 ]; then
printf "Error copying world data, path %s not found.\n" $MIRROR_PATH/$1
exit 1
fi
# Check for a clean dismount from the previous server run. If we have a
# <world>-original directory within <world> we didn't stop cleanly.
if [ -d "WORLDS_LOCATION/$1/$1-original" ]; then
# Remove the symlink to the world-file mirror image.
execute "rm -r $WORLDS_LOCATION/$1/$1" $USER_NAME
# Move the world files back to their original path name.
execute "mv $WORLDS_LOCATION/$1/$1-original $WORLDS_LOCATION/$1/$1" $USER_NAME
fi
# Copy the world files over to the mirror.
execute "cp -R $WORLDS_LOCATION/$1/$1/* $MIRROR_PATH/$1" $USER_NAME
# Rename the original world file directory.
execute "mv $WORLDS_LOCATION/$1/$1 $WORLDS_LOCATION/$1/$1-original" $USER_NAME
# Create a symlink from the world file directory's original name to the mirrored files.
execute "ln -s $MIRROR_PATH/$1 $WORLDS_LOCATION/$1/$1" $USER_NAME
fi
# Change to the world's directory.
cd $WORLD_DIR
# Start the server.
execute "$SCREEN -dmS minecraft-$1 $SERVER_COMMAND" $USER_NAME
if [ $? -ne 0 ]; then
printf "Error starting the server.\n"
exit 1
fi
# Grab the Screen Process ID of the server.
PID=$(echo $(getProcessIDs $1) | cut -d ' ' -f1)
if [ ! -n "$PID" ]; then
printf "Error starting the server: SCREEN failed to create a screen for the server.\n"
exit 1
fi
# Grab the Java Process ID of the server.
PID=$(echo $(getProcessIDs $1) | cut -d ' ' -f2)
if [ ! -n "$PID" ]; then
printf "Error starting the server: couldn't retrieve the server's process ID.\n"
exit 1
fi
# Start a tail process to watch for changes to the query.dat file to pipe to
# the Minecraft query server via netcat. The response from the query
# server is piped into the response.dat file.
execute "rm -Rf $WORLD_DIR/query.dat $WORLD_DIR/response.dat" $USER_NAME
execute "printf '' > $WORLD_DIR/query.dat" $USER_NAME
execute "printf '' > $WORLD_DIR/response.dat" $USER_NAME
execute "
tail -f --pid=$PID $WORLD_DIR/query.dat | \
$SOCAT - UDP:127.0.0.1:$(getPropertiesValue $1 'server-port') > \
$WORLD_DIR/response.dat &
" $USER_NAME
# Create a lock file on RedHat and derivatives.
if [ -d "/var/lock/subsys" ]; then
touch /var/lock/subsys/minecraft_server
fi
}
# ---------------------------------------------------------------------------
# Stop the world server.
#
# @param 1 The world server to stop.
# ---------------------------------------------------------------------------
stop() {
local WORLD NUM
sendCommand $1 "stop"
# Synchronize the mirror image of the world prior to closing, if
# required.
if [ $ENABLE_MIRROR -eq 1 ] && [ -d $MIRROR_PATH ]; then
syncMirrorImage $1
# Remove the symlink to the world-file mirror image.
execute "rm -r $WORLDS_LOCATION/$1/$1" $USER_NAME
# Move the world files back to their original path name.
execute "mv $WORLDS_LOCATION/$1/$1-original $WORLDS_LOCATION/$1/$1" $USER_NAME
fi
# Remove the lock file on Redhat and derivatives if all world servers
# are stopped.
if [ -e "/var/lock/subsys/minecraft_server" ]; then
NUM=0
for WORLD in $ALL_WORLDS; do
if [ "$1" != "$WORLD" ] && [ $(serverRunning $WORLD) -eq 1 ]; then
NUM=$(($NUM + 1))
fi
done
if [ $NUM -eq 0 ]; then
rm -f /var/lock/subsys/minecraft_server
fi
fi
}
# ---------------------------------------------------------------------------
# Forcibly stop the world server.
#
# @param 1 The world server to forcibly stop.
# ---------------------------------------------------------------------------
forceStop() {
local PIDS
PIDS=$(getProcessIDs $1)
# Try to stop the server cleanly first.
stop $1
sleep 5
# Kill the process ids of the world server.
kill -9 $PIDS > /dev/null 2>&1
# Cleanup the dead screen.
execute "$SCREEN -wipe" $USER_NAME > /dev/null 2>&1
# Remove the lock file on Redhat and derivatives if it is still
# around.
rm -f /var/lock/subsys/minecraft_server
}
# ---------------------------------------------------------------------------
# Backup the world server.
#
# @param 1 The world server to backup.
# ---------------------------------------------------------------------------
worldBackup() {
# Make sure that the backup location exists.
execute "mkdir -p $BACKUP_LOCATION" $USER_NAME
# Create the backup.
execute "$RDIFF_BACKUP -v5 --print-statistics $WORLDS_LOCATION/$1 $BACKUP_LOCATION/$1 >> $BACKUP_LOG" $USER_NAME
# Cleanup old backups.
if [ $BACKUP_DURATION -gt 0 ]; then
execute "$RDIFF_BACKUP --remove-older-than ${BACKUP_DURATION}D --force $BACKUP_LOCATION/$1 >> $BACKUP_LOG" $USER_NAME
fi
}
# ---------------------------------------------------------------------------
# Update the Minecraft client software.
# ---------------------------------------------------------------------------
updateClientSoftware() {
# Make sure the client software directory exists.
execute "mkdir -p $CLIENT_LOCATION" $USER_NAME
# Download the Minecraft client software.
if [ ! -e "$CLIENT_LOCATION/$CLIENT_JAR" ]; then
execute "$WGET -qO \"$CLIENT_LOCATION/$CLIENT_JAR\" \"$CLIENT_URL\"" $USER_NAME
# Report any errors.
if [ $? -ne 0 ]; then
printf "\nError updating the Minecraft client software.\n"
exit 1
fi
fi
}
# ---------------------------------------------------------------------------
# Update the Minecraft server software.
# ---------------------------------------------------------------------------
updateServerSoftware() {
execute "mkdir -p $SERVER_LOCATION" $USER_NAME
# Backup the old jar file.
if [ -e "$SERVER_LOCATION/$SERVER_JAR" ]; then
execute "mv -f \"$SERVER_LOCATION/$SERVER_JAR\" \"$SERVER_LOCATION/$SERVER_JAR.old\"" $USER_NAME
fi
# Download the new minecraft server software.
execute "$WGET -qO \"$SERVER_LOCATION/$SERVER_JAR\" \"$SERVER_URL\"" $USER_NAME
# Check for error and restore backup if found.
if [ $? -ne 0 ]; then
printf "\nError updating the Minecraft server software.\n"
if [ -e "$SERVER_LOCATION/$SERVER_JAR.old" ]; then
execute "mv -f $SERVER_LOCATION/$SERVER_JAR.old $SERVER_LOCATION/$SERVER_JAR" $USER_NAME
fi
exit 1
fi
}
# ---------------------------------------------------------------------------
# Run Minecraft Overviewer mapping software on the world. Generates an
# index.html file using the Google Maps API.
#
# @param 1 The world server to map with Overviewer.
# ---------------------------------------------------------------------------
overviewer() {
# Make sure the maps directory exists.
execute "mkdir -p $MAPS_LOCATION/$1" $USER_NAME
# Make sure the Minecraft client is available.
updateClientSoftware
# Make sure that the world files are actually there before mapping.
if [ -e "$WORLDS_LOCATION/$1/server.properties" ]; then
# Check for Overviewer settings file.
if [ -e "$WORLDS_LOCATION/$1/overviewer-settings.py" ]; then
# Generate map and POI with custom settings.
execute "$OVERVIEWER_BIN --config=$WORLDS_LOCATION/$1/overviewer-settings.py" $USER_NAME
execute "$OVERVIEWER_BIN --config=$WORLDS_LOCATION/$1/overviewer-settings.py --genpoi" $USER_NAME
else
# Generate map with default settings.
execute "$OVERVIEWER_BIN --rendermodes=normal,lighting,cave --processes 1 $WORLDS_LOCATION/$1/$1 $MAPS_LOCATION/$1" $USER_NAME > /dev/null 2>&1
fi
fi
}
# ---------------------------------------------------------------------------
# Pack a hex string into a buffer file that is piped to the Minecraft query
# server.
#
# @param 1 The world server of interest.
# @param 2 The packet type.
# @param 3 The packet ID.
# @param 4 The packet payload.
# @param 5 The response format.
# @return The response from the Query server in the requested format.
# ---------------------------------------------------------------------------
querySendPacket() {
local PACKET RESPONSE WORLD_DIR
# The world's directory.
WORLD_DIR="$WORLDS_LOCATION/$1"
# Add the magic bytes to the incoming packet.
PACKET=$(printf "FEFD%s%s%s" "$2" "$3" "$4")
# Remove any old responses from the response.dat buffer file.
execute "printf '' > $WORLD_DIR/response.dat" $USER_NAME
# Pack the hex string packet and write it to the query.dat buffer file.
execute "$PERL -e '
print map { pack (\"C\", hex(\$_)) } (\"'$PACKET'\" =~ /(..)/g);
' >> $WORLD_DIR/query.dat" $USER_NAME
# Give the query server a moment to respond.
sleep 1
# Unpack the token from the response.dat buffer file. There are a
# variable amount of null bytes at the start of the response string, so
# find the start of the packet by searching for the packet type and ID.
RESPONSE=$($PERL -ne '
$hex .= sprintf "%.2x", $_ foreach (unpack "C*", $_);
$hex =~ s/^0*'$2$3'/'$2$3'/;
print $hex;
' $WORLD_DIR/response.dat)
# Return the response in the format requested.
$PERL -e '
$packed = join "", map { pack ("C", hex($_)) } ("'$RESPONSE'" =~ /(..)/g);
printf "%s\n", join "\t", unpack ("'$5'", $packed);
'
}
# ---------------------------------------------------------------------------
# Send a challenge packet to the Minecraft query server.
#
# @param 1 The world server of interest.
# @return Tab separated values:
# type - The packet type.
# id - The packet identifier.
# token - The token.
# ---------------------------------------------------------------------------
querySendChallengePacket() {
local ID PACKET RESPONSE
# The packet identifier.
ID="00000001"
# Use an empty packet.
PACKET="00000000"
# Send the challenge packet to the Minecraft query server.
RESPONSE=$(querySendPacket "$1" "09" "$ID" "$PACKET" "Cl>Z*")
# Return the response.
printf "$RESPONSE\n"
}
# ---------------------------------------------------------------------------
# Send an information request packet to the Minecraft query server.
#
# @param 1 The world server of interest.
# @param 2 The challenge token.
# @return Tab separated values:
# type - The packet type.
# id - The packet identifier.
# MOTD - The world's message of the day.
# gametype - The world's game type, hardcoded to 'SMP'.
# map - The world's name.
# numplayers - The world's current number of players.
# maxplayers - The world's maximum number of players.
# hostport - The world's host port.
# hostip - The world's host IP address.
# ---------------------------------------------------------------------------
querySendInformationPacket() {
local ID PACKET RESPONSE
# The packet identifier.
ID="00000001"
# Use the challenge token for the packet.
PACKET=$(printf "%.8x" $2)
# Send the information request packet to the Minecraft query server.
RESPONSE=$(querySendPacket "$1" "00" "$ID" "$PACKET" "Cl>Z*Z*Z*Z*Z*s<Z*")
# Return the response.
printf "$RESPONSE\n"
}
# ---------------------------------------------------------------------------
# Send a detailed information request packet to the Minecraft query server.
#
# @param 1 The world server of interest.
# @param 2 The challenge token.
# @return Tab separated values:
# type - The packet type.
# id - The packet identifier.
# * - The string 'splitnum'.
# * - The value 128.
# * - The value 0.
# * - The string 'hostname'.
# MOTD - The world's message of the day.
# * - The string 'gametype'.
# gametype - The world's game type, hardcoded to 'SMP'.
# * - The string 'game_id'.
# gameid - The world's game ID, hardcoded to 'MINECRAFT'.
# * - The string 'version'.
# version - The world's Minecraft version.
# * - The string 'plugins'.
# plugins - The world's plugins.
# * - The string 'map'.
# map - The world's name.
# * - The string 'numplayers'.
# numplayers - The world's current number of players.
# * - The string 'maxplayers'.
# maxplayers - The world's maximum number of players.
# * - The string 'hostport'.
# hostport - The world's host port.
# * - The string 'hostip'.
# hostip - The world's host IP address.
# * - The value 0.
# * - The value 1.
# * - The string 'player_'.
# * - The value 0.
# players - The players currently logged onto the world.
# ---------------------------------------------------------------------------
querySendDetailedInformationPacket() {
local ID PACKET RESPONSE
# The packet identifier.
ID="00000001"
# Use the challenge token for the packet, with the ID on the end.
PACKET=$(printf "%.8x%s" $2 $ID)
# Send the information request packet to the Minecraft query server.
RESPONSE=$(querySendPacket "$1" "00" "$ID" "$PACKET" \
"Cl>Z*CCZ*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*Z*CCZ*C(Z*)*")
# Return the response.
printf "$RESPONSE\n"
}
# ---------------------------------------------------------------------------
# Send a status query to the Minecraft query server.
#
# @param 1 The world server of interest.
# @return Tab separated values:
# type - The packet type.
# id - The packet identifier.
# MOTD - The world's message of the day.
# gametype - The world's game type.
# map - The name of the world.
# numplayers - The current number of players.
# maxplayers - The maximum number of players.
# hostport - The host's port
# hostip - The host's IP address.
# ---------------------------------------------------------------------------
queryStatus() {
local TOKEN RESPONSE
# Send a challenge packet to the Minecraft query server.
TOKEN=$(querySendChallengePacket $1 | cut -f 3)
# Send an information request packet to the Minecraft query server.
RESPONSE=$(querySendInformationPacket $1 $TOKEN)
# Return the response.
printf "$RESPONSE\n"
}
# ---------------------------------------------------------------------------
# Send a detailed status query to the Minecraft query server.
#
# @param 1 The world server of interest.
# @return Tab separated values:
# type - The packet type.
# id - The packet identifier.
# * - The string 'splitnum'.
# * - The value 128.
# * - The value 0.
# * - The string 'hostname'.
# MOTD - The world's message of the day.
# * - The string 'gametype'.
# gametype - The world's game type, hardcoded to 'SMP'.
# * - The string 'game_id'.
# gameid - The world's game ID, hardcoded to 'MINECRAFT'.
# * - The string 'version'.
# version - The world's Minecraft version.
# * - The string 'plugins'.
# plugins - The world's plugins.
# * - The string 'map'.
# map - The world's name.
# * - The string 'numplayers'.
# numplayers - The world's current number of players.
# * - The string 'maxplayers'.
# maxplayers - The world's maximum number of players.
# * - The string 'hostport'.
# hostport - The world's host port.
# * - The string 'hostip'.
# hostip - The world's host IP address.
# * - The value 0.
# * - The value 1.
# * - The string 'player_'.
# * - The value 0.
# players - The players currently logged onto the world.
# ---------------------------------------------------------------------------
queryDetailedStatus() {
local TOKEN RESPONSE
# Send a challenge packet to the Minecraft query server.
TOKEN=$(querySendChallengePacket $1 | cut -f 3)
# Send an information request packet to the Minecraft query server.
RESPONSE=$(querySendDetailedInformationPacket $1 $TOKEN)
# Return the response.
printf "$RESPONSE\n"
}
# ---------------------------------------------------------------------------
# Display the status of a Minecraft world server.
#
# @param 1 The world server of interest.
# ---------------------------------------------------------------------------
worldStatus() {
local STATUS
if [ $(serverRunning $1) -eq 1 ]; then
STATUS=$(queryStatus $1)
printf "running (%d of %d users online).\n" $(echo "$STATUS" | cut -f6) $(echo "$STATUS" | cut -f7)
printf " Process IDs: Screen %d, Java %d.\n" $(getProcessIDs $1)
else
printf "not running.\n"
fi
}
# ---------------------------------------------------------------------------
# Begin.
# ---------------------------------------------------------------------------
# Make sure that Java, Perl, Python, Rsync, GNU Screen, and GNU Wget are
# installed.
if [ ! -e "$JAVA" ]; then
printf "ERROR: Java not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install default-jre"
exit 1
fi
if [ ! -e "$PERL" ]; then
printf "ERROR: Perl not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install perl\n"
exit 1
fi
if [ ! -e "$PYTHON" ]; then
printf "ERROR: Python not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install python\n"
exit 1
fi
if [ ! -e "$RSYNC" ] && [ $ENABLE_MIRROR -eq 1 ]; then
printf "ERROR: Rsync not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install rsync\n"
exit 1
fi
if [ ! -e "$SCREEN" ]; then
printf "ERROR: GNU Screen not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install screen\n"
exit 1
fi
if [ ! -e "$WGET" ]; then
printf "ERROR: GNU Wget not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install wget\n"
exit 1
fi
if [ ! -e "$RDIFF_BACKUP" ]; then
printf "ERROR: rdiff-backup not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install rdiff-backup\n"
exit 1
fi
if [ ! -e "$SOCAT" ]; then
printf "ERROR: socat not found!\n"
printf "Try installing this with:\n"
printf "sudo apt-get install socat\n"
exit 1
fi
# Make sure that the minecraft user exists.
if [ ! -n "$(grep $USER_NAME /etc/passwd)" ]; then
printf "ERROR: This script requires that a user account named "
printf "$USER_NAME exist on this system.\nEither modify the "
printf "USER_NAME variable in this script, or try adding this "
printf "user:\n"
printf "sudo adduser $USER_NAME\n"
exit 1
fi
# Warn if the script is running with the wrong user.
if [ $(id -u) -ne 0 ] && [ "$(whoami)" != "$USER_NAME" ]; then
printf "WARNING: This script appears to have been started by the "
printf "wrong user.\n"
printf "Expected to find the user: $USER_NAME. You can try to log "
printf "on to this user:\n"
printf "su $USER_NAME\n"
exit 1
fi
# Make sure that the server software exists.
if [ ! -e "$SERVER_LOCATION/$SERVER_JAR" ]; then
printf "Server software not found, downloading it...\n"
updateServerSoftware
fi
# Grab the list of worlds.
ALL_WORLDS=$(getWorlds)
# Respond to the command line arguments.
case "$1" in
start)
# Figure out which worlds to start.
WORLDS="$ALL_WORLDS"
# If there are no worlds in the list, then use the default world.
if [ ! -n "$WORLDS" ]; then
WORLDS="$DEFAULT_WORLD"
createWorld "$DEFAULT_WORLD" "$DEFAULT_PORT" "$DEFAULT_IP"
fi
# If an optional argument was supplied, verify it is a valid world.
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Start each world requested, if not already running.
printf "Starting Minecraft Server:"
for WORLD in $WORLDS; do
if [ $(serverRunning $WORLD) -eq 0 ]; then
printf " $WORLD"
start $WORLD
fi
done
printf ".\n"
;;
stop|force-stop)
# Figure out which worlds to stop.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Stop each world requested, if running.
printf "Stopping Minecraft Server:"
for WORLD in $WORLDS; do
# Try to stop the world cleanly.
if [ $(serverRunning $WORLD) -eq 1 ]; then
printf " $WORLD"
if [ $(printf "%d" $(queryStatus $WORLD | cut -f6)) -gt 0 ]; then
sendCommand $WORLD "say The server admin has initiated a server shut down."
sendCommand $WORLD "say The server will shut down in 1 minute..."
sleep 60
sendCommand $WORLD "say The server is now shutting down."
fi
sendCommand $WORLD "save-all"
sendCommand $WORLD "save-off"
if [ "$1" = "force-stop" ]; then
forceStop $WORLD
else
stop $WORLD
fi
elif [ "$1" = "force-stop" ]; then
printf " $WORLD"
forceStop $WORLD
fi
done
printf ".\n"
;;
restart|reload|force-restart|force-reload)
# Figure out which worlds to restart.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Restart each world requested, start those not already
# running.
printf "Restarting Minecraft Server:"
for WORLD in $WORLDS; do
printf " $WORLD"
if [ $(serverRunning $WORLD) -eq 1 ]; then
if [ $(printf "%d" $(queryStatus $WORLD | cut -f6)) -gt 0 ]; then
sendCommand $WORLD "say The server admin has initiated a server restart."
sendCommand $WORLD "say The server will restart in 1 minute..."
sleep 60
sendCommand $WORLD "say The server is now restarting."
fi
sendCommand $WORLD "save-all"
sendCommand $WORLD "save-off"
if [ "$(echo \"$1\" | cut -d '-' -f1)" = "force" ]; then
forceStop $WORLD
else
stop $WORLD
fi
sleep 5
fi
start $WORLD
done
printf ".\n"
;;
create|new)
if [ ! -n "$2" ]; then
printf "A name for the new world must be supplied.\n"
exit 1
fi
if [ ! -n "$3" ]; then
printf "A port for the new world must be supplied.\n"
exit 1
fi
printf "Creating Minecraft world: $2"
createWorld "$2" "$3" "$4"
printf ".\n"
;;
delete|remove)
if [ ! -n "$2" ] || [ $(listContains "$2" "$ALL_WORLDS") -eq 0 ]; then
printf "World not found, unable to delete world '$2'.\n"
exit 1
fi
printf "Deleting Minecraft world: $2"
if [ $(serverRunning "$2") -eq 1 ]; then
# If the world server has users logged in, announce that the world is
# being deleted.
if [ $(printf "%d" $(queryStatus $2 | cut -f6)) -gt 0 ]; then
sendCommand "$2" "say The server admin is deleting this world."
sendCommand "$2" "say The server will be deleted in 1 minute..."
sleep 60
sendCommand "$2" "say The server is now shutting down."
fi
# Stop the world server.
stop "$2"
sleep 5
fi
# Delete the world.
deleteWorld "$2"
printf ".\n"
;;
disable)
if [ ! -n "$2" ] || [ $(listContains "$2" "$ALL_WORLDS") -eq 0 ]; then
printf "World not found, unable to disable world '$2'.\n"
exit 1
fi
printf "Disabling Minecraft world: $2"
if [ $(serverRunning "$2") -eq 1 ]; then
# If the world server has users logged in, announce that the world is
# being disabled.
if [ $(printf "%d" $(queryStatus $2 | cut -f6)) -gt 0 ]; then
sendCommand "$2" "say The server admin is disabling this world."
sendCommand "$2" "say The server will be disabled in 1 minute..."
sleep 60
sendCommand "$2" "say The server is now shutting down."
fi
# Stop the world server.
stop "$2"
sleep 5
fi
# Disable the world.
disableWorld "$2"
printf ".\n"
;;
enable)
if [ ! -n "$2" ] || [ ! -f "$DISABLED_WORLDS_LOCATION/$2/server.properties" ]; then
printf "World not found, unable to enable world '$2'.\n"
exit 1
fi
printf "Enabling Minecraft world: $2"
# Enable the world.
enableWorld "$2"
# Start the world.
start "$2"
printf ".\n"
;;
status|show)
# Figure out which worlds to show the status for.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Show the status of each world requested.
printf "Minecraft Server Status:\n"
for WORLD in $WORLDS; do
printf " $WORLD: "
worldStatus $WORLD
done
;;
sync|synchronize)
# Figure out which worlds to synchronize.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Synchronize the images for each world.
printf "Synchronizing Minecraft Server:"
for WORLD in $WORLDS; do
if [ $(serverRunning $WORLD) -eq 1 ]; then
printf " $WORLD"
sendCommand $WORLD "save-all"
if [ $ENABLE_MIRROR -eq 1 ]; then
sendCommand $WORLD "save-off"
sleep 20
syncMirrorImage $WORLD
sendCommand $WORLD "save-on"
fi
fi
done
printf ".\n"
;;
send)
# Check for the world command line argument.
if [ -n "$2" ] && [ $(listContains $2 "$ALL_WORLDS") -eq 1 ] && [ -n "$3" ]; then
WORLD=$2
shift 2
printf "Sending command to world: $WORLD - '$*'.\n"
sendCommand $WORLD "$*"
else
printf "Usage: $0 $1 <world> <command>\n"
printf " ie: $0 $1 world say Hello World!\n"
exit 1
fi
;;
screen)
# Check for the world command line argument.
if [ -n "$2" ] && [ $(listContains $2 "$ALL_WORLDS") -eq 1 ]; then
printf "About to load the screen for world: $2.\n"
printf "To exit the screen, hit Ctrl+A then type the letter d.\n"
sleep 5
displayScreen $2
else
if [ -n "$2" ]; then
printf "Minecraft world $2 not found!\n"
else
printf "Minecraft world not provided!\n"
fi
printf " Usage: $0 $1 <world>\n"
exit 1
fi
;;
watch)
# Check for the world command line argument.
if [ -n "$2" ] && [ $(listContains $2 "$ALL_WORLDS") -eq 1 ]; then
printf "Monitoring Minecraft Server: $2.\n"
watchLog $2
else
if [ -n "$2" ]; then
printf "Minecraft world $2 not found!\n"
else
printf "Minecraft world not provided!\n"
fi
printf " Usage: $0 $1 <world>\n"
exit 1
fi
;;
logrotate)
# Figure out which worlds to rotate the log.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Backup each world requested.
printf "Rotating Minecraft Server Log:"
for WORLD in $WORLDS; do
printf " $WORLD"
rotateLog $WORLD
done
printf ".\n"
;;
backup)
# Figure out which worlds to backup.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Backup each world requested.
printf "Backing up Minecraft Server:"
for WORLD in $WORLDS; do
printf " $WORLD"
if [ $(serverRunning $WORLD) -eq 1 ]; then
sendCommand $WORLD "say Backing up the world."
sendCommand $WORLD "save-all"
sendCommand $WORLD "save-off"
sleep 20
worldBackup $WORLD
sendCommand $WORLD "save-on"
sendCommand $WORLD "say Backup complete."
else
worldBackup $WORLD
fi
done
printf ".\n"
;;
update)
# Stop all of the world servers and backup the worlds.
printf "Stopping Minecraft Server:"
for WORLD in $ALL_WORLDS; do
if [ $(serverRunning $WORLD) -eq 1 ]; then
printf " $WORLD"
if [ $(printf "%d" $(queryStatus $WORLD | cut -f6)) -gt 0 ]; then
sendCommand $WORLD "say The server admin has initiated a software update."
sendCommand $WORLD "say The server will restart and update in 1 minute..."
sleep 60
sendCommand $WORLD "say The server is now restarting."
fi
sendCommand $WORLD "save-all"
sendCommand $WORLD "save-off"
stop $WORLD
fi
done
printf ".\n"
printf "Backing up Minecraft Server:"
for WORLD in $ALL_WORLDS; do
printf " $WORLD"
worldBackup $WORLD
done
printf ".\n"
printf "Updating software package:"
# Update the client software.
printf " client"
updateClientSoftware
# Update the server software.
printf " server"
updateServerSoftware
printf ".\n"
printf "Restarting Minecraft Server:"
for WORLD in $ALL_WORLDS; do
printf " $WORLD"
start $WORLD
done
printf ".\n"
;;
map|overviewer)
# Make sure that the Minecraft Overviewer software exists.
if [ ! -e "$OVERVIEWER_BIN" ]; then
printf "Mincraft Overviewer software not found.\n"
exit 1
fi
# Figure out which worlds to map.
WORLDS="$ALL_WORLDS"
if [ -n "$2" ] && [ $(listContains "$2" "$ALL_WORLDS") -eq 1 ]; then
WORLDS="$2"
elif [ -n "$2" ]; then
printf "World '$2' not recognized.\n"
printf " Usage: $0 $1 <world>\n"
exit 1
fi
# Run Minecraft Overviewer on each world requested.
printf "Running Minecraft Overviewer mapping:"
for WORLD in $WORLDS; do
printf " $WORLD"
if [ $(serverRunning $WORLD) -eq 1 ]; then
sendCommand $WORLD "say The world is about to be mapped with Minecraft Overviewer."
sendCommand $WORLD "save-all"
sendCommand $WORLD "save-off"
sleep 20
worldBackup $WORLD
overviewer $WORLD
sendCommand $WORLD "save-on"
sendCommand $WORLD "say Mapping is complete. You can access the maps at:"
sendCommand $WORLD "say $MAPS_URL/$WORLD"
else
worldBackup $WORLD
overviewer $WORLD
fi
done
printf ".\n"
;;
*)
printf "Error, in command line usage.\n"
printf "\n"
printf "$USAGE\n"
exit 1
;;
esac
exit 0