mirror of
https://github.com/msmhq/msm.git
synced 2024-08-30 18:12:35 +00:00
2648 lines
85 KiB
Bash
Executable File
2648 lines
85 KiB
Bash
Executable File
#!/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
|
||
|
||
# Get the MSM_CONF environment variable or use the default location
|
||
CONF="${MSM_CONF:-/etc/msm.conf}"
|
||
|
||
|
||
### The Minecraft Server Manager version, use "msm version" to check yours.
|
||
VERSION="0.3.2 Beta"
|
||
|
||
|
||
### Config variables the user should not 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 server configuration file name
|
||
declare -r SERVER_CONF_NAME="server.properties"
|
||
# 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
|
||
error_exit INVALID_USER "This command must be executed as the user \"$1\" or \"root\"."
|
||
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
|
||
}
|
||
|
||
# Exit's the script
|
||
error_exit() {
|
||
case "$1" in
|
||
INVALID_USER) code=64;;
|
||
INVALID_COMMAND) code=65;;
|
||
INVALID_ARGUMENT) code=66;;
|
||
SERVER_STOPPED) code=67;;
|
||
SERVER_RUNNING) code=68;;
|
||
NAME_NOT_FOUND) code=69;;
|
||
FILE_NOT_FOUND) code=70;;
|
||
DUPLICATE_NAME) code=71;;
|
||
LOGS_NOT_ROLLED) code=72;;
|
||
CONF_ERROR) code=73;;
|
||
FATAL_ERROR) code=74;;
|
||
esac
|
||
|
||
echo "${2:-"Unknown Error"}" 1>&2
|
||
exit "${code:-$1}"
|
||
}
|
||
|
||
# A function used to print debug messages to stdout. Prevents messages from
|
||
# appearing unless in debug mode, and allows debug statements to be easily
|
||
# distinguished from necessary echo statements.
|
||
# $1: The message to output
|
||
debug() {
|
||
if [[ "$DEBUG" == "true" ]]; then
|
||
echoerr "$1"
|
||
fi
|
||
}
|
||
|
||
# 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.
|
||
# $1: The name to check
|
||
is_valid_name() {
|
||
local valid="^[a-zA-Z0-9\_\-]+$"
|
||
local invalid="^(start|stop|restart|version|server|jargroup|all)$"
|
||
|
||
if [[ "$1" =~ $valid ]]; then
|
||
if [[ "$1" =~ $invalid ]]; then
|
||
error_exit INVALID_ARGUMENT "Invalid name \"$1\": A name may not be any of the following reserved worlds \"start\", \"stop\", \"restart\", \"server\", \"version\", \"jargroup\" or \"all\"."
|
||
else
|
||
return 0
|
||
fi
|
||
else
|
||
error_exit INVALID_ARGUMENT "Invalid name \"$1\": A name may only contain letters, numbers, dashes and unscores."
|
||
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" -gt "$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 '$(basename "${WORLD_FLAG_INRAM[$1]}")' \"${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 '$(basename "${WORLD_FLAG_INRAM[$1]}")' \"${WORLD_RAMDISK_PATH[$1]}/\" \"${WORLD_PATH[$1]}\""
|
||
}
|
||
|
||
# Toggles a worlds ramdisk state
|
||
# $1: The ID of the world
|
||
world_toggle_ramdisk_state() {
|
||
if [ -f "${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]}"
|
||
local containing_dir="$(dirname "${WORLD_PATH[$1]}")"
|
||
local dir_name="$(basename "${WORLD_PATH[$1]}")"
|
||
as_user "${SERVER_USER_NAME[$server_id]}" "mkdir -p \"${WORLD_BACKUP_PATH[$1]}\" && cd \"$containing_dir\" && zip -rq \"${WORLD_BACKUP_PATH[$1]}/${file_name}\" \"${dir_name}\""
|
||
|
||
echo "Done."
|
||
}
|
||
|
||
# Activates a world
|
||
# $1: The ID of the world
|
||
world_activate() {
|
||
if [ -d "${WORLD_INACTIVE_PATH[$1]}" ]; then
|
||
echo -n "Moving world \"${WORLD_NAME[$1]}\" to the active worldstorage directory... "
|
||
local new_path="${WORLD_ACTIVE_PATH[$1]}"
|
||
as_user "${SERVER_USER_NAME[${WORLD_SERVER_ID[$1]}]}" "mkdir -p \"$new_path\" && mv \"${WORLD_INACTIVE_PATH[$1]}\" \"$new_path\""
|
||
echo "Done."
|
||
else
|
||
if [ -d "${WORLD_ACTIVE_PATH[$1]}" ]; then
|
||
echo "World \"${WORLD_NAME[$1]}\" is already activate."
|
||
else
|
||
error_exit DIR_NOT_FOUND "Directory \"${WORLD_INACTIVE_PATH[$1]}\" could not be found."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Deactivates a world
|
||
# $1: The ID of the world
|
||
world_deactivate() {
|
||
if server_is_running "${WORLD_SERVER_ID[$1]}"; then
|
||
exit_error 68 "Worlds cannot be deactivated whilst the server is running."
|
||
else
|
||
if [ -d "${WORLD_ACTIVE_PATH[$1]}" ]; then
|
||
echo -n "Moving world \"${WORLD_NAME[$1]}\" to the inactive worldstorage directory... "
|
||
local new_path="${WORLD_INACTIVE_PATH[$1]}"
|
||
as_user "${SERVER_USER_NAME[${WORLD_SERVER_ID[$1]}]}" "mkdir -p \"$new_path\" && mv \"${WORLD_PATH[$1]}\" \"$new_path\""
|
||
echo "Done."
|
||
else
|
||
if [ -d "${WORLD_INACTIVE_PATH[$1]}" ]; then
|
||
echo "World \"${WORLD_NAME[$1]}\" is already deactivate."
|
||
else
|
||
exit_error DIR_NOT_FOUND "Directory \"${WORLD_ACTIVE_PATH[$1]}\" could not be found."
|
||
fi
|
||
fi
|
||
fi
|
||
}
|
||
|
||
### 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() {
|
||
for ((i=0; i<$num_servers; i++)); do
|
||
if [[ "${SERVER_NAME[$i]}" == "$1" ]]; then
|
||
echo "$i"
|
||
return 0
|
||
fi
|
||
done
|
||
|
||
error_exit NAME_NOT_FOUND "Could not find id for server name \"$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() {
|
||
if [ -d "${SERVER_WORLD_STORAGE[$1]}/$2" ] || [ -d "${SERVER_WORLD_STORAGE_INACTIVE[$1]}/$2" ]; then
|
||
# If the directory exists
|
||
|
||
local start="${SERVER_WORLD_OFFSET[$1]}"
|
||
local max="$(( $i + ${SERVER_NUM_WORLDS[$1]} ))"
|
||
|
||
# For each of the servers worlds:
|
||
for ((i=$start; i<$max; i++)); do
|
||
if [[ "${WORLD_NAME[$i]}" == "$2" ]]; then
|
||
echo "$i"
|
||
return 0
|
||
fi
|
||
done
|
||
fi
|
||
|
||
error_exit NAME_NOT_FOUND "Could not find id for world \"$2\" for server \"${SERVER_NAME[$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 start="${SERVER_WORLD_OFFSET[$1]}"
|
||
local max="$(( $start + ${SERVER_NUM_WORLDS[$1]} ))"
|
||
local output="false"
|
||
|
||
for ((i=$start; i<$max; i++)); do
|
||
if [[ "${WORLD_STATUS[$i]}" != "active" ]]; then
|
||
# Remove the symbolic link if it exists
|
||
as_user "${SERVER_USER_NAME[$1]}" "rm -f \"${WORLD_LINK[$i]}\""
|
||
continue
|
||
fi
|
||
|
||
# -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
|
||
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 [ -d "${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:0:${#line}-3}"
|
||
return 0
|
||
fi
|
||
done
|
||
fi
|
||
done < <(as_user "${SERVER_USER_NAME[$1]}" "tail --pid=$$ --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"
|
||
;;
|
||
*)
|
||
error_exit INVALID_ARGUMENT "Invalid argument."
|
||
;;
|
||
esac
|
||
}
|
||
|
||
|
||
### Jar Group Functions
|
||
|
||
# Lists the jar files grouped by jar groups.
|
||
jargroup_list() {
|
||
if [[ -d "${JAR_STORAGE_PATH}" ]]; then
|
||
local jargroup_name
|
||
local jar_name
|
||
|
||
while IFS= read -r -d $'\0' jargroup_path; do
|
||
jargroup_name="$(basename "${jargroup_path}")"
|
||
echo "$jargroup_name"
|
||
while IFS= read -r -d $'\0' jar_path; do
|
||
jar_name="$(basename "${jar_path}")"
|
||
if [[ "$jar_name" =~ ^[0-9]{4}-[0-9]{2}-[0-9]{2}- ]]; then
|
||
echo " $jar_name"
|
||
fi
|
||
done < <(find "${JAR_STORAGE_PATH}/${jargroup_name}" -mindepth 1 -maxdepth 1 -type f -print0)
|
||
done < <(find "${JAR_STORAGE_PATH}" -mindepth 1 -maxdepth 1 -type d -print0)
|
||
fi
|
||
}
|
||
|
||
# Creates a new jargroup
|
||
# $1: The name for the jargroup
|
||
jargroup_create() {
|
||
if is_valid_name "$1"; then
|
||
if [[ ! -d "$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."
|
||
error_exit FILE_NOT_FOUND "$error"
|
||
fi
|
||
|
||
error="$(as_user "$USERNAME" "echo \"$2\" > \"$JAR_STORAGE_PATH/$1/$JARGROUP_TARGET\"")"
|
||
if [[ "$error" != "" ]]; then
|
||
echo "Failed."
|
||
error_exit FILE_NOT_FOUND "$error"
|
||
fi
|
||
|
||
echo "Done."
|
||
else
|
||
error_exit DUPLICATE_NAME "A jar group with that name already exists."
|
||
fi
|
||
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 [[ -d "$JAR_STORAGE_PATH/$1" ]]; then
|
||
if [[ -f "$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."
|
||
error_exit FILE_NOT_FOUND "$error"
|
||
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 [[ ! -f "$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
|
||
|
||
[[ -f "$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
|
||
error_exit FILE_NOT_FOUND "Target URL not found, use $0 jargroup seturl <download-url>"
|
||
fi
|
||
else
|
||
error_exit NAME_NOT_FOUND "There is no jar group with the name \"$1\"."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Deletes an existing jargroup
|
||
# $1: The name of the existing jargroup
|
||
jargroup_delete() {
|
||
if is_valid_name "$1"; then
|
||
if [[ -d "$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
|
||
error_exit NAME_NOT_FOUND "There is no jar group with the name \"$1\"."
|
||
fi
|
||
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 [[ -d "$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
|
||
error_exit DUPLICATE_NAME "Could not be renamed, there is already a jar group with the name \"$2\"."
|
||
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
|
||
fi
|
||
else
|
||
error_exit NAME_NOT_FOUND "There is no jar group with the name \"$1\"."
|
||
fi
|
||
fi
|
||
}
|
||
|
||
|
||
### Server Functions
|
||
|
||
# Echos a list of servers in the SERVER_STORAGE_PATH
|
||
server_list() {
|
||
if [ -d "$SERVER_STORAGE_PATH" ]; then
|
||
ls -1 "$SERVER_STORAGE_PATH"
|
||
else
|
||
echo "[There are no servers]"
|
||
fi
|
||
}
|
||
|
||
# Creates a new server
|
||
# $1: The server name to create
|
||
server_create() {
|
||
if is_valid_name "$1"; then
|
||
if [[ -d "$SERVER_STORAGE_PATH/$1" ]]; then
|
||
error_exit DUPLICATE_NAME "A server with that name already exists."
|
||
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
|
||
|
||
# TODO: Handle server default setup stuff better than just using
|
||
# the "minecraft" jar group. And make it configurable.
|
||
if [ -d "$JAR_STORAGE_PATH/minecraft" ]; then
|
||
server_set_jar "$(server_get_id "$1")" "minecraft"
|
||
fi
|
||
fi
|
||
fi
|
||
}
|
||
|
||
# Deletes an existing server
|
||
# $1: The server name to delete
|
||
server_delete() {
|
||
if is_valid_name "$1"; then
|
||
if [[ -d "$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
|
||
error_exit NAME_NOT_FOUND "There is no server with the name \"$1\"."
|
||
fi
|
||
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 [ -d "$SERVER_STORAGE_PATH/$1" ]; then
|
||
# If the server name is valid and exists
|
||
|
||
local existing_id="$(server_get_id "$1")"
|
||
if server_is_running "$existing_id"; then
|
||
error_exit SERVER_RUNNING "Can only rename a stopped server."
|
||
else
|
||
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
|
||
error_exit DUPLICATE_NAME "Could not be renamed, there is already a server with the name \"$2\"."
|
||
else
|
||
as_user "$USERNAME" "mv '$SERVER_STORAGE_PATH/$1' '$SERVER_STORAGE_PATH/$2'"
|
||
echo "Renamed server \"$1\" to \"$2\"."
|
||
fi
|
||
fi
|
||
fi
|
||
else
|
||
error_exit NAME_NOT_FOUND "There is no server with the name \"$1\"."
|
||
fi
|
||
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[$1]="true"
|
||
|
||
server_eval "$1" "say ${SERVER_STOP_MESSAGE[$1]}"
|
||
echo "Issued the warning \"${SERVER_STOP_MESSAGE[$1]}\" to players."
|
||
|
||
echo -n "Shutting down... "
|
||
|
||
for ((i="${SERVER_STOP_DELAY[$1]}"; 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[$1]="true"
|
||
|
||
server_eval "$1" "say ${SERVER_RESTART_MESSAGE[$1]}"
|
||
echo "Issued the warning \"${SERVER_RESTART_MESSAGE[$1]}\" to players."
|
||
|
||
echo -n "Restarting... "
|
||
|
||
for ((i="${SERVER_STOP_DELAY[$1]}"; 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
|
||
echo "Failed."
|
||
error_exit LOGS_NOT_ROLLED "Logs were not rolled."
|
||
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 [ -d "$JAR_STORAGE_PATH/$2" ]; 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
|
||
error_exit NAME_NOT_FOUND "There is no jar named \"$3\" in jargroup \"$2\"."
|
||
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
|
||
error_exit NAME_NOT_FOUND "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}"
|
||
|
||
if [ -z "$players" ]; then
|
||
echo "No players are connected."
|
||
else
|
||
echo "$players"
|
||
fi
|
||
else
|
||
echo "Server \"${SERVER_NAME[$1]}\" 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]}" -gt "$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
|
||
}
|
||
|
||
|
||
### Command Functions
|
||
|
||
# Starts all servers
|
||
command_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
|
||
}
|
||
|
||
# Stops all servers after a delay
|
||
command_stop() {
|
||
manager_stop_all_servers "stop"
|
||
}
|
||
|
||
# Stops all servers without delay
|
||
command_stop_now() {
|
||
manager_stop_all_servers_now
|
||
}
|
||
|
||
# Restarts all servers
|
||
command_restart() {
|
||
echo "Stopping servers:"
|
||
command_stop
|
||
|
||
echo "Starting servers:"
|
||
command_start
|
||
}
|
||
|
||
# Restarts all servers without delay
|
||
command_restart_now() {
|
||
echo "Stopping servers:"
|
||
command_stop_now
|
||
|
||
echo "Starting servers:"
|
||
command_start
|
||
}
|
||
|
||
# Displays the MSM version
|
||
command_version() {
|
||
echo "Minecraft Server Manager $VERSION"
|
||
}
|
||
|
||
# Displays a list of servers
|
||
command_server_list() {
|
||
server_list
|
||
}
|
||
|
||
# Creates a new server with name $1
|
||
# $1: The new (valid) server name
|
||
command_server_create() {
|
||
server_create "$1"
|
||
}
|
||
|
||
# Deletes an existing server with name $1
|
||
# $1: The name of the existing server
|
||
command_server_delete() {
|
||
server_delete "$1"
|
||
}
|
||
|
||
# Renames an existing server
|
||
# $1: The existing server name
|
||
# $2: The new (valid) server name
|
||
command_server_rename() {
|
||
server_rename "$1" "$2"
|
||
}
|
||
|
||
# Displays a list of all jar's in jar groups
|
||
command_jargroup_list() {
|
||
jargroup_list
|
||
}
|
||
|
||
# Creates a new jar group
|
||
# $1: The new (valid) jar group name
|
||
# $2: The URL to use as the jar group target
|
||
command_jargroup_create() {
|
||
jargroup_create "$1" "$2"
|
||
}
|
||
|
||
# Deletes and existing jar group
|
||
# $1: The name of a jar group to delete
|
||
command_jargroup_delete() {
|
||
jargroup_delete "$1"
|
||
}
|
||
|
||
# Renames an existing jar group
|
||
# $1: The name of the existing jar group
|
||
# $2: The new (valid) name for the jar group
|
||
command_jargroup_rename() {
|
||
jargroup_rename "$1" "$2"
|
||
}
|
||
|
||
# Changes a jar group's target url for automatic downloads
|
||
# $1: The jar group name
|
||
# $2: The new URL to use
|
||
command_jargroup_changetarget() {
|
||
jargroup_settarget "$1" "$2"
|
||
}
|
||
|
||
# Downloads the latest jar for a jar group
|
||
# $1: The name of the jar group
|
||
command_jargroup_getlatest() {
|
||
jargroup_getlatest "$1"
|
||
}
|
||
|
||
# Displays a list of possible commands and help strings
|
||
command_help() {
|
||
# Outputs a list of all commands
|
||
echo -e "Usage: $0 command:"
|
||
echo -e
|
||
echo -e "--Setup Commands------------------------------------------------"
|
||
echo -e " server list List servers"
|
||
echo -e " server create <name> Creates a new Minecraft server"
|
||
echo -e " server delete <name> Deletes an existing Minecraft server"
|
||
echo -e " server rename <name> <new-name> Renames an existing Minecraft server"
|
||
echo -e
|
||
echo -e "--Server Mangement Commands-------------------------------------"
|
||
echo -e " <server> start Starts a server"
|
||
echo -e " <server> stop [now] Stops a server after warning players, or right now"
|
||
echo -e " <server> restart [now] Restarts a server after warning players, or right now"
|
||
echo -e " <server> status Show the running/stopped status of a server"
|
||
echo -e " <server> connected List a servers connected players"
|
||
echo -e " <server> worlds list Lists the worlds a server has"
|
||
echo -e " <server> worlds load Creates links to worlds in storage for a server"
|
||
echo -e " <server> worlds ram <world> Toggles a world's \"in RAM\" status"
|
||
echo -e " <server> worlds todisk Synchronises any \"in RAM\" worlds to disk a server has"
|
||
echo -e " <server> worlds backup Makes a backup of all worlds a server has"
|
||
echo -e " <server> worlds on|off <world> Activate or deactivate a world, inactive worlds are not backed up"
|
||
echo -e " <server> logroll Move a server log to a gziped archive, to reduce lag"
|
||
echo -e " <server> backup Makes a backup of an entire server directory"
|
||
echo -e " <server> jar <jargroup> [<file>] Sets a server's jar file"
|
||
echo -e
|
||
echo -e "--Server Pass Through Commands----------------------------------"
|
||
echo -e " <server> wl on|off Enables/disables server whitelist checking"
|
||
echo -e " <server> wl add|remove <player> Add/remove a player to/from a server's whitelist"
|
||
echo -e " <server> wl list List the players whitelisted for a server"
|
||
echo -e " <server> bl player add|remove <player> Ban/pardon a player from/for a server"
|
||
echo -e " <server> bl ip add|remove <ip address> Ban/pardon an IP address from/for a server"
|
||
echo -e " <server> bl list Lists the banned players and IP address for a server"
|
||
echo -e " <server> op add|remove <player> Add/remove operator status for a player on a server"
|
||
echo -e " <server> op list Lists the operator players for a server"
|
||
echo -e " <server> gm survival|creative <player> Change the game mode for a player on a server"
|
||
echo -e " <server> kick <player> Forcibly disconnect a player from a server"
|
||
echo -e " <server> say <message> Broadcast a (pink) message to all players on a server"
|
||
echo -e " <server> time set|add <number> Set/increment time on a server (0-24000)"
|
||
echo -e " <server> toggledownfall Toggles rain and snow on a server"
|
||
echo -e " <server> save on|off Enable/disable writing world changes to file"
|
||
echo -e " <server> save all Force the writing of all non-saved world changes to file"
|
||
echo -e " <server> cmd <command> Send a command string to the server and return"
|
||
echo -e " <server> cmdlog <command> Same as 'cmd' but shows log output afterwards (Ctrl+C to exit)"
|
||
echo -e
|
||
echo -e "--Jar Commands--------------------------------------------------"
|
||
echo -e " jargroup list List the stored jar files."
|
||
echo -e " jargroup create <name> <download-url> Create a new jar group, with a URL for new downloads"
|
||
echo -e " jargroup delete <name> Delete a jar group"
|
||
echo -e " jargroup rename <name> <new-name> Rename a jar group"
|
||
echo -e " jargroup changeurl <name> <download-url> Change the download URL for a jar group"
|
||
echo -e " jargroup getlatest <name> Download the latest jar file for a jar group"
|
||
echo -e
|
||
echo -e "--Global Commands-----------------------------------------------"
|
||
echo -e " start Starts all active servers"
|
||
echo -e " stop [now] Stops all running servers"
|
||
echo -e " restart [now] Restarts all active servers"
|
||
echo -e " version Prints the Minecraft Server Manager version installed"
|
||
}
|
||
|
||
# Starts an individual server
|
||
# $1: The server ID
|
||
command_server_start() {
|
||
server_set_active "$1" "active"
|
||
server_start "$1"
|
||
}
|
||
|
||
# Stops an individual server after a delay
|
||
# $1: The server ID
|
||
command_server_stop() {
|
||
server_set_active "$1" "inactive"
|
||
server_stop "$1"
|
||
}
|
||
|
||
# Stops an individual server without delay
|
||
# $1: The server ID
|
||
command_server_stop_now() {
|
||
server_set_active "$1" "inactive"
|
||
server_stop_now "$1"
|
||
}
|
||
|
||
# Restarts an individual server after a delay
|
||
# $1: The server ID
|
||
command_server_restart() {
|
||
server_set_active "$1" "active"
|
||
server_restart "$1"
|
||
}
|
||
|
||
# Restarts an individual server without delay
|
||
# $1: The server ID
|
||
command_server_restart_now() {
|
||
server_set_active "$1" "active"
|
||
server_restart_now "$1"
|
||
}
|
||
|
||
# Displays the running/stopped status of an individual server
|
||
# $1: The server ID
|
||
command_server_status() {
|
||
if server_is_running "$1"; then
|
||
echo "Server \"${SERVER_NAME[$1]}\" is running."
|
||
else
|
||
echo "Server \"${SERVER_NAME[$1]}\" is stopped."
|
||
fi
|
||
}
|
||
|
||
# Displays a list of connected players for an individual server
|
||
# $1: The server ID
|
||
command_server_connected() {
|
||
server_connected "$1"
|
||
}
|
||
|
||
# Displays a list of worlds for an individual server
|
||
# $1: The server ID
|
||
command_server_worlds_list() {
|
||
server_worlds_list "$1"
|
||
}
|
||
|
||
# Creates symlinks for all active worlds so they can be used by the Minecraft
|
||
# server when running
|
||
# $1: The server ID
|
||
command_server_worlds_load() {
|
||
server_ensure_links "$1"
|
||
}
|
||
|
||
# Toggles a world's inram status
|
||
# $1: The server ID
|
||
# $2: The world ID
|
||
command_server_worlds_ram() {
|
||
if server_is_running "$1"; then
|
||
error_exit SERVER_RUNNING "Server \"${SERVER_NAME[$1]}\" is running. Please stop the server before altering a worlds in-ram status."
|
||
else
|
||
world_toggle_ramdisk_state "$2"
|
||
fi
|
||
}
|
||
|
||
# Synchronises all inram worlds back to disk for an individual server
|
||
# $1: The server ID
|
||
command_server_worlds_todisk() {
|
||
server_save_off "$1"
|
||
server_save_all "$1"
|
||
server_worlds_to_disk "$1"
|
||
server_save_on "$1"
|
||
}
|
||
|
||
# Makes a backup of all worlds for an individual server
|
||
# $1: The server ID
|
||
command_server_worlds_backup() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "say ${SERVER_WORLD_BACKUP_STARTED[$1]}"
|
||
server_save_off "$1"
|
||
server_save_all "$1"
|
||
fi
|
||
|
||
server_worlds_to_disk "$1"
|
||
server_worlds_backup "$1"
|
||
|
||
if server_is_running "$1"; then
|
||
server_save_on "$1"
|
||
server_eval "$1" "say ${SERVER_WORLD_BACKUP_FINISHED[$1]}"
|
||
fi
|
||
|
||
echo "Backup took $SECONDS seconds".
|
||
}
|
||
|
||
# Enables a world to be used by its server
|
||
# $1: The server ID
|
||
# $2: The world ID
|
||
command_server_worlds_on() {
|
||
world_activate "$2"
|
||
}
|
||
|
||
# Disables a world from being used by its server, also prevents it from being
|
||
# backed up with the other worlds.
|
||
# $1: The server ID
|
||
# $2: The world ID
|
||
command_server_worlds_off() {
|
||
world_deactivate "$2"
|
||
}
|
||
|
||
# Moves an individual server's log text to another file, leaving it empty
|
||
# $1: The server ID
|
||
command_SERVER_LOGroll() {
|
||
SERVER_LOG_roll "$1"
|
||
}
|
||
|
||
# Makes a backup of an entire server directory
|
||
# $1: The server ID
|
||
command_server_backup() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "say ${SERVER_COMPLETE_BACKUP_STARTED[$1]}"
|
||
server_save_off "$1"
|
||
server_save_all "$1"
|
||
fi
|
||
|
||
server_worlds_to_disk "$1"
|
||
server_backup "$1"
|
||
|
||
if server_is_running "$1"; then
|
||
server_save_on "$1"
|
||
server_eval "$1" "say ${SERVER_COMPLETE_BACKUP_FINISHED[$1]}"
|
||
fi
|
||
|
||
echo "Backup took $SECONDS seconds".
|
||
}
|
||
|
||
# Sets an individual server's jar file to use when starting up
|
||
# $1: The server ID
|
||
# $2: The jar group name
|
||
# $3: Optionally a specific jar file name which exists within that jargroup, if
|
||
# not provided the latest version will be used.
|
||
command_server_jar() {
|
||
server_set_jar "$1" "$2" "$3"
|
||
}
|
||
|
||
# Turns a server's whitelist protection on
|
||
# $1: The server ID
|
||
command_server_whitelist_on() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "whitelist on"
|
||
echo "Whitelist enabled"
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Turns a server's whitelist protection off
|
||
# $1: The server ID
|
||
command_server_whitelist_off() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "whitelist off"
|
||
echo "Whitelist disabled"
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Adds a player name to a server's whitelist
|
||
# $1: The server ID
|
||
# $2: The player name
|
||
command_server_whitelist_add() {
|
||
# TODO: Support whitelisting multiple players (see blacklist player add)
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "whitelist add $2"
|
||
echo "Added \"$2\" to the whitelist."
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Removes a player name from a server's whitelist
|
||
# $1: The server ID
|
||
# $2: The player name
|
||
command_server_whitelist_remove() {
|
||
# TODO: Support multiple player names
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "whitelist remove $2"
|
||
echo "Removed \"$2\" from the whitelist."
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Displays a list of whitelisted players for an individual server
|
||
# $1: The server ID
|
||
command_server_whitelist_list() {
|
||
if [ -f "${SERVER_WHITELIST[$1]}" ]; then
|
||
local players="$(cat "${SERVER_WHITELIST[$1]}")"
|
||
|
||
if [ -z "$players" ]; then
|
||
echo "No players are whitelisted."
|
||
else
|
||
echo "$players"
|
||
fi
|
||
else
|
||
echo "No players are whitelisted."
|
||
fi
|
||
}
|
||
|
||
# Adds player names to a server's ban list
|
||
# $1: The server ID
|
||
# $2->: The player names
|
||
command_server_blacklist_player_add() {
|
||
for player in "${@:2}"; do
|
||
server_eval "$1" "ban $player"
|
||
done
|
||
|
||
if [[ $# -gt 2 ]]; then
|
||
echo -n "Blacklisted the following players: "
|
||
echo -n "$2"
|
||
for player in "${@:3}"; do
|
||
echo -n ", $player"
|
||
done
|
||
echo "."
|
||
else
|
||
echo "Blacklisted \"$2\"."
|
||
fi
|
||
}
|
||
|
||
# Removes player names from a server's ban list
|
||
# $1: The server ID
|
||
# $2->: The player names
|
||
command_server_blacklist_player_remove() {
|
||
for player in "${@:2}"; do
|
||
server_eval "$1" "pardon $player"
|
||
done
|
||
|
||
if [[ $# -gt 2 ]]; then
|
||
echo -n "Removed the following players from the blacklist: "
|
||
echo -n "$2"
|
||
for player in "${@:3}"; do
|
||
echo -n ", $player"
|
||
done
|
||
echo "."
|
||
else
|
||
echo "Removed \"$2\" from the blacklist."
|
||
fi
|
||
}
|
||
|
||
# Adds ip addresses to a server's ban list
|
||
# $1: The server ID
|
||
# $2->: The ip addresses
|
||
command_server_blacklist_ip_add() {
|
||
for address in "${@:2}"; do
|
||
server_eval "$1" "ban-ip $address"
|
||
done
|
||
|
||
if [[ $# -gt 2 ]]; then
|
||
echo -n "Blacklisted the following ip addresses: "
|
||
echo -n "$2"
|
||
for player in "${@:3}"; do
|
||
echo -n ", $address"
|
||
done
|
||
echo "."
|
||
else
|
||
echo "Blacklisted \"$2\"."
|
||
fi
|
||
}
|
||
|
||
# Removes ip addresses to a server's ban list
|
||
# $1: The server ID
|
||
# $2->: The ip addresses
|
||
command_server_blacklist_ip_remove() {
|
||
for address in "${@:2}"; do
|
||
server_eval "$1" "pardon-ip $address"
|
||
done
|
||
|
||
if [[ $# -gt 2 ]]; then
|
||
echo -n "Removed the following ip addresses from the blacklist: "
|
||
echo -n "$2"
|
||
for player in "${@:3}"; do
|
||
echo -n ", $address"
|
||
done
|
||
echo "."
|
||
else
|
||
echo "Removed \"$2\" from the ip blacklist."
|
||
fi
|
||
}
|
||
|
||
# Displays a server's banned player names and ip addresses
|
||
# $1: The server ID
|
||
command_server_blacklist_list() {
|
||
local players
|
||
local ips
|
||
|
||
if [ -f "${SERVER_BANNED_PLAYERS[$1]}" ]; then
|
||
players="$(cat "${SERVER_BANNED_PLAYERS[$1]}")"
|
||
fi
|
||
|
||
if [ -f "${SERVER_BANNED_IPS[$1]}" ]; then
|
||
ips="$(cat "${SERVER_BANNED_IPS[$1]}")"
|
||
fi
|
||
|
||
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
|
||
}
|
||
|
||
# Adds a player name to a server's list of operators
|
||
# $1: The server ID
|
||
# $2: The player name
|
||
command_server_operator_add() {
|
||
# TODO: Support multiple player names
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "op $2"
|
||
echo "The player \"$2\" is now an operator for the server."
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Removes a player name to a server's list of operators
|
||
# $1: The server ID
|
||
# $2: The player name
|
||
command_server_operator_remove() {
|
||
# TODO: Support multiple player names
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "deop $2"
|
||
echo "The player \"$2\" is no longer an operator for the server."
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Displays a list of operators for an individual server
|
||
# $1: The server ID
|
||
command_server_operator_list() {
|
||
# TODO: Protect against non-existent files
|
||
local players="$(cat "${SERVER_OPS[$1]}")"
|
||
|
||
if [ -z "$players" ]; then
|
||
echo "No players are operators."
|
||
else
|
||
echo "$players"
|
||
fi
|
||
}
|
||
|
||
# Sets the game mode for
|
||
# $1: The server ID
|
||
# $2: The game mode
|
||
# $3: The player name
|
||
command_server_gamemode() {
|
||
# TODO: Support multiple player names
|
||
if server_is_running "$1"; then
|
||
local mode line regex
|
||
case "$2" in
|
||
creative|1) mode=1;;
|
||
survival|0) mode=0;;
|
||
*) error_exit INVALID_ARGUMENT "Invalid gamemode type \"$2\" options are \"survival\", \"creative\", \"0\" or \"1\".";;
|
||
esac
|
||
|
||
line="$(server_eval_and_get_line "$1" "gamemode $3 $mode" "${SERVER_CONFIRM_GAMEMODE[$1]}" "${SERVER_CONFIRM_GAMEMODE_FAIL_NO_USER[$1]}" "${SERVER_CONFIRM_GAMEMODE_FAIL_NO_CHANGE[$1]}")"
|
||
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_GAMEMODE[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "Changed game mode of \"$3\" to \"$2\"."
|
||
fi
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_GAMEMODE_FAIL_NO_USER[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "The player \"$3\" was not found to be logged on."
|
||
fi
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_GAMEMODE_FAIL_NO_CHANGE[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "The player \"$3\" was already in mode \"$2\"."
|
||
fi
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Kicks a connected player from a server
|
||
# $1: The server ID
|
||
# $2: The player name
|
||
command_server_kick() {
|
||
# TODO: Support multiple player names
|
||
if server_is_running "$1"; then
|
||
local line regex
|
||
line="$(server_eval_and_get_line "$1" "kick $2" "${SERVER_CONFIRM_KICK[$1]}" "${SERVER_CONFIRM_KICK_FAIL[$1]}")"
|
||
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_KICK[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "Kicked \"$2\" from game."
|
||
fi
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_KICK_FAIL[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "The player \"$2\" is not connected."
|
||
fi
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Broadcasts a message to all connected players for a server
|
||
# $1: The server ID
|
||
# $2->: Words of the message, will be concatinated with spaces
|
||
command_server_say() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "say ${*:2}"
|
||
echo "Message sent to players."
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Sets the time on an individual server
|
||
# $1: The server ID
|
||
# $2: The time
|
||
command_server_time_set() {
|
||
if server_is_running "$1"; then
|
||
local line regex
|
||
line="$(server_eval_and_get_line "$1" "time set $2" "${SERVER_CONFIRM_TIME_SET[$1]}" "${SERVER_CONFIRM_TIME_SET_FAIL[$1]}")"
|
||
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_TIME_SET[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "Set time to \"$2\"."
|
||
fi
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_TIME_SET_FAIL[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
error_exit INVALID_ARGUMENT "Unable to convert \"$2\" to a time."
|
||
fi
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Increments the time on an individual server
|
||
# $1: The server ID
|
||
# $2: The time to add
|
||
command_server_time_add() {
|
||
if server_is_running "$1"; then
|
||
local line regex
|
||
line="$(server_eval_and_get_line "$1" "time add $2" "${SERVER_CONFIRM_TIME_ADD[$1]}" "${SERVER_CONFIRM_TIME_ADD_FAIL[$1]}")"
|
||
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_TIME_ADD[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "Added \"$2\" to time."
|
||
fi
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_TIME_ADD_FAIL[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
error_exit INVALID_ARGUMENT "Unable to convert \"$2\" to a time."
|
||
fi
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Toggles the downfall of rain and snow on an individual server
|
||
# $1: The server ID
|
||
command_server_toggledownfall() {
|
||
if server_is_running "$1"; then
|
||
local line regex
|
||
line="$(server_eval_and_get_line "$1" "toggledownfall" "${SERVER_CONFIRM_TOGGLEDOWNFALL[$1]}" "${SERVER_CONFIRM_TOGGLEDOWNFALL_FAIL[$1]}")"
|
||
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_TOGGLEDOWNFALL[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "${line:36}"
|
||
fi
|
||
regex="${LOG_REGEX} ${SERVER_CONFIRM_TOGGLEDOWNFALL_FAIL[$1]}"
|
||
if [[ "$line" =~ $regex ]]; then
|
||
echo "${line:34}"
|
||
fi
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Turns world saving on for an individual server
|
||
# $1: The server ID
|
||
command_server_save_on() {
|
||
server_save_on "$1"
|
||
}
|
||
|
||
# Turns world saving off for an individual server
|
||
# $1: The server ID
|
||
command_server_save_off() {
|
||
server_save_off "$1"
|
||
}
|
||
|
||
# Forces the saving of all pending world saves
|
||
# $1: The server ID
|
||
command_server_save_all() {
|
||
server_save_all "$1"
|
||
}
|
||
|
||
# Sends a command string to the server to be executed
|
||
# $1: The server ID
|
||
# $2->: A command, separate arguments are concatinated with spaces
|
||
command_server_cmd() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "${*:2}"
|
||
echo "Command sent."
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Sends a command string to the server to be executed, and then tails the
|
||
# server logs to watch fro results.
|
||
# $1: The server ID
|
||
# $2->: A command, separate arguments are concatinated with spaces
|
||
command_server_cmdlog() {
|
||
if server_is_running "$1"; then
|
||
server_eval "$1" "${*:2}"
|
||
echo "Now watching logs (press Ctrl+C to exit):"
|
||
as_user "${SERVER_USER_NAME[$1]}" "tail --pid=$$ --follow --lines=0 --sleep-interval=0.1 ${SERVER_LOG[$1]}"
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
# Resumes a server's screen session (requires ssh-ed in as server user, using
|
||
# the `su` command will not work.)
|
||
# $1: The server ID
|
||
command_server_console() {
|
||
if server_is_running "$1"; then
|
||
as_user "${SERVER_USER_NAME[$1]}" "screen -r ${SERVER_SCREEN_NAME[$1]}"
|
||
else
|
||
error_exit SERVER_STOPPED "Server \"${SERVER_NAME[$1]}\" is not running."
|
||
fi
|
||
}
|
||
|
||
|
||
### Main Functions
|
||
|
||
# Initialises a server's world
|
||
# $1: The server id
|
||
# $2: The world id to use
|
||
# $3: The name of the world
|
||
server_world_init() {
|
||
WORLD_SERVER_ID[$2]="$1"
|
||
WORLD_NAME[$2]="$3"
|
||
WORLD_ACTIVE_PATH[$2]="${SERVER_WORLD_STORAGE[$1]}/${WORLD_NAME[$2]}"
|
||
WORLD_INACTIVE_PATH[$2]="${SERVER_WORLD_STORAGE_INACTIVE[$1]}/${WORLD_NAME[$2]}"
|
||
|
||
# Set the status of this world (active/inactive)
|
||
if [ -d "${WORLD_ACTIVE_PATH[$2]}" ]; then
|
||
WORLD_STATUS[$2]="active"
|
||
WORLD_PATH[$2]="${WORLD_ACTIVE_PATH[$2]}"
|
||
else
|
||
if [ -d "${WORLD_INACTIVE_PATH[$2]}" ]; then
|
||
WORLD_STATUS[$2]="inactive"
|
||
WORLD_PATH[$2]="${WORLD_INACTIVE_PATH[$2]}"
|
||
else
|
||
WORLD_STATUS[$2]="unknown"
|
||
|
||
error_exit NAME_NOT_FOUND "World cannot be found in either \"${WORLD_ACTIVE_PATH[$2]}\" or \"${WORLD_INACTIVE_PATH[$2]}\"."
|
||
fi
|
||
fi
|
||
|
||
# TODO: Allow the inram flag location to be overridable.
|
||
WORLD_FLAG_INRAM[$2]="${WORLD_PATH[$2]}/inram"
|
||
WORLD_LINK[$2]="${SERVER_PATH[$1]}/${WORLD_NAME[$2]}"
|
||
WORLD_BACKUP_PATH[$2]="$WORLD_ARCHIVE_PATH/${SERVER_NAME[$1]}/${WORLD_NAME[$2]}"
|
||
|
||
# If the ramdisk path is set, get the path for this world
|
||
if [ ! -z "$RAMDISK_STORAGE_PATH" ]; then
|
||
WORLD_RAMDISK_PATH[$2]="${RAMDISK_STORAGE_PATH}/${SERVER_NAME[$1]}/${WORLD_NAME[$2]}"
|
||
fi
|
||
|
||
# Detect whether this world should be in ram
|
||
if [[ -e "${WORLD_FLAG_INRAM[$2]}" ]]; then
|
||
WORLD_INRAM[$2]="true"
|
||
else
|
||
WORLD_INRAM[$2]="false"
|
||
fi
|
||
}
|
||
|
||
# Load a config file for a server
|
||
# $1: The id of the server to laod
|
||
server_load_config() {
|
||
local name value
|
||
|
||
if [[ -f "${SERVER_CONF[$1]}" ]]; then
|
||
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
|
||
# Minecraft settings:
|
||
allow-flight) SERVER_PROPERTIES_ALLOW_FLIGHT="$value";;
|
||
allow-nether) SERVER_PROPERTIES_ALLOW_NETHER="$value";;
|
||
difficulty) SERVER_PROPERTIES_DIFFICULTY="$value";;
|
||
enable-query) SERVER_PROPERTIES_ENABLE_QUERY="$value";;
|
||
enable-rcon) SERVER_PROPERTIES_ENABLE_RCON="$value";;
|
||
gamemode) SERVER_PROPERTIES_GAMEMODE="$value";;
|
||
generate-structures) SERVER_PROPERTIES_GENERATE_STRUCTURES="$value";;
|
||
level-name) SERVER_PROPERTIES_LEVEL_NAME="$value";;
|
||
level-seed) SERVER_PROPERTIES_LEVEL_SEED="$value";;
|
||
level-type) SERVER_PROPERTIES_LEVEL_TYPE="$value";;
|
||
max-build-height) SERVER_PROPERTIES_MAX_BUILD_HEIGHT="$value";;
|
||
max-players) SERVER_PROPERTIES_MAX_PLAYERS="$value";;
|
||
motd) SERVER_PROPERTIES_MOTD="$value";;
|
||
texture-pack) SERVER_PROPERTIES_TEXTURE_PACK="$value";;
|
||
online-mode) SERVER_PROPERTIES_ONLINE_MODE="$value";;
|
||
pvp) SERVER_PROPERTIES_PVP="$value";;
|
||
query.port) SERVER_PROPERTIES_QUERY_PORT="$value";;
|
||
rcon.password) SERVER_PROPERTIES_RCON_PASSWORD="$value";;
|
||
rcon.port) SERVER_PROPERTIES_RCON_PORT="$value";;
|
||
server-ip) SERVER_PROPERTIES_SERVER_IP="$value";;
|
||
server-port) SERVER_PROPERTIES_SERVER_PORT="$value";;
|
||
spawn-animals) SERVER_PROPERTIES_SPAWN_ANIMALS="$value";;
|
||
spawn-monsters) SERVER_PROPERTIES_SPAWN_MONSTERS="$value";;
|
||
spawn-npcs) SERVER_PROPERTIES_SPAWN_NPCS="$value";;
|
||
view-distance) SERVER_PROPERTIES_VIEW_DISTANCE="$value";;
|
||
white-list) SERVER_PROPERTIES_WHITE_LIST="$value";;
|
||
|
||
# MSM setttings:
|
||
msm-server-user) SERVER_USER_NAME[$1]="$value";;
|
||
msm-screen-name) SERVER_SCREEN_NAME[$1]="$value";;
|
||
msm-world-storage-path) SERVER_WORLD_STORAGE[$1]="${SERVER_PATH[$1]}/$value";;
|
||
msm-world-storage-inactive-path) SERVER_WORLD_STORAGE_INACTIVE[$1]="${SERVER_PATH[$1]}/$value";;
|
||
|
||
msm-log) SERVER_LOG[$1]="${SERVER_PATH[$1]}/$value";;
|
||
msm-whitelist) SERVER_WHITELIST[$1]="${SERVER_PATH[$1]}/$value";;
|
||
msm-banned-players) SERVER_BANNED_PLAYERS[$1]="${SERVER_PATH[$1]}/$value";;
|
||
msm-banned-ips) SERVER_BANNED_IPS[$1]="${SERVER_PATH[$1]}/$value";;
|
||
|
||
msm-ops) SERVER_OPS[$1]="${SERVER_PATH[$1]}/$value";;
|
||
msm-jar) SERVER_JAR[$1]="${SERVER_PATH[$1]}/$value";;
|
||
msm-ram) SERVER_RAM[$1]="$value";;
|
||
msm-invocation) SERVER_INVOCATION[$1]="$value";;
|
||
|
||
msm-stop-delay) SERVER_STOP_DELAY[$1]="$value";;
|
||
msm-restart-delay) SERVER_RESTART_DELAY[$1]="$value";;
|
||
|
||
msm-stop-message) SERVER_STOP_MESSAGE[$1]="$value";;
|
||
msm-stop-abort) SERVER_STOP_ABORT[$1]="$value";;
|
||
msm-restart-message) SERVER_RESTART_MESSAGE[$1]="$value";;
|
||
msm-restart-abort) SERVER_RESTART_ABORT[$1]="$value";;
|
||
msm-world-backup-started) SERVER_WORLD_BACKUP_STARTED[$1]="$value";;
|
||
msm-world-backup-finished) SERVER_WORLD_BACKUP_FINISHED[$1]="$value";;
|
||
msm-complete-backup-started) SERVER_COMPLETE_BACKUP_STARTED[$1]="$value";;
|
||
msm-complete-backup-finished) SERVER_COMPLETE_BACKUP_FINISHED[$1]="$value";;
|
||
msm-complete-backup-follow-symlinks) SERVER_COMPLETE_BACKUP_FOLLOW_SYMLINKS[$1]="$value";;
|
||
|
||
msm-confirm-save-on) SERVER_CONFIRM_SAVE_ON[$1]="$value";;
|
||
msm-confirm-save-off) SERVER_CONFIRM_SAVE_OFF[$1]="$value";;
|
||
msm-confirm-save-all) SERVER_CONFIRM_SAVE_ALL[$1]="$value";;
|
||
msm-confirm-start) SERVER_CONFIRM_START[$1]="$value";;
|
||
msm-confirm-whitelist-list) SERVER_CONFIRM_WHITELIST_LIST[$1]="$value";;
|
||
msm-confirm-kick) SERVER_CONFIRM_CONFIRM_KICK[$1]="$value";;
|
||
msm-confirm-kick-fail) SERVER_CONFIRM_KICK_FAIL[$1]="$value";;
|
||
msm-confirm-time-set) SERVER_CONFIRM_TIME_SET[$1]="$value";;
|
||
msm-confirm-time-set-fail) SERVER_CONFIRM_TIME_SET_FAIL[$1]="$value";;
|
||
msm-confirm-time-add) SERVER_CONFIRM_TIME_ADD[$1]="$value";;
|
||
msm-confirm-time-add-fail) SERVER_CONFIRM_TIME_ADD_FAIL[$1]="$value";;
|
||
msm-confirm-toggledownfall) SERVER_CONFIRM_TOGGLEDOWNFALL[$1]="$value";;
|
||
msm-confirm-toggledownfall-fail) SERVER_CONFIRM_TOGGLEDOWNFALL_FAIL[$1]="$value";;
|
||
msm-confirm-gamemode) SERVER_CONFIRM_GAMEMODE[$1]="$value";;
|
||
msm-confirm-gamemode-fail-no-user) SERVER_CONFIRM_GAMEMODE_FAIL_NO_USER[$1]="$value";;
|
||
msm-confirm-gamemode-fail-no-change) SERVER_CONFIRM_GAMEMODE_FAIL_NO_CHANGE[$1]="$value";;
|
||
esac
|
||
done < "${SERVER_CONF[$1]}"
|
||
fi
|
||
}
|
||
|
||
# Initialise a server's variables
|
||
# $1: The id to use for this server
|
||
# $2: The name of the server
|
||
server_init() {
|
||
SERVER_NAME[$1]="$2"
|
||
SERVER_PATH[$1]="$SERVER_STORAGE_PATH/$2"
|
||
SERVER_CONF[$1]="${SERVER_PATH[$1]}/$SERVER_CONF_NAME"
|
||
SERVER_FLAG_ACTIVE[$1]="${SERVER_PATH[$1]}/active"
|
||
SERVER_BACKUP_PATH[$1]="$BACKUP_ARCHIVE_PATH/${SERVER_NAME[$1]}"
|
||
SERVER_LOG_archive_path[$1]="$LOG_ARCHIVE_PATH/${SERVER_NAME[$1]}"
|
||
|
||
if [[ -e "${SERVER_FLAG_ACTIVE[$1]}" ]]; then
|
||
SERVER_ACTIVE[$1]="true"
|
||
else
|
||
SERVER_ACTIVE[$1]="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[$1]="$DEFAULT_SERVER_USER"
|
||
SERVER_SCREEN_NAME[$1]="${DEFAULT_SCREEN_NAME//\{SERVER_NAME\}/${SERVER_NAME[$1]}}" # Replace tags now, they cannot change
|
||
SERVER_WORLD_STORAGE[$1]="${SERVER_PATH[$1]}/$DEFAULT_WORLD_STORAGE_PATH"
|
||
SERVER_WORLD_STORAGE_INACTIVE[$1]="${SERVER_PATH[$1]}/$DEFAULT_WORLD_STORAGE_INACTIVE_PATH"
|
||
SERVER_LOG[$1]="${SERVER_PATH[$1]}/$DEFAULT_LOG"
|
||
SERVER_WHITELIST[$1]="${SERVER_PATH[$1]}/$DEFAULT_WHITELIST"
|
||
SERVER_BANNED_PLAYERS[$1]="${SERVER_PATH[$1]}/$DEFAULT_BANNED_PLAYERS"
|
||
SERVER_BANNED_IPS[$1]="${SERVER_PATH[$1]}/$DEFAULT_BANNED_IPS"
|
||
SERVER_OPS[$1]="${SERVER_PATH[$1]}/$DEFAULT_OPS"
|
||
SERVER_JAR[$1]="${SERVER_PATH[$1]}/$DEFAULT_JAR"
|
||
SERVER_RAM[$1]="$DEFAULT_RAM"
|
||
SERVER_INVOCATION[$1]="$DEFAULT_INVOCATION" # Don't replace tags yet, they may change
|
||
SERVER_STOP_DELAY[$1]="$DEFAULT_STOP_DELAY"
|
||
SERVER_RESTART_DELAY[$1]="$DEFAULT_RESTART_DELAY"
|
||
SERVER_STOP_MESSAGE[$1]="$DEFAULT_STOP_MESSAGE"
|
||
SERVER_STOP_ABORT[$1]="$DEFAULT_STOP_ABORT"
|
||
SERVER_RESTART_MESSAGE[$1]="$DEFAULT_RESTART_MESSAGE"
|
||
SERVER_RESTART_ABORT[$1]="$DEFAULT_RESTART_ABORT"
|
||
SERVER_WORLD_BACKUP_STARTED[$1]="$DEFAULT_WORLD_BACKUP_STARTED"
|
||
SERVER_WORLD_BACKUP_FINISHED[$1]="$DEFAULT_WORLD_BACKUP_FINISHED"
|
||
SERVER_COMPLETE_BACKUP_STARTED[$1]="$DEFAULT_COMPLETE_BACKUP_STARTED"
|
||
SERVER_COMPLETE_BACKUP_FINISHED[$1]="$DEFAULT_COMPLETE_BACKUP_FINISHED"
|
||
SERVER_CONFIRM_SAVE_ON[$1]="$DEFAULT_CONFIRM_SAVE_ON"
|
||
SERVER_CONFIRM_SAVE_OFF[$1]="$DEFAULT_CONFIRM_SAVE_OFF"
|
||
SERVER_CONFIRM_SAVE_ALL[$1]="$DEFAULT_CONFIRM_SAVE_ALL"
|
||
SERVER_CONFIRM_START[$1]="$DEFAULT_CONFIRM_START"
|
||
SERVER_CONFIRM_KICK[$1]="$DEFAULT_CONFIRM_KICK"
|
||
SERVER_CONFIRM_KICK_FAIL[$1]="$DEFAULT_CONFIRM_KICK_FAIL"
|
||
SERVER_CONFIRM_TIME_SET[$1]="$DEFAULT_CONFIRM_TIME_SET"
|
||
SERVER_CONFIRM_TIME_SET_FAIL[$1]="$DEFAULT_CONFIRM_TIME_SET_FAIL"
|
||
SERVER_CONFIRM_TIME_ADD[$1]="$DEFAULT_CONFIRM_TIME_ADD"
|
||
SERVER_CONFIRM_TIME_ADD_FAIL[$1]="$DEFAULT_CONFIRM_TIME_ADD_FAIL"
|
||
SERVER_CONFIRM_TOGGLEDOWNFALL[$1]="$DEFAULT_CONFIRM_TOGGLEDOWNFALL"
|
||
SERVER_CONFIRM_TOGGLEDOWNFALL_FAIL[$1]="$DEFAULT_CONFIRM_TOGGLEDOWNFALL_FAIL"
|
||
SERVER_CONFIRM_GAMEMODE[$1]="$DEFAULT_CONFIRM_GAMEMODE"
|
||
SERVER_CONFIRM_GAMEMODE_FAIL_NO_USER[$1]="$DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_USER"
|
||
SERVER_CONFIRM_GAMEMODE_FAIL_NO_CHANGE[$1]="$DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_CHANGE"
|
||
SERVER_COMPLETE_BACKUP_FOLLOW_SYMLINKS[$1]="$DEFAULT_COMPLETE_BACKUP_FOLLOW_SYMLINKS"
|
||
|
||
|
||
# Load config overrides from server config file if present
|
||
server_load_config "$1"
|
||
|
||
|
||
# Replace tags in delay messages
|
||
SERVER_STOP_MESSAGE[$1]="${SERVER_STOP_MESSAGE[$1]//\{DELAY\}/${SERVER_STOP_DELAY[$1]}}"
|
||
SERVER_RESTART_MESSAGE[$1]="${SERVER_RESTART_MESSAGE[$1]//\{DELAY\}/${SERVER_RESTART_DELAY[$1]}}"
|
||
|
||
# Replace tags in server invocation
|
||
SERVER_INVOCATION[$1]="${SERVER_INVOCATION[$1]//\{RAM\}/${SERVER_RAM[$1]}}"
|
||
SERVER_INVOCATION[$1]="${SERVER_INVOCATION[$1]//\{JAR\}/${SERVER_JAR[$1]}}"
|
||
|
||
|
||
# Load worlds if there is a world storage directory present
|
||
SERVER_WORLD_OFFSET[$1]=0
|
||
SERVER_NUM_WORLDS[$1]=0
|
||
|
||
# Start world id's for this server's worlds at the end of the array
|
||
local id="$num_worlds"
|
||
|
||
# Record the index at which worlds for this server start
|
||
SERVER_WORLD_OFFSET[$1]="$id"
|
||
|
||
if [[ -d "${SERVER_WORLD_STORAGE[$1]}" ]]; then
|
||
# Load active worlds
|
||
while IFS= read -r -d $'\0' path; do
|
||
local name="$(basename "$path")"
|
||
server_world_init "$1" "$id" "$name"
|
||
|
||
# Build the server_worlds comma separated list
|
||
if [[ "$id" == "${SERVER_WORLD_OFFSET[$1]}" ]]; then
|
||
SERVER_WORLDS[$1]="$name"
|
||
else
|
||
SERVER_WORLDS[$1]="${SERVER_WORLDS[$1]}, $name"
|
||
fi
|
||
|
||
id="$(($id+1))"
|
||
num_worlds="$id"
|
||
done < <(find "${SERVER_WORLD_STORAGE[$1]}" -mindepth 1 -maxdepth 1 -type d -print0)
|
||
fi
|
||
|
||
if [[ -d "${SERVER_WORLD_STORAGE_INACTIVE[$1]}" ]]; then
|
||
# Load inactive worlds
|
||
while IFS= read -r -d $'\0' path; do
|
||
local name="$(basename "$path")"
|
||
server_world_init "$1" "$id" "$name"
|
||
|
||
# Build the server_worlds_inactive comma separated list
|
||
if [[ "$id" == "${SERVER_WORLD_OFFSET[$1]}" ]]; then
|
||
SERVER_WORLDS[$1]="$name"
|
||
else
|
||
SERVER_WORLDS[$1]="${SERVER_WORLDS[$1]}, $name"
|
||
fi
|
||
|
||
id="$(($id+1))"
|
||
num_worlds="$id"
|
||
done < <(find "${SERVER_WORLD_STORAGE_INACTIVE[$1]}" -mindepth 1 -maxdepth 1 -type d -print0)
|
||
fi
|
||
|
||
# Record the number of worlds this server has
|
||
SERVER_NUM_WORLDS[$1]="$(( $id - ${SERVER_WORLD_OFFSET[$1]} ))"
|
||
}
|
||
|
||
# Asserts that a variable has been set in the config file
|
||
# $1: The name of the variable to test
|
||
assert_is_set_in_config() {
|
||
[[ ! ${!1} && ${!1-_} ]] && {
|
||
error_exit CONF_ERROR "$1 must be set in $CONF"
|
||
}
|
||
}
|
||
|
||
# Ensures all non-optional settings have been set
|
||
init_ensure_settings() {
|
||
assert_is_set_in_config USERNAME
|
||
assert_is_set_in_config SERVER_STORAGE_PATH
|
||
assert_is_set_in_config JAR_STORAGE_PATH
|
||
assert_is_set_in_config RAMDISK_STORAGE_PATH
|
||
|
||
assert_is_set_in_config WORLD_ARCHIVE_PATH
|
||
assert_is_set_in_config LOG_ARCHIVE_PATH
|
||
assert_is_set_in_config BACKUP_ARCHIVE_PATH
|
||
|
||
assert_is_set_in_config DEFAULT_SERVER_USER
|
||
assert_is_set_in_config DEFAULT_SCREEN_NAME
|
||
assert_is_set_in_config DEFAULT_WORLD_STORAGE_PATH
|
||
assert_is_set_in_config DEFAULT_WORLD_STORAGE_INACTIVE_PATH
|
||
assert_is_set_in_config DEFAULT_COMPLETE_BACKUP_FOLLOW_SYMLINKS
|
||
|
||
assert_is_set_in_config DEFAULT_LOG
|
||
assert_is_set_in_config DEFAULT_PROPERTIES
|
||
assert_is_set_in_config DEFAULT_WHITELIST
|
||
assert_is_set_in_config DEFAULT_BANNED_PLAYERS
|
||
assert_is_set_in_config DEFAULT_BANNED_IPS
|
||
assert_is_set_in_config DEFAULT_OPS
|
||
|
||
assert_is_set_in_config DEFAULT_JAR
|
||
assert_is_set_in_config DEFAULT_RAM
|
||
assert_is_set_in_config DEFAULT_INVOCATION
|
||
|
||
assert_is_set_in_config DEFAULT_STOP_DELAY
|
||
assert_is_set_in_config DEFAULT_RESTART_DELAY
|
||
assert_is_set_in_config DEFAULT_STOP_MESSAGE
|
||
assert_is_set_in_config DEFAULT_STOP_ABORT
|
||
assert_is_set_in_config DEFAULT_RESTART_MESSAGE
|
||
assert_is_set_in_config DEFAULT_RESTART_ABORT
|
||
assert_is_set_in_config DEFAULT_WORLD_BACKUP_STARTED
|
||
assert_is_set_in_config DEFAULT_WORLD_BACKUP_FINISHED
|
||
assert_is_set_in_config DEFAULT_COMPLETE_BACKUP_STARTED
|
||
assert_is_set_in_config DEFAULT_COMPLETE_BACKUP_FINISHED
|
||
assert_is_set_in_config DEFAULT_CONFIRM_SAVE_ON
|
||
assert_is_set_in_config DEFAULT_CONFIRM_SAVE_OFF
|
||
assert_is_set_in_config DEFAULT_CONFIRM_SAVE_ALL
|
||
assert_is_set_in_config DEFAULT_CONFIRM_START
|
||
assert_is_set_in_config DEFAULT_CONFIRM_KICK
|
||
assert_is_set_in_config DEFAULT_CONFIRM_KICK_FAIL
|
||
assert_is_set_in_config DEFAULT_CONFIRM_TIME_SET
|
||
assert_is_set_in_config DEFAULT_CONFIRM_TIME_SET_FAIL
|
||
assert_is_set_in_config DEFAULT_CONFIRM_TIME_ADD
|
||
assert_is_set_in_config DEFAULT_CONFIRM_TIME_ADD_FAIL
|
||
assert_is_set_in_config DEFAULT_CONFIRM_TOGGLEDOWNFALL
|
||
assert_is_set_in_config DEFAULT_CONFIRM_TOGGLEDOWNFALL_FAIL
|
||
assert_is_set_in_config DEFAULT_CONFIRM_GAMEMODE
|
||
assert_is_set_in_config DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_USER
|
||
assert_is_set_in_config DEFAULT_CONFIRM_GAMEMODE_FAIL_NO_CHANGE
|
||
}
|
||
|
||
init() {
|
||
# Sourcing $CONF will override the previous defaults, with new values
|
||
source "$CONF"
|
||
|
||
init_ensure_settings
|
||
|
||
num_worlds=0
|
||
num_servers=0
|
||
|
||
if [ -d "$SERVER_STORAGE_PATH" ]; then
|
||
local id=0
|
||
while IFS= read -r -d $'\0' path; do
|
||
local name="$(basename "$path")"
|
||
server_init "$id" "$name"
|
||
id="$(($id+1))"
|
||
num_servers="$id"
|
||
done < <(find "$SERVER_STORAGE_PATH" -mindepth 1 -maxdepth 1 -type d -print0)
|
||
fi
|
||
}
|
||
|
||
# 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
|
||
}
|
||
|
||
COMMAND_COUNT=0
|
||
|
||
# Adds a command to the list, allowing it to be called from the command line.
|
||
# $1: The command signature, a coded string describing the structure of the
|
||
# command.
|
||
# $2: The handler function to call, if this command is identified.
|
||
register_command() {
|
||
# Here we build a regular expression which will match any user input
|
||
# that could be passed to the given handler function. It is derrived
|
||
# automatically from the given command signature.
|
||
|
||
local regex="^"
|
||
|
||
# Iterate over each element in the command signature
|
||
for word in $1; do
|
||
# Variables are denoted by angle brackets (e.g. "<variable>") and can
|
||
# at this stage be accepted as any non-zero string
|
||
if [[ "$word" =~ ^\<.*\>$ ]]; then
|
||
regex="${regex}.+ "
|
||
continue
|
||
fi
|
||
|
||
# Sometimes different worlds may be used to call the same command, in
|
||
# these cases, the different words may be written contiguously,
|
||
# separated by the pipe character (i.e. "|") and any of the options
|
||
# provided will be allowed as a match.
|
||
if [[ "$word" =~ \| ]]; then
|
||
regex="${regex}($word) "
|
||
continue
|
||
fi
|
||
|
||
# Anything else found in the command signature will be taken to mean
|
||
# a fixed string, which must be provided to match this command.
|
||
regex="${regex}$word "
|
||
done
|
||
|
||
if [ ${#regex} -ge 1 ]; then
|
||
regex="${regex:0:${#regex}-1}\$"
|
||
|
||
# Sets the global command varibales in order to register this command
|
||
COMMAND_SIGNATURE[$COMMAND_COUNT]="$1"
|
||
COMMAND_REGEX[$COMMAND_COUNT]="$regex"
|
||
COMMAND_HANDLER[$COMMAND_COUNT]="$2"
|
||
|
||
COMMAND_COUNT=$(( $COMMAND_COUNT + 1 ))
|
||
else
|
||
error_exit FATAL_ERROR "Fatal error: Sorry about this, would you be so kind as to file a bug at http://git.io/2f_x-A and cite: \"Erroneous command regex '${regex}' for signature '${1}'\""
|
||
fi
|
||
}
|
||
|
||
# Match and call a command from user input
|
||
# $*: User input
|
||
call_command() {
|
||
for ((i=0; i<$COMMAND_COUNT; i++)); do
|
||
if [[ "$*" =~ ${COMMAND_REGEX[$i]} ]]; then
|
||
unset args
|
||
local word_offset=1
|
||
local args
|
||
local arg_offset=0
|
||
local sid=-1
|
||
local wid=-1
|
||
|
||
# Helper function to build the argument list
|
||
# $1: The argument to push onto the list
|
||
push_arg() {
|
||
args[$arg_offset]="$1"
|
||
arg_offset="$(( $arg_offset + 1 ))"
|
||
}
|
||
|
||
# The following loop builds a set of arguments to pass to the
|
||
# matched command handler function. Rather than passing all args
|
||
# given to the script, to the handler (which may contain constant
|
||
# strings), it only includes variables.
|
||
for word in ${COMMAND_SIGNATURE[$i]}; do
|
||
# Whether a positional argument is a varibale or not is
|
||
# determined by the respective element in the command signature
|
||
# given when registering.
|
||
#
|
||
# This case statement handles each possible type of signature
|
||
# token, and pushes the respective user input onto the stack of
|
||
# arguments.
|
||
case "$word" in
|
||
# The "<string>" token expects any type of string argument,
|
||
# accepting spaces, limited to one argument.
|
||
"<string>")
|
||
# Do no checks, just push the argument onto the stack
|
||
push_arg "${!word_offset}"
|
||
;;
|
||
|
||
|
||
# The "<strings>" token must only be placed at the end of a
|
||
# commadn signature, and allows an arbitrary amount of
|
||
# arguments to be passed to the command handler function.
|
||
"<strings>")
|
||
# Put all remaining user input onto the argument stack
|
||
for input_arg in "${@:$word_offset}"; do
|
||
push_arg "$input_arg"
|
||
done
|
||
# Break from analysing the rest of the input
|
||
break
|
||
;;
|
||
|
||
|
||
# The "<name>" token is similar to "<string>" but adds an
|
||
# extra assurance that the string is a valid name, as used
|
||
# for creating servers and other things.
|
||
"<name>")
|
||
# Check the argument is a valid name and then add push it onto the argument stack
|
||
local specified_name="${!word_offset}"
|
||
|
||
if is_valid_name "$specified_name"; then
|
||
push_arg "$specified_name"
|
||
fi
|
||
;;
|
||
|
||
|
||
# The "<name:server>" token improves on "<name>" by also
|
||
# checking that the server exists, and passing the argument
|
||
# on as the server id, instead of the server name to
|
||
# command handler functions.
|
||
"<name:server>")
|
||
local specified_name="${!word_offset}"
|
||
if [[ "$specified_name" == "all" ]]; then
|
||
# Do for all servers
|
||
sid="server:all"
|
||
else
|
||
if is_valid_name "$specified_name"; then
|
||
if [ -d "$SERVER_STORAGE_PATH/$specified_name" ]; then
|
||
sid="$(server_get_id "$specified_name")"
|
||
fi
|
||
fi
|
||
|
||
if [[ "$sid" -eq "-1" ]]; then
|
||
error_exit NAME_NOT_FOUND "There is no server with the name \"$specified_name\"."
|
||
fi
|
||
fi
|
||
|
||
push_arg "$sid"
|
||
;;
|
||
|
||
|
||
# The "<name:world>" token also improves upon "<name>" by
|
||
# ensuring that the world actually exists, and passes the
|
||
# argument on to command handlers as the world ID, rather
|
||
# than the original world name input by the user.
|
||
"<name:world>")
|
||
local specified_name="${!word_offset}"
|
||
|
||
if [[ "$sid" -eq "-1" ]]; then
|
||
# Server id not set yet
|
||
exit_error 1 "Ill-defined command $*. Please file an issue by opening the following link: https://github.com/marcuswhybrow/minecraft-server-manager/issues"
|
||
fi
|
||
|
||
if [[ "$sid" -eq "-2" ]]; then
|
||
if [[ "$specified_name" == "all" ]]; then
|
||
wid="world:all"
|
||
else
|
||
exit_error INVALID_ARGUMENT "When specifying \"all\" servers, \"all\" worlds must be specified also."
|
||
fi
|
||
fi
|
||
|
||
if [[ "$sid" -ge "0" ]]; then
|
||
if is_valid_name "$specified_name"; then
|
||
if [ -d "${SERVER_WORLD_ACTIVE_PATH[$sid]}/$specified_name" ] || [ -d "${SERVER_WORLD_INACTIVE_PATH[$sid]}/$specified_name" ]; then
|
||
wid="$(server_world_get_id "$sid" "$specified_name")"
|
||
fi
|
||
fi
|
||
|
||
if [ -z "$wid" ]; then
|
||
error_exit NAME_NOT_FOUND "There is no world with the name \"$specified_name\"."
|
||
fi
|
||
|
||
push_arg "$wid"
|
||
fi
|
||
;;
|
||
esac
|
||
|
||
word_offset=$(( $word_offset + 1 ))
|
||
done
|
||
|
||
# The argument list for the call to the command handler has been
|
||
# built. But there are several ways to call a handler. Either just
|
||
# once, or multiple times based upon if multiple servers or worlds
|
||
# were specified.
|
||
|
||
|
||
# This code block calls the handler for all possible servers and
|
||
# all possible worlds.
|
||
if [[ "$sid" == "server:all" ]] && [[ "$wid" == "world:all" ]]; then
|
||
for ((j=0; j<$num_worlds; j++)); do
|
||
# Replace server and world id placeholders with actual id's
|
||
local replaced_args
|
||
for k in ${!args[@]}; do
|
||
replaced_args[$k]="${args[$k]//server:all/${WORLD_SERVER_ID[$j]}}"
|
||
replaced_args[$k]="${args[$k]//world:all/$j}"
|
||
done
|
||
|
||
# Call the function with the specific replaced args
|
||
${COMMAND_HANDLER[$i]} "${replaced_args[@]}"
|
||
done
|
||
|
||
# Prevent the default singular call later on.
|
||
return
|
||
fi
|
||
|
||
# This calls the handler for all possible servers, and preserves
|
||
# all other arguments.
|
||
if [[ "$sid" == "server:all" ]]; then
|
||
for ((j=0; j<$num_servers; j++)); do
|
||
local replaced_args
|
||
for k in ${!args[@]}; do
|
||
replaced_args[$k]="${args[$k]//server\:all/$j}"
|
||
done
|
||
|
||
${COMMAND_HANDLER[$i]} "${replaced_args[@]}"
|
||
done
|
||
|
||
return
|
||
fi
|
||
|
||
# This calls the handlers for all possible worlds for a specific
|
||
# server.
|
||
if [[ "$sid" != "server:all" ]] && [[ "$wid" == "world:all" ]]; then
|
||
for ((j=${SERVER_WORLD_OFFSET[$sid]}; j<${SERVER_NUM_WORLDS[$sid]}; j++)); do
|
||
local replaced_args
|
||
for k in ${!args[@]}; do
|
||
replaced_args[$k]="${args[$k]//world:all/$j}"
|
||
done
|
||
|
||
${COMMAND_HANDLER[$i]} "${replaced_args[@]}"
|
||
done
|
||
|
||
return
|
||
fi
|
||
|
||
# Otherwise it's a simple single call of the handler.
|
||
${COMMAND_HANDLER[$i]} "${args[@]}"
|
||
return
|
||
fi
|
||
done
|
||
|
||
echo "No such command. See $0 help"
|
||
}
|
||
|
||
# The main function which starts the script
|
||
main() {
|
||
# Initialises variables that represent system state
|
||
init
|
||
|
||
# Trap interrupts to the script by calling the interrupt function
|
||
trap interrupt EXIT
|
||
|
||
# The following section registers commands to be available for use. The
|
||
# register_command function accepts a command_signature and a
|
||
# command_handler_function_name as positional arguments 1 and 2
|
||
# respectively.
|
||
#
|
||
# A command signature consists of multiple elements separated by spaces,
|
||
# the available options are as follows:
|
||
#
|
||
# fixedstring Matches an argument containing the specified
|
||
# characters, in this case the characters "fixedstring"
|
||
#
|
||
# <string> Same as "fixedstring", but is variable and the value
|
||
# is passed to the handler function as a positional
|
||
# argument
|
||
#
|
||
# <strings> Same as "<string>", but matches multiple arguments,
|
||
# must be final element
|
||
#
|
||
# <name> Same as "<string>", also ensures it's a valid name
|
||
# using the is_valid_name function
|
||
#
|
||
# <name:server> Same as "<name>", also converts value to server id or
|
||
# fails if the server does not exist
|
||
#
|
||
# <name:world> Same as "<name>", also converts value to world id or
|
||
# fails if the world does not exist. Must only be
|
||
# included after a "<name:server>" element.
|
||
#
|
||
# Elements listed above encapsulated within angle brackets must be included
|
||
# within a signature verbatim, as apposed to the "fixedstring" element
|
||
# which is arbitrary.
|
||
#
|
||
# Variables passed to handler functions are of course positional and there
|
||
# position matches the position of that element in the command signature.
|
||
|
||
register_command "start" "command_start"
|
||
register_command "stop" "command_stop"
|
||
register_command "stop now" "command_stop_now"
|
||
register_command "restart" "command_restart"
|
||
register_command "restart now" "command_restart_now"
|
||
register_command "version" "command_version"
|
||
register_command "server list" "command_server_list"
|
||
register_command "server create <name>" "command_server_create"
|
||
register_command "server delete <name>" "command_server_delete"
|
||
register_command "server rename <name> <name>" "command_server_rename"
|
||
register_command "jargroup list" "command_jargroup_list"
|
||
register_command "jargroup create <name> <string>" "command_jargroup_create"
|
||
register_command "jargroup delete <name>" "command_jargroup_delete"
|
||
register_command "jargroup rename <name> <name>" "command_jargroup_rename"
|
||
register_command "jargroup changetarget <name> <string>" "command_jargroup_changetarget"
|
||
register_command "jargroup getlatest <name>" "command_jargroup_getlatest"
|
||
register_command "help" "command_help"
|
||
register_command "<name:server> start" "command_server_start"
|
||
register_command "<name:server> stop" "command_server_stop"
|
||
register_command "<name:server> stop now" "command_server_stop_now"
|
||
register_command "<name:server> restart" "command_server_restart"
|
||
register_command "<name:server> restart now" "command_server_restart_now"
|
||
register_command "<name:server> status" "command_server_status"
|
||
register_command "<name:server> connected" "command_server_connected"
|
||
register_command "<name:server> worlds list" "command_server_worlds_list"
|
||
register_command "<name:server> worlds load" "command_server_worlds_load"
|
||
register_command "<name:server> worlds ram <name:world>" "command_server_worlds_ram"
|
||
register_command "<name:server> worlds todisk" "command_server_worlds_todisk"
|
||
register_command "<name:server> worlds backup" "command_server_worlds_backup"
|
||
register_command "<name:server> worlds on <name:world>" "command_server_worlds_on"
|
||
register_command "<name:server> worlds off <name:world>" "command_server_worlds_off"
|
||
register_command "<name:server> logroll" "command_server_logroll"
|
||
register_command "<name:server> backup" "command_server_backup"
|
||
register_command "<name:server> jar <name>" "command_server_jar"
|
||
register_command "<name:server> jar <name> <name>" "command_server_jar"
|
||
register_command "<name:server> whitelist|wl on" "command_server_whitelist_on"
|
||
register_command "<name:server> whitelist|wl off" "command_server_whitelist_off"
|
||
register_command "<name:server> whitelist|wl add <string>" "command_server_whitelist_add"
|
||
register_command "<name:server> whitelist|wl remove <string>" "command_server_whitelist_remove"
|
||
register_command "<name:server> whitelist|wl list" "command_server_whitelist_list"
|
||
register_command "<name:server> blacklist|bl player add <string>" "command_server_blacklist_player_add"
|
||
register_command "<name:server> blacklist|bl player remove <string>" "command_server_blacklist_player_remove"
|
||
register_command "<name:server> blacklist|bl ip add <string>" "command_server_blacklist_ip_add"
|
||
register_command "<name:server> blacklist|bl ip remove <string>" "command_server_blacklist_ip_remove"
|
||
register_command "<name:server> blacklist|bl list" "command_server_blacklist_list"
|
||
register_command "<name:server> operator|op add <string>" "command_server_operator_add"
|
||
register_command "<name:server> operator|op remove <string>" "command_server_operator_remove"
|
||
register_command "<name:server> operator|op list" "command_server_operator_list"
|
||
register_command "<name:server> gamemode|gm survival|creative|0|1 <string>" "command_server_gamemode"
|
||
register_command "<name:server> kick <string>" "command_server_kick"
|
||
register_command "<name:server> say <strings>" "command_server_say"
|
||
register_command "<name:server> time set <string>" "command_server_time_set"
|
||
register_command "<name:server> time add <string>" "command_server_time_add"
|
||
register_command "<name:server> toggledownfall|tdf" "command_server_toggledownfall"
|
||
register_command "<name:server> save on" "command_server_save_on"
|
||
register_command "<name:server> save off" "command_server_save_off"
|
||
register_command "<name:server> save all" "command_server_save_all"
|
||
register_command "<name:server> cmd" "command_server_cmd"
|
||
register_command "<name:server> cmdlog" "command_server_cmdlog"
|
||
register_command "<name:server> console" "command_server_console"
|
||
|
||
# This function call matches the user input to a registered command
|
||
# signature, and then calls that commands handler function with positional
|
||
# arguments containing any variable strings.
|
||
call_command "$@"
|
||
}
|
||
|
||
|
||
### Start point
|
||
|
||
main "$@"
|
||
exit 0
|