msm/msm

2078 lines
62 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/bin/bash
### BEGIN INIT INFO
# Provides: msm
# Required-Start: $local_fs $remote_fs
# Required-Stop: $local_fs $remote_fs
# Should-Start: $network
# Should-Stop: $network
# Default-Start: 2 3 4 5
# Default-Stop: 0 1 6
# Short-Description:
# Description:
### END INIT INFO
# See http://www.debian.org/doc/debian-policy/ch-opersys.html#s-sysvinit for
# more information on debain init.d scripts, which may help you understand
# this script.
### The configuration file
CONFIG="/etc/msm.conf"
### Config variables the user should need/want to change
# Jar group file which contains the download target URL
declare -r JARGROUP_TARGET="target.txt"
# Jar group directory name to download new jars to, is deleted afterwards
declare -r JARGROUP_DOWNLOAD_DIR="downloads"
# The flag which indicates the world should be put in RAM
declare -r WORLD_FLAG_INRAM="inram"
# The flag which indicates the server is active
declare -r SERVER_FLAG_ACTIVE="active"
# The start of a regex to find a log line
declare -r LOG_REGEX="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} \[.*\]"
### Script State Variables
# "true" whilst the script is counting down a delay to stop the server
declare STOP_COUNTDOWN
# "true" whilst the script is counting down a delay to restart the server
declare RESTART_COUNTDOWN
### Utility Functions
# Executes the command "$2" as user "$1"
# $1: The user to execute the command as
# $2: The command to execute
as_user() {
local user="$(whoami)"
if [ "$user" == "$1" ]; then
bash -c "$2"
else
if [ "$user" == "root" ]; then
su - $1 -s /bin/bash -c "$2"
else
echoerr "This command must be executed as the user \"$1\" or \"root\"."
exit 1
fi
fi
}
# Executes the command "$1" as SERVER_USER but returns stderr instead
as_user_stderr() {
as_user "$@" > /dev/null 2>&1
}
# Echo to stderr
echoerr() {
echo "$@" 1>&2
}
# Determines whether "$1" is a valid name for a server or jar group directory
# It must only contain upper or lower case letters, digits, dashes or
# underscores.
# It must also not be one of a list of reserved names.
is_valid_name() {
local valid="^[a-zA-Z0-9\_\-]+$"
local invalid="^server|jargroup|start|stop|restart|all$"
if [[ $1 =~ $valid ]]; then
if [[ $1 =~ $invalid ]]; then
echoerr "Invalid name \"$1\": A name may not be any of the following reserved worlds \"start\", \"stop\", \"restart\", \"server\", \"jargroup\" or \"all\"."
return 1
else
return 0
fi
else
echoerr "Invalid name \"$1\": A name may only contain letters, numbers, dashes and unscores."
return 1
fi
}
# Gets the latest jar from a jar group, based upon the date and time encoded
# in the file name.
# $1: The directory to search
get_latest_file() {
local best_time=0
local best_file=""
while IFS= read -r -d $'\0' file; do
# Remove the path, leaving just the file name
local date_time=$(basename "$file" | awk -F '-' '{print $1 "-" $2 "-" $3 " " $4 ":" $5 ":" $6}')
# Get the time in seconds since 1970 from file name
local seconds=$(date -d "$date_time" "+%s" 2> /dev/null)
# If that is newer than the current best, override variables
if [[ $seconds > $best_time ]]; then
best_time=$seconds
best_file=$file
fi
done < <(find "$1" -maxdepth 1 -type f -print0)
echo $best_file
}
# Returns the current time as a UNIX timestamp (in seconds since 1970)
now() {
date +%s
}
### Log Utility Functions
# Gets the UNIX timestamp for a server log line
# $1: A server log line
# returns: Time in seconds since 1970-01-01 00:00:00 UTC
log_line_get_time() {
time_string=$(echo $1 | awk '{print $1 " " $2}')
date -d "$time_string" "+%s" 2> /dev/null
}
### World Utility Functions
# Moves a world to RAM
# $1: the ID of the world to move
world_to_ram() {
if [ ! -z $RAMDISK_STORAGE_PATH ]; then
as_user ${server_user_name[${world_server_id[$1]}]} "mkdir -p ${world_ramdisk_path[$1]} && rsync -rt --exclude '$WORLD_FLAG_INRAM' \"${world_path[$1]}/\" \"${world_ramdisk_path[$1]}\""
fi
}
# Moves a world in RAM to disk
# $1: the ID of the world to move
world_to_disk() {
as_user ${server_user_name[${world_server_id[$1]}]} "rsync -rt --exclude '$WORLD_FLAG_INRAM' \"${world_ramdisk_path[$1]}/\" \"${world_path[$1]}\""
}
# Toggles a worlds ramdisk state
# $1: The ID of the world
world_toggle_ramdisk_state() {
if [ -e ${world_flag_inram[$1]} ]; then
echo -n "Removing RAM flag from world \"${world_name[$1]}\"... "
as_user ${server_user_name[${world_server_id[$1]}]} "rm -f \"${world_flag_inram[$1]}\""
echo "Done."
echo -n "Removing world \"${world_name[$1]}\" from RAM... "
as_user ${server_user_name[${world_server_id[$1]}]} "rm -r \"${world_ramdisk_path[$1]}\""
echo "Done."
else
echo -n "Adding RAM flag to world \"${world_name[$1]}\"... "
as_user ${server_user_name[${world_server_id[$1]}]} "touch \"${world_flag_inram[$1]}\""
echo "Done."
echo -n "Copying world to RAM... "
world_to_ram $1
echo "Done."
fi
echo "Changes will only take effect after server is restarted."
}
# Backs up a world
# $1: The ID of the world
world_backup() {
echo -n "Backing up world \"${world_name[$1]}\"... "
file_name="$(date "+%F-%H-%M-%S").zip"
local server_id=${world_server_id[$1]}
as_user ${server_user_name[$server_id]} "mkdir -p \"${world_backup_path[$1]}\" && cd \"${server_world_storage[$server_id]}\" && zip -rq \"${world_backup_path[$1]}/${file_name}\" \"${world_name[$1]}\""
echo "Done."
}
### Server Utility Functions
# Returns the ID for a server.
# An ID is given to a server when loaded into memory, and can be used to lookup
# config information for that server
# $1: The name of the server
server_get_id() {
local i=0
while [[ $i -lt $num_servers ]]; do
if [[ "${server_name[$i]}" == "$1" ]]; then
echo "$i"
return 0
fi
i=$(( $i + 1 ))
done
return 1
}
# Returns the ID of a server's world.
# $1: The ID of the server
# $2: The name of the world
server_world_get_id() {
local i=${server_world_offset[$1]}
local max=$(( $i + ${server_num_worlds[$1]} ))
# For each of the servers worlds:
while [[ $i -lt $max ]]; do
if [[ "${world_name[$i]}" == "$2" ]]; then
echo $i
return 0
fi
i=$(( $i + 1 ))
done
return 1
}
# Returns 0 if the server $1 is running and 1 if not
# $1: The ID of the server
server_is_running() {
if ps ax | grep -v grep | grep "${server_screen_name[$1]} ${server_invocation[$1]}" > /dev/null
then
return 0
else
return 1
fi
}
# Creates symbolic links in the server directory (SERVER_STORAGE_PATH) for each
# of the Minecraft worlds located in the world storage directory.
# $1: The id of the server for which links should be ensured
server_ensure_links() {
echo -n "Maintaining world symbolic links... "
local i=${server_world_offset[$1]}
local max=$(( $i + ${server_num_worlds[$1]} ))
local output="false"
while [[ $i -lt $max ]]; do
# -L checks for the path being a link rather than a file
# ! -a, since it is within double square brackets means: the negation of
# the existence of the file. In other words: true if does not exist
if [[ -L "${world_link[$i]}" || ! -a "${world_link[$i]}" ]]; then
# If there is a symbolic link in the server direcotry to this world,
# or there is not a directory in the server directory containing this world.
# Get the original file path the symbolic link is pointing to
# If there is no link, link_target will contain nothing
link_target=$(readlink "${world_link[$i]}")
if ${world_inram[$i]}; then
# If this world is marked as loaded into RAM
if [ "${link_target}" != "${world_ramdisk_path[$i]}" ]; then
# If the symbolic link does not point to the RAM version of the world
# Remove the symbolic link if it exists
as_user ${server_user_name[$1]} "rm -f \"${world_link[$i]}\""
# Create a new symbolic link pointing to the RAM version of the world
as_user ${server_user_name[$1]} "ln -s \"${world_ramdisk_path[$i]}\" \"${world_link[$i]}\""
fi
else
# Otherwise the world is not loaded into RAM, and is just on disk
if [ "${link_target}" != "${world_path[$i]}" ]; then
# If the symbolic link does not point to the disk version of the world
# Remove the symbolic link if it exists
as_user ${server_user_name[$1]} "rm -f \"${world_link[$i]}\""
# Create a new symbolic link pointing to the disk version of the world
as_user ${server_user_name[$1]} "ln -s \"${world_path[$i]}\" \"${world_link[$i]}\""
fi
fi
else
echoerr -en "\n Error: Could not create link for world \"${world_name[$i]}\". The file \"${world_link[$i]}\" already exists, and should not be overwritten automatically. Either remove this file, or rename \"${world_name[$i]}\"."
output="true"
fi
i=$(( $i + 1 ))
done
if [[ $output == "true" ]]; then
echo -e "\nDone."
else
echo "Done."
fi
}
# Moves a servers worlds into RAM
# $1: The ID of the server
server_worlds_to_ram() {
# Only proceed if there is a ramdisk path set in config
if [ ! -z $RAMDISK_STORAGE_PATH ]; then
echo -n "Synchronising flagged worlds on disk to RAM... "
local i=${server_world_offset[$1]}
local max=$(( $i + ${server_num_worlds[$1]} ))
# For each of the servers worlds:
while [[ $i -lt $max ]]; do
if ${world_inram[$i]} && [ -L ${world_link[$i]} ]; then
world_to_ram $i
fi
i=$(( $i + 1 ))
done
echo "Done."
fi
}
# Moves a servers "in RAM" worlds back to disk
# $1: The ID of the server
server_worlds_to_disk() {
if [ ! -z $RAMDISK_STORAGE_PATH ]; then
echo -n "Synchronising worlds in RAM to disk... "
local i=${server_world_offset[$1]}
local max=$(( $i + ${server_num_worlds[$1]} ))
# For each of the servers worlds:
while [[ $i -lt $max ]]; do
if [ -e ${world_ramdisk_path[$i]} ]; then
world_to_disk $i
fi
i=$(( $i + 1 ))
done
echo "Done."
fi
}
# Watches a server's log for a specific line
# $1: The ID for the server
# $2: A UNIX timestamp (seconds since 1970) which the $3 line must be after
# $3->: The line or lines in the log to wait for
# returns: When the line is found
server_log_get_line() {
# Make sure there is a server log to check
as_user ${server_user_name[$1]} "touch ${server_log[$1]}"
while read line; do
line_time=$(log_line_get_time "$line")
# If the entry is old enough
if [[ $line_time -ge $2 ]]; then
for search_line in "${@:3}"; do
local regex="${LOG_REGEX} ${search_line}"
# and matches the regular expression
if [[ $line =~ $regex ]]; then
echo $line
return 0
fi
done
fi
done < <(as_user ${server_user_name[$1]} "tail --follow --lines=100 --sleep-interval=0.1 ${server_log[$1]}")
}
# The same as server_log_get_line, but does not print the line to stdout
# when found.
server_log_wait_for_line() {
server_log_get_line "$@" > /dev/null
}
# Sends as string to a server for execution
# $1: The ID of the server
# $2: The line of text to enter into the server console
server_eval() {
as_user ${server_user_name[$1]} "screen -p 0 -S ${server_screen_name[$1]} -X eval 'stuff \"$2\"\015'"
}
# The same as server_eval, but also waits for a log entry before returning
# $1: The ID of the server
# $2: A line of text to enter into the server console
# $3->: The line or lines of text in the log to wait for
# stdout: The full entry found in the logs
server_eval_and_get_line() {
time_now=$(now)
server_eval $1 "$2"
server_log_get_line $1 "$time_now" "${@:3}"
}
# The same as server_eval_and_get_line, but does not print anything to stdout
server_eval_and_wait() {
server_eval_and_get_line "$@" > /dev/null
}
# Gets the process ID for a server if running, otherwise it outputs nothing
# $1: The ID of the server
server_pid() {
ps ax | grep -v grep | grep "${server_screen_name[$1]} ${server_invocation[$1]}" | awk '{print $1}'
}
# Waits for a server to stop by polling 10 times a second
# This approach is fairyl intensive, so only use when you are expecting the
# server to stop soon
# $1: The ID of the server to wait for
server_wait_for_stop() {
local pid=$(server_pid $1)
# if the process is still running, wait for it to stop
if [ ! -z $pid ]; then
while ps -p $pid > /dev/null; do
sleep 0.1
done
fi
}
# Sets a server's active/inactive state
# $1: The ID of the server
# $2: A string containing "active" or "inactive"
server_set_active() {
case "$2" in
active)
as_user ${server_user_name[$1]} "touch ${server_flag_active[$1]}"
server_active[$1]="true"
;;
inactive)
as_user ${server_user_name[$1]} "rm -f ${server_flag_active[$1]}"
server_active[$1]="false"
;;
*)
return 1
;;
esac
}
### Jar Group Functions
# Lists the jar files grouped by jar groups.
jargroup_list() {
for group in $(ls -1 "$JAR_STORAGE_PATH"); do
echo "${group}:"
for jar in $(ls -1r $JAR_STORAGE_PATH/$group); do
if [[ $jar =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}- ]]; then
echo " $jar"
fi
done
done
}
# Creates a new jargroup
# $1: The name for the jargroup
jargroup_create() {
if is_valid_name "$1"; then
if [[ ! -e "$JAR_STORAGE_PATH/$1" ]]; then
printf "Creating jar group... "
local error=$(as_user_stderr "$USERNAME" "mkdir -p '$JAR_STORAGE_PATH/$1'")
if [[ "$error" != "" ]]; then
echo "Failed."
echo "Reason: $error"
return 1
fi
error=$(as_user "$USERNAME" "echo \"$2\" > '$JAR_STORAGE_PATH/$1/$JARGROUP_TARGET'")
if [[ "$error" != "" ]]; then
echo "Failed."
echo "Reason: $error"
return 1
fi
echo "Done."
# Download the latest version now
jargroup_getlatest "$1"
else
echo "A jar group with that name already exists."
return 1
fi
else
return 1
fi
}
# Downloads the latest version for a jargroup, using the target URL for that
# group. Saves the download with the date and time encoded in the start of the
# file name, in the jar group directory in question. Removes the file if there
# is no difference between it and the current version.
# $1: The jargroup name to download the latest version for
jargroup_getlatest() {
if is_valid_name "$1"; then
if [[ -e "$JAR_STORAGE_PATH/$1" ]]; then
if [[ -e "$JAR_STORAGE_PATH/$1/$JARGROUP_TARGET" ]]; then
printf "Downloading latest version... "
# Try and make
local error=$(as_user_stderr "$USERNAME" "mkdir -p '$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR'")
if [[ "$error" != "" ]]; then
echo "Failed."
echo "Reason: $error"
return 1
fi
as_user "$USERNAME" "wget --quiet --trust-server-names --no-check-certificate --input-file='$JAR_STORAGE_PATH/$1/$JARGROUP_TARGET' --directory-prefix='$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR'"
echo "Done."
local num_files=$(as_user "$USERNAME" "ls -1 '$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR' | wc -l")
if [[ $num_files == 1 ]]; then
# There was 1 file downloaded
local file_name=$(ls -1 "$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR")
local new_name="$(date +%F-%H-%M-%S)-$file_name"
local most_recent_jar=$(get_latest_file "$JAR_STORAGE_PATH/$1")
if [[ ! -e "$most_recent_jar" ]] || ! diff "$most_recent_jar" "$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR/$file_name" > /dev/null; then
# There is not a previous version to do a comparison against, or
# The previous version is different:
# Add it to the group
[[ -e "$most_recent_jar" ]]
local was_previous=$?
as_user "$USERNAME" "mv '$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR/$file_name' '$JAR_STORAGE_PATH/$1/$new_name'"
if [[ ! -z $most_recent_jar ]]; then
echo "Downloaded version was different to previous latest. Saved as \"$JAR_STORAGE_PATH/$1/$new_name\"."
else
echo "Saved as \"$JAR_STORAGE_PATH/$1/$new_name\"."
fi
else
echo "Existing version \"$JAR_STORAGE_PATH/$1/$new_name\" was already up to date."
fi
elif [[ $num_files == 0 ]]; then
# No file was downloaded
echo "Failed. No files were downloaded."
else
# Multiple files were
echo "Error. URL downloads multiple files."
fi
# Clean up the temp download folder
as_user "$USERNAME" "rm -fr '$JAR_STORAGE_PATH/$1/$JARGROUP_DOWNLOAD_DIR'"
else
echo "Target URL not found, use $0 jargroup seturl <download-url>"
return 1
fi
else
echo "There is no jar group with the name \"$1\"."
return 1
fi
else
return 1
fi
}
# Deletes an existing jargroup
# $1: The name of the existing jargroup
jargroup_delete() {
if is_valid_name "$1"; then
if [[ -e "$JAR_STORAGE_PATH/$1" ]]; then
printf "Are you sure you want to delete this jar group [y/N]: "
read answer
if [[ $answer =~ ^y|Y|yes$ ]]; then
as_user "$USERNAME" "rm -rf $JAR_STORAGE_PATH/$1"
echo "Jar group deleted."
else
echo "Jar group was NOT deleted."
fi
else
echo "There is no jar group with the name \"$1\"."
return 1
fi
else
return 1
fi
}
# Renames an existing jargroup
# $1: The name of the existing jargroup
# $2: The new name
jargroup_rename() {
if is_valid_name "$1"; then
if [[ -e "$JAR_STORAGE_PATH/$1" ]]; then
# If the jar group name is valid,
# and there is no other jar group with the name $1
if is_valid_name "$2"; then
if [[ -e "$JAR_STORAGE_PATH/$2" ]]; then
echo "Could not be renamed, there is already a jar group with the name \"$2\"."
return 1
else
# TODO: Update any symbolic links which point to a jar in this directory
as_user "$USERNAME" "mv '$JAR_STORAGE_PATH/$1' '$JAR_STORAGE_PATH/$2'"
echo "Renamed jar group \"$1\" to \"$2\"."
fi
else
return 1
fi
else
echo "There is no jar group with the name \"$1\"."
return 1
fi
else
return 1
fi
}
### Server Functions
# Echos a list of servers in the SERVER_STORAGE_PATH
server_list() {
echo "Servers:"
for server in "$SERVER_STORAGE_PATH/*"; do
echo " $(basename $server)"
done
}
# Creates a new server
# $1: The server name to create
server_create() {
if is_valid_name "$1"; then
if [[ -e "$SERVER_STORAGE_PATH/$1" ]]; then
echo "A server with that name already exists."
return 1
else
printf "Creating server directory... "
as_user "$USERNAME" "mkdir -p '$SERVER_STORAGE_PATH/$1'"
as_user "$USERNAME" "touch '$SERVER_STORAGE_PATH/$1/$DEFAULT_WHITELIST'"
as_user "$USERNAME" "touch '$SERVER_STORAGE_PATH/$1/$DEFAULT_BANNED_IPS'"
as_user "$USERNAME" "touch '$SERVER_STORAGE_PATH/$1/$DEFAULT_BANNED_PLAYERS'"
as_user "$USERNAME" "touch '$SERVER_STORAGE_PATH/$1/$DEFAULT_OPS'"
as_user "$USERNAME" "touch '$SERVER_STORAGE_PATH/$1/$DEFAULT_PROPERTIES'"
echo "Done."
# Now that the new server has been created, we must call init again
# to ensure it is recognised.
init
server_set_jar $(server_get_id "$1") "minecraft"
fi
else
return 1
fi
}
# Deletes an existing server
# $2: The server name to delete
server_delete() {
if is_valid_name "$1"; then
if [[ -e "$SERVER_STORAGE_PATH/$1" ]]; then
printf "Are you sure you want to delete this server and its worlds (note: backups are preserved) [y/N]: "
read answer
if [[ $answer =~ ^y|Y|yes$ ]]; then
# TODO: stop the server if running first
as_user "$USERNAME" "rm -rf '$SERVER_STORAGE_PATH/$1'"
echo "Server deleted."
else
echo "Server was NOT deleted."
fi
else
echo "There is no server with the name \"$1\"."
return 1
fi
else
return 1
fi
}
# Renames an existing server
# $1: The server name to change
# $2: The new name for the server
server_rename() {
if is_valid_name "$1"; then
if [[ -e "$SERVER_STORAGE_PATH/$1" ]]; then
# If the server name is valid and exists
# TODO: Check that the server is not running first, return if it is
if is_valid_name "$2"; then
# If the server name is valid
if [[ -e "$SERVER_STORAGE_PATH/$2" ]]; then
# and there is not already a server with the name $2
echo "Could not be renamed, there is already a server with the name \"$2\"."
return 1
else
as_user "$USERNAME" "mv '$SERVER_STORAGE_PATH/$1' '$SERVER_STORAGE_PATH/$2'"
echo "Renamed server \"$1\" to \"$2\"."
fi
else
return 1
fi
else
echo "There is no server with the name \"$1\"."
return 1
fi
else
return 1
fi
}
# Starts a single server
# $1: The ID of the server
server_start() {
if server_is_running $1; then
echo "Server \"${server_name[$1]}\" is already running!"
else
server_ensure_links $1
server_worlds_to_ram $1
local time_now=$(now)
printf "Starting server... "
as_user ${server_user_name[$1]} "cd ${server_path[$1]} && screen -dmS ${server_screen_name[$1]} ${server_invocation[$1]}"
server_log_wait_for_line $1 "$time_now" "${server_confirm_start[$1]}"
echo "Done."
fi
}
# Sends the "save-all" command to a server
# $1: The ID of the server
server_save_all() {
if server_is_running $1; then
echo -n "Forcing save... "
# Send the "save-all" command and wait for it to finish
server_eval_and_wait $1 "save-all" "${server_confirm_save_all[$1]}"
echo "Done."
else
echo "Server \"${server_name[$1]}\" is not running."
fi
}
# Sends the "save-off" command to a server
# $1: The ID of the server
server_save_off() {
if server_is_running $1; then
echo -n "Disabling level saving... "
# Send the "save-off" command and wait for it to finish
server_eval_and_wait $1 "save-off" "${server_confirm_save_off[$1]}"
echo "Done."
# Writes any in-memory data manged by the kernel to disk
sync
else
echo "Server \"${server_name[$1]}\" is not running."
fi
}
# Sends the "save-on" command to a server
# $1: The ID of the server
server_save_on() {
if server_is_running $1; then
echo -n "Enabling level saving... "
# Send the "save-on" command and wait for it to finish
server_eval_and_wait $1 "save-on" "${server_confirm_save_on[$1]}"
echo "Done."
else
echo "Server \"${server_name[$1]}\" is not running."
fi
}
# Stops a single server after a delay
# $1: The ID of the server
server_stop() {
if server_is_running $1; then
# Change the state of the script
STOP_COUNTDOWN[$id]="true"
server_eval $id "say ${server_stop_message[$id]}"
echo "Issued the warning \"${server_stop_message[$id]}\" to players."
echo -n "Shutting down... "
for ((i=${server_stop_delay[$id]}; i>0; i--)); do
tput sc # Save cursor position
echo -n "in $i seconds."
sleep 1
tput rc # Restore cursor to position of last `sc'
tput el # Clear to end of line
done
echo -e "Now."
server_stop_now $1
else
echo "Server \"${server_name[$1]}\" is not running."
fi
}
# Stops a single server right now
# $1: The ID of the server
server_stop_now() {
if server_is_running $1; then
server_save_all $1
echo -n "Stopping the server... "
server_eval $1 "stop"
STOP_COUNTDOWN[$1]="false"
RESTART_COUNTDOWN[$1]="false"
server_wait_for_stop $1
echo "Done."
# Synchronise all worlds in RAM to disk
server_worlds_to_disk $1
else
echo "Server \"${server_name[$1]}\" is not running."
fi
}
# Restarts a single server after a delay
# $1: The ID of the server
server_restart() {
# Restarts the server if it is already running
if server_is_running $1; then
# Change the state of the script
RESTART_COUNTDOWN[$id]="true"
server_eval $id "say ${server_restart_message[$id]}"
echo "Issued the warning \"${server_restart_message[$id]}\" to players."
echo -n "Restarting... "
for ((i=${server_stop_delay[$id]}; i>0; i--)); do
tput sc # Save cursor position
echo -n "in $i seconds."
sleep 1
tput rc # Restore cursor to position of last `sc'
tput el # Clear to end of line
done
echo -e "Now."
server_stop_now $1
fi
server_start $1
}
# Restarts a single server right away
# $1: The ID of the server
server_restart_now() {
# Restarts the server if it is already running
if server_is_running $1; then
server_stop_now $1
fi
server_start $1
}
# List the worlds available for a server
# $1: The ID of the server
server_worlds_list() {
local i=${server_world_offset[$1]}
local max=$(( $i + ${server_num_worlds[$1]} ))
# For each of the servers worlds:
while [[ $i -lt $max ]]; do
if ${world_inram[$i]}; then
echo "[RAM] ${world_name[$i]}"
else
echo "[DSK] ${world_name[$i]}"
fi
i=$(( $i + 1 ))
done
}
# Backs up the worlds for a server
# $1: The ID of the server
server_worlds_backup() {
local i=${server_world_offset[$1]}
local max=$(( $i + ${server_num_worlds[$1]} ))
# For each of the servers worlds:
while [[ $i -lt $max ]]; do
world_backup $i
i=$(( $i + 1 ))
done
}
# Moves a servers log into another file, leaving the original log file empty
# $1: The ID of the server
server_log_roll() {
# Moves and Gzips the logfile, a big log file slows down the
# server A LOT (what was notch thinking?)
printf "Rolling server logs... "
if [ -e "${server_log[$1]}" ]; then
file_name="${server_name[$1]}-$(date +%F-%H-%M-%S).log"
as_user ${server_user_name[$1]} "mkdir -p \"${server_log_archive_path[$1]}\" && cp \"${server_log[$1]}\" \"${server_log_archive_path[$1]}/${file_name}\" && gzip \"${server_log_archive_path[$1]}/${file_name}\""
if [ -e "${server_log_archive_path[$1]}/${file_name}.gz" ]; then
as_user ${server_user_name[$1]} "cp \"/dev/null\" ${server_log[$1]}"
as_user ${server_user_name[$1]} "echo \"Previous logs can be found at \\\"${server_log_archive_path[$1]}\\\"\" > ${server_log[$1]}"
else
echoerr "Failed."
return 1
fi
fi
echo "Done."
}
# Backups a server's directory
# $1: The ID of the server
server_backup() {
echo -n "Backing up the entire server directory... "
zip_flags="-rq"
# Add the "y" flag if symbolic links should not be followed
if [ "${server_complete_backup_follow_symlinks[$1]}" != "true" ]; then
zip_flags="${zip_flags}y"
fi
# Zip up the server directory
file_name="${server_backup_path[$1]}/$(date "+%F-%H-%M-%S").zip"
as_user ${server_user_name[$1]} "mkdir -p \"${server_backup_path[$1]}\" && cd \"$SERVER_STORAGE_PATH\" && zip ${zip_flags} \"${file_name}\" \"${server_name[$1]}\""
echo "Done."
}
# Sets a server's jar file
# $1: The ID of the server
# $2: The name of the jar group
# $3: Optionally, a specific jar to use.
server_set_jar() {
if [ -e "$JAR_STORAGE_PATH/$3" ]; then
if [ -z "$3" ]; then
# If a specific jar file is not mentioned
# Download the latest version
jargroup_getlatest "$2"
local jar=$(get_latest_file "$JAR_STORAGE_PATH/$2")
else
# If a specific jar IS mentioned use that
local jar="$JAR_STORAGE_PATH/$2/$3"
if [[ ! -e "$jar" ]]; then
echo "There is no jar named \"$3\" in jargroup \"$2\"."
return 1
fi
fi
if [[ ! -z "$jar" ]]; then
as_user "${server_user[$1]}" "ln -sf $jar ${server_jar[$1]}"
echo "Server \"${server_name[$1]}\" is now using \"$jar\"."
fi
else
echo "There is no jargorup named \"$2\"."
fi
}
# Lists the players currently connected to a server
# $1: The ID of the server
server_connected() {
if server_is_running $1; then
local line=$(server_eval_and_get_line $1 "list" "Connected players:")
# Cuts the start off the line, and the last three (invisible)
# characters from the end.
local players=${line:46:(-3)}
if [ -z "$players" ]; then
echo "No players are connected."
else
echo "$players"
fi
else
echo "Server \"${server_name[$id]}\" is not running. No users are connected."
fi
}
### Manager Functions
# Stops all running servers after a servers specified delay
# $1: String containing "stop" or "restart". Represents whether the stop is
# with a mind to stop the server, or just to restart it. And effects
# the message issued to players on a server.
manager_stop_all_servers() {
# An array of true/false for each server
local was_running
# False if no servers were running at all
local any_running="false"
# For all running servers issue the stop warning
local max_countdown=0
for ((server=0; server<${num_servers}; server++)); do
if server_is_running $server; then
any_running="true"
was_running[$server]="true"
STOP_COUNTDOWN[$server]="true"
if [[ ${server_stop_delay[$i]} > $max_countdown ]]; then
max_countdown=${server_stop_delay[$server]}
fi
# Send a warning message to the server
case "$1" in
stop) server_eval $server "say ${server_stop_message[$server]}";;
restart) server_eval $server "say ${server_restart_message[$server]}";;
esac
# Send message to stdout
echo "Server \"${server_name[$server]}\" was running, now stopping:"
case "$1" in
stop) echo " Issued the warning \"${server_stop_message[$server]}\" to players.";;
restart) echo " Issued the warning \"${server_restart_message[$server]}\" to players.";;
esac
case ${server_stop_delay[$server]} in
0) echo " Stopping without delay.";;
1) echo " Stopping after 1 second.";;
*) echo " Stopping after ${server_stop_delay[$server]} seconds.";;
esac
else
echo "Server \"${server_name[$server]}\" was NOT running."
was_running[$server]="false"
fi
done
if "$any_running"; then
# Wait for the maximum possible delay, stopping servers
# at the correct times
echo -n "All servers will have been issued the stop command... "
for ((tick=${max_countdown}; tick>=0; tick--)); do
tput sc # Save cursor position
if [[ $tick -le 1 ]]; then
echo -n "in $tick second."
else
echo -n "in $tick seconds."
fi
# Each second check all server, to see if its their time to
# stop. If so issue the stop command, and don't hang.
for ((server=0; server<${num_servers}; server++)); do
if server_is_running $server; then
stop_tick=$(( ${max_countdown} - ${server_stop_delay[$server]} ))
if [[ $stop_tick == $tick ]]; then
server_eval $server "stop"
STOP_COUNTDOWN[$server]="false"
fi
fi
done
if [[ $tick > 0 ]]; then
sleep 1
fi
tput rc # Restore cursor to position of last `sc'
tput el # Clear to end of line
done
# Start a new line
echo "Now."
# Finally check all servers have stopped
for ((server=0; server<${num_servers}; server++)); do
if "${was_running[$server]}"; then
echo -n "Ensuring server \"${server_name[$server]}\" has stopped... "
server_wait_for_stop $server
echo "Done."
fi
done
else
echo "No servers were running."
fi
}
# Stops all running servers without delay
manager_stop_all_servers_now() {
# An array of true/false for each server
local was_running
# False if no servers were running at all
local any_running="false"
# Stop all servers at the same time
for ((server=0; server<${num_servers}; server++)); do
if server_is_running $server; then
was_running[$server]="true"
any_running="true"
echo "Server \"${server_name[$server]}\" was running, now stopping."
server_eval $server "stop"
else
echo "Server \"${server_name[$server]}\" was NOT running."
was_running[$server]="false"
fi
done
if $any_running; then
# Ensure all the servers have stopped
for ((server=0; server<${num_servers}; server++)); do
if ${was_running[$server]}; then
echo -n "Ensuring server \"${server_name[$server]}\" has stopped... "
server_wait_for_stop $server
echo "Done."
fi
done
else
echo "No servers were running."
fi
}
### Main Functions
init() {
# These defaults can be overriden in the file specified by $CONFIG
USERNAME="minecraft"
SERVER_STORAGE_PATH="/opt/msm/servers"
JAR_STORAGE_PATH="/opt/msm/jars"
RAMDISK_STORAGE_PATH="/dev/shm/msm"
WORLD_ARCHIVE_PATH="/opt/msm/archives/worlds"
LOG_ARCHIVE_PATH="/opt/msm/archives/logs"
BACKUP_ARCHIVE_PATH="/opt/msm/archives/backups"
DEFAULT_SERVER_CONF="server.conf"
DEFAULT_SERVER_USER="minecraft"
DEFAULT_SCREEN_NAME="msm-{SERVER_NAME}"
DEFAULT_WORLD_STORAGE_PATH="worldstorage"
DEFAULT_COMPLETE_BACKUP_FOLLOW_SYMLINKS="false"
DEFAULT_LOG="server.log"
DEFAULT_PROPERTIES="server.properties"
DEFAULT_WHITELIST="white-list.txt"
DEFAULT_BANNED_PLAYERS="banned-players.txt"
DEFAULT_BANNED_IPS="banned-ips.txt"
DEFAULT_OPS="ops.txt"
DEFAULT_JAR="server.jar"
DEFAULT_RAM="1024"
DEFAULT_INVOCATION="java -Xms{RAM}M -Xmx{RAM}M -XX:+UseConcMarkSweepGC -XX:+CMSIncrementalPacing -XX:+AggressiveOpts -jar {JAR} nogui"
DEFAULT_STOP_DELAY=10
DEFAULT_RESTART_DELAY=10
DEFAULT_STOP_MESSAGE="SERVER SHUTTING DOWN IN {DELAY} SECONDS!"
DEFAULT_STOP_ABORT="Server shut down aborted."
DEFAULT_RESTART_MESSAGE="SERVER REBOOT IN {DELAY} SECONDS!"
DEFAULT_RESTART_ABORT="Server reboot aborted."
DEFAULT_WORLD_BACKUP_STARTED="Backing up world."
DEFAULT_WORLD_BACKUP_FINISHED="Backup complete."
DEFAULT_COMPLETE_BACKUP_STARTED="Backing up entire server."
DEFAULT_COMPLETE_BACKUP_FINISHED="Backup complete."
DEFAULT_CONFIRM_SAVE_ON="CONSOLE: Enabling level saving.."
DEFAULT_CONFIRM_SAVE_OFF="CONSOLE: Disabling level saving.."
DEFAULT_CONFIRM_SAVE_ALL="CONSOLE: Save complete."
DEFAULT_CONFIRM_START="Done"
DEFAULT_CONFIRM_KICK="CONSOLE: Kicking "
DEFAULT_CONFIRM_KICK_FAIL="Can't find user "
DEFAULT_CONFIRM_TIME_SET="CONSOLE: Set time to"
DEFAULT_CONFIRM_TIME_SET_FAIL="Unable to convert time value"
DEFAULT_CONFIRM_TIME_ADD="CONSOLE: Added .+ to time"
DEFAULT_CONFIRM_TIME_ADD_FAIL="Unable to convert time value"
DEFAULT_CONFIRM_TOGGLEDOWNFALL="CONSOLE: Toggling downfall on|off for world"
DEFAULT_CONFIRM_TOGGLEDOWNFALL_FAIL=".\[31m;1mNo world exists with the name"
DEFAULT_CONFIRM_GAMEMODE="CONSOLE: Setting .+ to game mode (1|0)"
DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_USER="Can't find user .+"
DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_CHANGE=".+ already has game mode (1|0)"
# Sourcing $CONFIG will override the previous defaults, with new values
source $CONFIG
local i=0
local j=0
for server in $(ls -1 "$SERVER_STORAGE_PATH"); do
server_name[$i]="$server"
server_path[$i]="$SERVER_STORAGE_PATH/$server"
server_conf[$i]="${server_path[$i]}/$DEFAULT_SERVER_CONF"
server_flag_active[$i]="${server_path[$i]}/$SERVER_FLAG_ACTIVE"
server_backup_path[$i]="$BACKUP_ARCHIVE_PATH/${server_name[$i]}"
server_log_archive_path[$i]="$LOG_ARCHIVE_PATH/${server_name[$i]}"
if [[ -e "${server_flag_active[$i]}" ]]; then
server_active[$i]="true"
else
server_active[$i]="false"
fi
# Setup defaults
# Note: screen_name will at this stage have the {SERVER_NAME} tag in it
# which needs to be replaced.
# Invocation may also the {RAM} and {JAR} tags.
server_user_name[$i]="$DEFAULT_SERVER_USER"
server_screen_name[$i]="${DEFAULT_SCREEN_NAME//\{SERVER_NAME\}/${server_name[$i]}}" # Replace tags now, they cannot change
server_world_storage[$i]="${server_path[$i]}/$DEFAULT_WORLD_STORAGE_PATH"
server_log[$i]="${server_path[$i]}/$DEFAULT_LOG"
server_properties[$i]="${server_path[$i]}/$DEFAULT_PROPERTIES"
server_whitelist[$i]="${server_path[$i]}/$DEFAULT_WHITELIST"
server_banned_players[$i]="${server_path[$i]}/$DEFAULT_BANNED_PLAYERS"
server_banned_ips[$i]="${server_path[$i]}/$DEFAULT_BANNED_IPS"
server_ops[$i]="${server_path[$i]}/$DEFAULT_OPS"
server_jar[$i]="${server_path[$i]}/$DEFAULT_JAR"
server_ram[$i]="$DEFAULT_RAM"
server_invocation[$i]="$DEFAULT_INVOCATION" # Don't replace tags yet, they may change
server_stop_delay[$i]="$DEFAULT_STOP_DELAY"
server_restart_delay[$i]="$DEFAULT_RESTART_DELAY"
server_stop_message[$i]="$DEFAULT_STOP_MESSAGE"
server_stop_abort[$i]="$DEFAULT_STOP_ABORT"
server_restart_message[$i]="$DEFAULT_RESTART_MESSAGE"
server_restart_abort[$i]="$DEFAULT_RESTART_ABORT"
server_world_backup_started[$i]="$DEFAULT_WORLD_BACKUP_STARTED"
server_world_backup_finished[$i]="$DEFAULT_WORLD_BACKUP_FINISHED"
server_complete_backup_started[$i]="$DEFAULT_COMPLETE_BACKUP_STARTED"
server_complete_backup_finished[$i]="$DEFAULT_COMPLETE_BACKUP_FINISHED"
server_confirm_save_on[$i]="$DEFAULT_CONFIRM_SAVE_ON"
server_confirm_save_off[$i]="$DEFAULT_CONFIRM_SAVE_OFF"
server_confirm_save_all[$i]="$DEFAULT_CONFIRM_SAVE_ALL"
server_confirm_start[$i]="$DEFAULT_CONFIRM_START"
server_confirm_kick[$i]="$DEFAULT_CONFIRM_KICK"
server_confirm_kick_fail[$i]="$DEFAULT_CONFIRM_KICK_FAIL"
server_confirm_time_set[$i]="$DEFAULT_CONFIRM_TIME_SET"
server_confirm_time_set_fail[$i]="$DEFAULT_CONFIRM_TIME_SET_FAIL"
server_confirm_time_add[$i]="$DEFAULT_CONFIRM_TIME_ADD"
server_confirm_time_add_fail[$i]="$DEFAULT_CONFIRM_TIME_ADD_FAIL"
server_confirm_toggledownfall[$i]="$DEFAULT_CONFIRM_TOGGLEDOWNFALL"
server_confirm_toggledownfall_fail[$i]="$DEFAULT_CONFIRM_TOGGLEDOWNFALL_FAIL"
server_confirm_gamemode[$i]="$DEFAULT_CONFIRM_GAMEMODE"
server_confirm_gamemode_fail_no_user[$i]="$DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_USER"
server_confirm_gamemode_fail_no_change[$i]="$DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_CHANGE"
server_complete_backup_follow_symlinks[$i]="$DEFAULT_COMPLETE_BACKUP_FOLLOW_SYMLINKS"
# Load config overrides from server config file if present
if [[ -e "${server_conf[$i]}" ]]; then
local name
local value
while read line; do
# ignore comment lines
echo "$line" | grep "^#" >/dev/null 2>&1 && continue
# if not empty, set the property using declare
if [ ! -z "$line" ]; then
name="$(echo $line | awk -F '=' '{print $1}')"
value="$(echo $line | awk -F '\"' '{print $2}')"
fi
case "$name" in
SERVER_USER) server_user_name[$i]="$value";;
SCREEN_NAME) server_screen_name[$i]="$value";;
WORLD_STORAGE_DIR) server_world_storage[$i]="${server_path[$i]}/$value";;
LOG) server_log[$i]="${server_path[$i]}/$value";;
PROPERTIES) server_properties[$i]="$value";;
WHITELIST) server_whitelist[$i]="$value";;
BANNED_PLAYERS) server_banned_players[$i]="$value";;
BANNED_IPS) server_banned_ips[$i]="$value";;
OPS) server_ops[$i]="$value";;
JAR) server_jar[$i]="${server_path[$i]}/$value";;
RAM) server_ram[$i]="$value";;
INVOCATION) server_invocation[$i]="$value";;
STOP_DELAY) server_stop_delay[$i]="$value";;
RESTART_DELAY) server_restart_delay[$i]="$value";;
STOP_MESSAGE) server_stop_message[$i]="$value";;
STOP_ABORT) server_stop_abort[$i]="$value";;
RESTART_MESSAGE) server_restart_message[$i]="$value";;
RESTART_ABORT) server_restart_abort[$i]="$value";;
WORLD_BACKUP_STARTED) server_world_backup_started[$i]="$value";;
WORLD_BACKUP_FINISHED) server_world_backup_finished[$i]="$value";;
COMPLETE_BACKUP_STARTED) server_complete_backup_started[$i]="$value";;
COMPLETE_BACKUP_FINISHED) server_complete_backup_finished[$i]="$value";;
CONFIRM_SAVE_ON) server_confirm_save_on[$i]="$value";;
CONFIRM_SAVE_OFF) server_confirm_save_off[$i]="$value";;
CONFIRM_SAVE_ALL) server_confrim_save_all[$i]="$value";;
CONFIRM_START) server_confrim_start[$i]="$value";;
CONFIRM_WHITELIST_LIST) server_confirm_whitelist_list[$i]="$value";;
CONFIRM_KICK) server_confirm_kick[$i]="$value";;
CONFIRM_KICK_FAIL) server_confirm_kick_fail[$i]="$value";;
CONFIRM_TIME_SET) server_confirm_time_set[$i]="$value";;
CONFIRM_TIME_SET_FAIL) server_confirm_time_set_fail[$i]="$value";;
CONFIRM_TIME_ADD) server_confirm_time_add[$i]="$value";;
CONFIRM_TIME_ADD_FAIL) server_confirm_time_add_fail[$i]="$value";;
CONFIRM_TOGGLEDOWNFALL) server_confirm_toggledownfall[$i]="$value";;
CONFIRM_TOGGLEDOWNFALL_FAIL) server_confirm_toggledownfall_fail[$i]="$value";;
CONFIRM_GAMEMODE) server_confirm_gamemode[$i]="$value";;
CONFIRM_GAMEMODE_FAIL_NO_USER) server_confirm_gamemode_fail_no_user[$i]="$value";;
CONFIRM_GAMEMODE_FAIL_NO_CHANGE) server_confirm_gamemode_fail_no_change[$i]="$value";;
COMPLETE_BACKUP_FOLLOW_SYMLINKS) server_complete_backup_follow_symlinks[$i]="$value";;
esac
done < "${server_conf[$i]}"
fi
# Replace tags in delay messages
server_stop_message[$i]="${server_stop_message[$i]//\{DELAY\}/${server_stop_delay[$i]}}"
server_restart_message[$i]="${server_restart_message[$i]//\{DELAY\}/${server_restart_delay[$i]}}"
# Replace tags in server invocation
server_invocation[$i]="${server_invocation[$i]//\{RAM\}/${server_ram[$i]}}"
server_invocation[$i]="${server_invocation[$i]//\{JAR\}/${server_jar[$i]}}"
# Load worlds if there is a world storage directory present
server_world_offset[$i]=0
server_num_worlds[$i]=0
if [[ -e "${server_world_storage[$i]}" ]]; then
server_worlds[$i]=$(ls -1 "${server_world_storage[$i]}")
# Record the index at which worlds for this server start
server_world_offset[$i]=$j
for world in ${server_worlds[$i]}; do
world_server_id[$j]="$i"
world_name[$j]="$world"
world_path[$j]="${server_world_storage[$i]}/${world_name[$j]}"
world_link[$j]="${server_path[$i]}/${world_name[$j]}"
world_flag_inram[$j]="${world_path[$j]}/$WORLD_FLAG_INRAM"
world_backup_path[$j]="$WORLD_ARCHIVE_PATH/${server_name[$i]}/${world_name[$j]}"
if [ ! -z $RAMDISK_STORAGE_PATH ]; then
world_ramdisk_path[$j]="${RAMDISK_STORAGE_PATH}/${server_name[$i]}/${world_name[$j]}"
fi
if [[ -e "${world_path[$j]}/$WORLD_FLAG_INRAM" ]]; then
world_inram[$j]="true"
else
world_inram[$j]="false"
fi
j=$(($j+1))
done
# Record the number of worlds this server has
server_num_worlds[$i]=$(( $j - ${server_world_offset[$i]} ))
fi
i=$(($i+1))
done
num_servers=$i
num_worlds=$j
}
# Called if the script is interrupted before exiting naturally
interrupt() {
local exit_message="false"
for ((i=0; $i<$num_servers; i++)); do
if [[ "${STOP_COUNTDOWN[$i]}" == "true" ]] && server_is_running $i; then
if [[ "$exit_message" == "false" ]]; then
echo -e "\nInterrupted..."
exit_message="true"
fi
server_eval $i "say ${server_stop_abort[$i]}"
echo "Server \"${server_name[$i]}\" shutdown was aborted."
fi
if [[ "${RESTART_COUNTDOWN[$i]}" == "true" ]] && server_is_running $i; then
if [[ "$exit_message" == "false" ]]; then
echo -e "\nInterrupted..."
exit_message="true"
fi
server_eval $i "say ${server_restart_abort[$i]}"
echo "Server \"${server_name[$i]}\" restart was aborted."
fi
done
exit
}
# The main function which starts the script
main() {
# Initialise variables that represent system state
init
# Trap interrupts to the script by calling the interrupt function
trap interrupt EXIT
case "$1" in
start)
# Required start option, for debian init.d scripts
for ((server=0; server<${num_servers}; server++)); do
# Only starts active servers
if ${server_active[$server]}; then
if server_is_running $server; then
echo "[ACTIVE] Server \"${server_name[$server]}\" already started."
else
echo "[ACTIVE] Server \"${server_name[$server]}\" starting:"
server_start $server
fi
else
if server_is_running $server; then
echo "[INACTIVE] Server \"${server_name[$server]}\" already started. It should not be running! Use \"$0 ${server_name[$server]} stop\" to stop this server."
else
echo "[INACTIVE] Server \"${server_name[$server]}\" leaving stopped, as this server is inactive."
fi
fi
done
;;
stop)
if [ "$2" != "now" ]; then
# If the the now flag is not specified
manager_stop_all_servers "stop"
else
# If the now flag is specified
manager_stop_all_servers_now
fi
;;
restart)
# Required restart option, for debian init.d scripts
# Stop any running servers, now if specified
echo "Stopping servers:"
main "stop" "$2"
echo "Starting servers:"
main "start"
;;
server)
case "$2" in
list)
# Lists the existing servers
server_list
;;
create)
if [ -z "$3" ]; then
# If a server name is not provided
echo "Invalid command."
else
# Create a new server
server_create "$3"
fi
;;
delete)
if [ -z "$3" ]; then
# If a server name is not provided
echo "Invalid command."
else
# Delete an existing server, with confirmation
server_delete "$3"
fi
;;
rename)
if [ -z "$3" ] || [ -z "$4" ]; then
# If a server name is not provided
echo "Invalid command."
else
# Rename an existing server
server_rename "$3" "$4"
fi
;;
*)
# "server" is not a valid command
echo "Invalid command."
;;
esac
;;
jargroup)
case "$2" in
list)
# Lists the jars grouped by jargroup
jargroup_list
;;
create)
if [ -z "$3" ] || [ -z "$4" ]; then
echo "Invlaid command."
else
jargroup_create "$3" "$4"
fi
;;
delete)
if [ -z "$3" ]; then
echo "Invalid command."
else
jargroup_delete "$3"
fi
;;
rename)
if [ -z "$3" ] || [ -z "$4" ]; then
echo "Invalid command."
else
jargroup_rename $3 $4
fi
;;
changetarget)
if [ -z "$3" ] || [ -z "$4" ]; then
echo "Invalid command."
else
jargroup_settarget "$3" "$4"
fi
;;
getlatest)
if [ -z "$3" ]; then
echo "Invalid command."
else
jargroup_getlatest "$3"
fi
;;
*)
# "jargroup" is not a valid command
echo "Invalid command."
;;
esac
;;
help)
# Outputs a list of all commands
echo -e "Usage: $0 command:"
echo -e
echo -e "--Setup Commands------------------------------------------------"
echo -e " server list \t\t\t\t\tList servers"
echo -e " server create <name> \t\t\t\tCreates a new Minecraft server"
echo -e " server delete <name> \t\t\t\tDeletes an existing Minecraft server"
echo -e " server rename <name> <new-name> \t\tRenames an existing Minecraft server"
echo -e
echo -e "--Server Mangement Commands-------------------------------------"
echo -e " <server> start \t\t\t\tStarts a server"
echo -e " <server> stop [now] \t\t\t\tStops a server after warning players, or right now"
echo -e " <server> restart [now] \t\t\tRestarts a server after warning players, or right now"
echo -e " <server> status \t\t\t\tShow the running/stopped status of a server"
echo -e " <server> connected \t\t\t\tList a servers connected players"
echo -e " <server> worlds list \t\t\t\tLists the worlds a server has"
echo -e " <server> worlds load \t\t\t\tCreates links to worlds in storage for a server"
echo -e " <server> worlds ram <world> \t\t\tToggles a world's \"in RAM\" status"
echo -e " <server> worlds toram \t\t\tSynchronises any RAM enabled worlds to RAM a server has"
echo -e " <server> worlds todisk \t\t\tSynchronises any \"in RAM\" worlds to disk a server has"
echo -e " <server> worlds backup \t\t\tMakes a backup of all worlds a server has"
echo -e " <server> logroll \t\t\t\tMove a server log to a gziped archive, to reduce lag"
echo -e " <server> backup \t\t\t\tMakes a backup of an entire server directory"
echo -e
echo -e "--Server Pass Through Commands----------------------------------"
echo -e " <server> wl on|off <player> \t\t\tEnabled/disable server whitelist check"
echo -e " <server> wl add|remove <player> \t\tAdd/remove a player to/from a server's whitelist"
echo -e " <server> wl list \t\t\t\tList the players whitelisted for a server"
echo -e " <server> bl player add|remove <player> \tBan/pardon a player from/for a server"
echo -e " <server> bl ip add|remove <ip address> \tBan/pardon an IP address from/for a server"
echo -e " <server> bl list \t\t\t\tLists the banned players and IP address for a server"
echo -e " <server> op add|remove <player> \t\tAdd/remove operator status for a player on a server"
echo -e " <server> gm survival|creative <player> \tChange the game mode for a player on a server"
echo -e " <server> kick <player> \t\t\tForcibly disconnect a player from a server"
echo -e " <server> say <message> \t\t\tBroadcast a (pink) message to all players on a server"
echo -e " <server> time set|add <number> \t\tSet/increment time on a server (0-24000)"
echo -e " <server> toggledownfall \t\t\tToggles rain and snow on a server"
echo -e " <server> save on|off \t\t\t\tEnable/disable writing world changes to file"
echo -e " <server> save all \t\t\t\tForce the writing of all non-saved world changes to file"
echo -e " <server> cmd <command> \t\t\tSend a command string to the server and return"
echo -e " <server> cmdlog <command> \t\t\tSame as 'cmd' but shows log output afterwards (Ctrl+C to exit)"
echo -e
echo -e "--Jar Commands--------------------------------------------------"
echo -e " jargroup list \t\t\t\tList the stored jar files."
echo -e " jargroup create <name> <download-url> \tCreate a new jar group, with a URL for new downloads"
echo -e " jargroup delete <name> \t\t\tDelete a jar group"
echo -e " jargroup rename <name> <new-name> \t\tRename a jar group"
echo -e " jargroup changeurl <name> <download-url> \tChange the download URL for a jar group"
echo -e " jargroup getlatest <name> \t\t\tDownload the latest jar file for a jar group"
echo -e
echo -e "--Global Commands-----------------------------------------------"
echo -e " start \t\t\t\t\tStarts all active servers"
echo -e " stop [now]\t\t\t\t\tStops all running servers"
echo -e " restart [now]\t\t\t\t\tRestarts all active servers"
;;
"")
echo "No such command see: $0 help"
;;
*)
if [[ "$1" == "all" ]] || [[ -e "$SERVER_STORAGE_PATH/$1" ]]; then
local id=$(server_get_id "$1")
case "$2" in
start)
server_set_active $id "active"
server_start $id
;;
stop)
server_set_active $id "inactive"
if [[ $3 != "now" ]]; then
server_stop $id
else
server_stop_now $id
fi
;;
restart)
server_set_active $1 "active"
if [[ $3 != "now" ]]; then
server_restart $id
else
server_restart_now $id
fi
;;
status)
if server_is_running $id; then
echo "Server \"${server_name[$id]}\" is running."
else
echo "Server \"${server_name[$id]}\" is stopped."
fi
;;
connected)
server_connected $id
;;
worlds)
case "$3" in
list)
server_worlds_list $id
;;
load)
server_ensure_links $id
;;
ram)
if [ -z "$4" ]; then
echo "Invalid command."
else
world_id=$(server_world_get_id $id "$4")
if [ ! -z $world_id ]; then
world_toggle_ramdisk_state $world_id
else
echo "Server \"${server_name[$id]}\" has no world with that name."
fi
fi
;;
todisk)
server_save_off $id
server_save_all $id
server_worlds_to_disk $id
server_save_on $id
;;
backup)
if server_is_running $id; then
server_eval $id "say ${server_world_backup_started[$id]}"
server_save_off $id
server_save_all $id
fi
server_worlds_to_disk $id
server_worlds_backup $id
if server_is_running $id; then
server_save_on $id
server_eval $id "say ${server_world_backup_finished[$id]}"
fi
echo "Backup took $SECONDS seconds".
;;
*)
echo "Invalid command."
;;
esac
;;
logroll)
server_log_roll $id
;;
backup)
if server_is_running $id; then
server_eval $id "say ${server_complete_backup_started[$id]}"
server_save_off $id
server_save_all $id
fi
server_worlds_to_disk $id
server_backup $id
if server_is_running $id; then
server_save_on $id
server_eval $id "say ${server_complete_backup_finished[$id]}"
fi
echo "Backup took $SECONDS seconds".
;;
jar)
if [ -z "$3" ]; then
echo "Invalid command."
else
server_set_jar $id "$3"
fi
;;
whitelist|wl)
case "$3" in
on)
if server_is_running $id; then
server_eval $id "whitelist on"
echo "Whitelist enabled"
else
echo "Server \"${server_name[$id]}\" is not running."
fi
;;
off)
if server_is_running $id; then
server_eval $id "whitelist off"
echo "Whitelist disabled"
else
echo "Server \"${server_name[$id]}\" is not running."
fi
;;
add)
if [ -z "$4" ]; then
echo "Invalid command."
else
if server_is_running $id; then
server_eval $id "whitelist add $4"
echo "Added \"$4\" to the whitelist."
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
remove)
if [ -z "$4" ]; then
echo "Invalid command."
else
if server_is_running $id; then
server_eval $id "whitelist remove $4"
echo "Removed \"$4\" from the whitelist."
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
list)
local players=$(cat ${server_whitelist[$id]})
if [ -z "$players" ]; then
echo "No players are whitelisted."
else
echo "$players"
fi
;;
*)
echo "Invalid command."
;;
esac
;;
blacklist|bl)
case "$3" in
player)
if [ -z "$5" ]; then
echo "Invalid command."
else
case "$4" in
add)
for player in ${*:5}; do
server_eval $id "ban $player"
done
if [[ $# > 5 ]]; then
echo -n "Blacklisted the following players: "
echo -n "$5"
for player in ${*:6}; do
echo -n ", $player"
done
echo "."
else
echo "Blacklisted \"$5\"."
fi
;;
remove)
for player in ${*:5}; do
server_eval $id "pardon $player"
done
if [[ $# > 5 ]]; then
echo -n "Removed the following players from the blacklist: "
echo -n "$5"
for player in ${*:6}; do
echo -n ", $player"
done
echo "."
else
echo "Removed \"$5\" from the blacklist."
fi
;;
*)
echo "Invalid command."
;;
esac
fi
;;
ip)
case "$4" in
add)
for address in ${*:5}; do
server_eval $id "ban-ip $address"
done
if [[ $# > 5 ]]; then
echo -n "Blacklisted the following ip addresses: "
echo -n "$5"
for player in ${*:6}; do
echo -n ", $address"
done
echo "."
else
echo "Blacklisted \"$5\"."
fi
;;
remove)
for address in ${*:5}; do
server_eval $id "pardon-ip $address"
done
if [[ $# > 5 ]]; then
echo -n "Removed the following ip addresses from the blacklist: "
echo -n "$5"
for player in ${*:6}; do
echo -n ", $address"
done
echo "."
else
echo "Removed \"$5\" from the ip blacklist."
fi
;;
*)
echo "Invalid command."
;;
esac
;;
list)
local players=$(cat ${server_banned_players[$id]})
local ips=$(cat ${server_banned_ips[$id]})
if [[ -z "$players" && -z "$ips" ]]; then
echo "The blacklist is empty."
else
if [[ ! -z "$players" ]]; then
echo "Players:"
for name in $players; do
echo " $name"
done
fi
if [[ ! -z "$ips" ]]; then
echo "IP Addresses:"
for address in $ips; do
echo " $address"
done
fi
fi
;;
*)
echo "Invalid command."
;;
esac
;;
operator|op)
case "$3" in
add)
if server_is_running $id; then
server_eval $id "op $4"
echo "The player \"$4\" is now an operator for the server."
else
echo "Server \"${server_name[$id]}\" is not running."
fi
;;
remove)
if server_is_running $id; then
server_eval $id "deop $4"
echo "The player \"$4\" is no longer an operator for the server."
else
echo "Server \"${server_name[$id]}\" is not running."
fi
;;
*)
echo "Invalid command"
;;
esac
;;
gamemode|gm)
case "$3" in
survival|creative|0|1)
if [ -z "$4" ]; then
echo "Invalid command."
else
if server_is_running $id; then
case "$3" in
creative|1) local mode=1;;
survival|0) local mode=0;;
*) echoerr "Invalid mode"; exit 1;;
esac
local line=$(server_eval_and_get_line $id "gamemode $4 $mode" "${server_confirm_gamemode[$id]}" "${server_confirm_gamemode_fail_no_user[$id]}" "${server_confirm_gamemode_fail_no_change[$id]}")
local regex="${LOG_REGEX} ${server_confirm_gamemode[$id]}"
if [[ $line =~ $regex ]]; then
echo "Changed game mode of \"$4\" to \"$3\"."
fi
local regex="${LOG_REGEX} ${server_confirm_gamemode_fail_no_user[$id]}"
if [[ $line =~ $regex ]]; then
echo "The player \"$4\" was not found to be logged on."
fi
local regex="${LOG_REGEX} ${server_confirm_gamemode_fail_no_change[$id]}"
if [[ $line =~ $regex ]]; then
echo "The player \"$4\" was already in \"$3\" mode."
fi
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
*)
echo "Invalid command."
;;
esac
;;
kick)
if [ -z "$3" ]; then
echo "Invalid command."
else
if server_is_running $id; then
local line=$(server_eval_and_get_line $id "kick $3" "${server_confirm_kick[$id]}" "${server_confirm_kick_fail[$id]}")
local regex="${LOG_REGEX} ${server_confirm_kick[$id]}"
if [[ $line =~ $regex ]]; then
echo "Kicked \"$3\" from game."
fi
local regex="${LOG_REGEX} ${server_confirm_kick_fail[$id]}"
if [[ $line =~ $regex ]]; then
echo "The player \"$3\" is not connected."
fi
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
say)
if [ -z "$3" ]; then
echo "Invalid command."
else
if server_is_running $id; then
server_eval $id "say ${*:3}"
echo "Message sent to players."
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
time)
case "$3" in
set)
if [ -z "$4" ]; then
echo "Invalid command."
else
if server_is_running $id; then
local line=$(server_eval_and_get_line $id "time set $4" "${server_confirm_time_set[$id]}" "${server_confirm_time_set_fail[$id]}")
local regex="${LOG_REGEX} ${server_confirm_time_set[$id]}"
if [[ $line =~ $regex ]]; then
echo "Set time to \"$4\"."
fi
local regex="${LOG_REGEX} ${server_confirm_time_set_fail[$id]}"
if [[ $line =~ $regex ]]; then
echo "Unable to convert \"$4\" to a time."
fi
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
add)
if [ -z "$4" ]; then
echo "Invalid command."
else
if server_is_running $id; then
local line=$(server_eval_and_get_line $id "time add $4" "${server_confirm_time_add[$id]}" "${server_confirm_time_add_fail[$id]}")
local regex="${LOG_REGEX} ${server_confirm_time_add[$id]}"
if [[ $line =~ $regex ]]; then
echo "Added \"$4\" to time."
fi
local regex="${LOG_REGEX} ${server_confirm_time_add_fail[$id]}"
if [[ $line =~ $regex ]]; then
echo "Unable to convert \"$4\" to a time."
fi
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
*)
echo "Invalid command."
;;
esac
;;
toggledownfall|tdf)
if server_is_running $id; then
local line=$(server_eval_and_get_line $id "toggledownfall $3" "${server_confirm_toggledownfall[$id]}" "${server_confirm_toggledownfall_fail[$id]}")
local regex="${LOG_REGEX} ${server_confirm_toggledownfall[$id]}"
if [[ $line =~ $regex ]]; then
echo ${line:36:(-3)}
fi
local regex="${LOG_REGEX} ${server_confirm_toggledownfall_fail[$id]}"
if [[ $line =~ $regex ]]; then
echo ${line:34:(-3)}
fi
else
echo "Server \"${server_name[$id]}\" is not running."
fi
;;
save)
case "$3" in
on)
server_save_on $id
;;
off)
server_save_off $id
;;
all)
server_save_all $id
;;
*)
echo "Invalid command."
;;
esac
;;
cmd)
if [ -z "$3" ]; then
echo "Invalid command."
else
if server_is_running $id; then
server_eval $id "${*:3}"
echo "Command sent."
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
cmdlog)
if [ -z "$3" ]; then
echo "Invalid command."
else
if server_is_running $id; then
server_eval $id "${*:3}"
echo "Now watching logs (press Ctrl+C to exit):"
as_user ${server_user_name[$id]} "tail --follow --lines=0 --sleep-interval=0.1 ${server_log[$id]}"
else
echo "Server \"${server_name[$id]}\" is not running."
fi
fi
;;
console)
if server_is_running $id; then
as_user ${server_user_name[$1]} "screen -r ${server_screen_name[$1]}"
else
echo "Server \"${server_name[$id]}\" is not running."
fi
;;
*)
echo "Invalid command."
;;
esac
else
echo "No server with that name."
fi
;;
esac
}
### Start point
main "$@"
exit 0