diff options
author | Gordian Edenhofer | 2016-02-29 21:35:38 +0100 |
---|---|---|
committer | Gordian Edenhofer | 2016-02-29 21:35:38 +0100 |
commit | f312efaac7a9714c82cd506027c342a41e66e211 (patch) | |
tree | 6c3e41efc24e49cda37190da87854f02c654b45d /minecraftd.sh | |
parent | 8a419426041fecec19b4a9b97bcdaf8c0bb9a6be (diff) | |
download | aur-f312efaac7a9714c82cd506027c342a41e66e211.tar.gz |
Upgpkg: 1.9-1
Add suspending capability for the minecraft server through the control
script and an additional screen session which listens on the same port
with netcat.
* Update .conf accordingly
* IDLE_SERVER can be either true or false
* The default is no idle server
* Choose an available netcat flavor automatically
Variable renaming in .install.
Notify the user of kept directories on post_remove and alter ownership
to root before removing the game user.
Print error messages to stderr.
Diffstat (limited to 'minecraftd.sh')
-rwxr-xr-x | minecraftd.sh | 264 |
1 files changed, 196 insertions, 68 deletions
diff --git a/minecraftd.sh b/minecraftd.sh index 8743bd746654..a44d84e6b493 100755 --- a/minecraftd.sh +++ b/minecraftd.sh @@ -1,72 +1,177 @@ #!/bin/bash -source /etc/conf.d/minecraft || echo "Could not source /etc/conf.d/minecraft" +# The actual program name +declare -r myname="minecraftd" +declare -r game="minecraft" # General rule for the variable-naming-schema: # Variables in capital letters may be passed through the command line others not. - -# You may use this script for any minecraft server of your choice, just alter the config file -SERVER_ROOT="${SERVER_ROOT:-/srv/minecraft}" -BACKUPPATH="${BACKUPPATH:-/srv/minecraft/backup}" -LOGPATH="${LOGPATH:-/srv/minecraft/logs}" -WORLDPATHS="${WORLDPATHS:-world}" -KEEP_BACKUPS="${KEEP_BACKUPS:-10}" -MC_USER="${MC_USER:-minecraft}" -MAIN_JAR="${MAIN_JAR:-minecraft_server.jar}" -SESSION_NAME="${SESSION_NAME:-minecraft}" - -# Specify system parameters for the minecraft server -MINHEAP="${MINHEAP:-512M}" -MAXHEAP="${MAXHEAP:-1024M}" -THREADS="${THREADS:-1}" -JAVA_PARMS="${JAVA_PARMS:-"-Xmx${MAXHEAP} -Xms${MINHEAP} -XX:ParallelGCThreads=${THREADS}"}" - -# The actual program name -declare -r myname="minecraftd" +# Avoid altering any of those later in the code since they may be readonly. + +# You may use this script for any game server of your choice, just alter the config file +[[ ! -z "${SERVER_ROOT}" ]] && declare -r SERVER_ROOT=${SERVER_ROOT} || SERVER_ROOT="/srv/${game}" +[[ ! -z "${BACKUP_DEST}" ]] && declare -r BACKUP_DEST=${BACKUP_DEST} || BACKUP_DEST="/srv/${game}/backup" +[[ ! -z "${LOGPATH}" ]] && declare -r LOGPATH=${LOGPATH} || LOGPATH="/srv/${game}/logs" +[[ ! -z "${BACKUP_PATHS}" ]] && declare -r BACKUP_PATHS=${BACKUP_PATHS} || BACKUP_PATHS="world" +[[ ! -z "${KEEP_BACKUPS}" ]] && declare -r KEEP_BACKUPS=${KEEP_BACKUPS} || KEEP_BACKUPS="10" +[[ ! -z "${GAME_USER}" ]] && declare -r GAME_USER=${GAME_USER} || GAME_USER="minecraft" +[[ ! -z "${MAIN_EXECUTABLE}" ]] && declare -r MAIN_EXECUTABLE=${MAIN_EXECUTABLE} || MAIN_EXECUTABLE="minecraft_server.jar" +[[ ! -z "${SESSION_NAME}" ]] && declare -r SESSION_NAME=${SESSION_NAME} || SESSION_NAME="${game}" + +# System parameters for java +[[ ! -z "${MINHEAP}" ]] && declare -r MINHEAP=${MINHEAP} || MINHEAP="512M" +[[ ! -z "${MAXHEAP}" ]] && declare -r MAXHEAP=${MAXHEAP} || MAXHEAP="1024M" +[[ ! -z "${THREADS}" ]] && declare -r THREADS=${THREADS} || THREADS="1" +[[ ! -z "${JAVA_PARMS}" ]] && declare -r JAVA_PARMS=${JAVA_PARMS} || JAVA_PARMS="-Xmx${MAXHEAP} -Xms${MINHEAP} -XX:ParallelGCThreads=${THREADS}" + +# System parameters for the control script +[[ ! -z "${IDLE_SERVER}" ]] && declare -r IDLE_SERVER=${IDLE_SERVER} || IDLE_SERVER="false" +[[ ! -z "${IDLE_SESSION_NAME}" ]] && declare -r IDLE_SESSION_NAME=${IDLE_SESSION_NAME} || IDLE_SESSION_NAME="idle_server" +[[ ! -z "${GAME_PORT}" ]] && declare -r GAME_PORT=${GAME_PORT} || GAME_PORT="25565" +[[ ! -z "${CHECK_PLAYER_TIME}" ]] && declare -r CHECK_PLAYER_TIME=${CHECK_PLAYER_TIME} || CHECK_PLAYER_TIME="30" +[[ ! -z "${IDLE_IF_TIME}" ]] && declare -r IDLE_IF_TIME=${IDLE_IF_TIME} || IDLE_IF_TIME="1200" + +# Variables passed over the command line will always override the one from a config file +source /etc/conf.d/minecraft || echo "Could not source /etc/conf.d/minecraft" # Check whether sudo is needed at all -if [[ $(whoami) == ${MC_USER} ]]; then +if [[ $(whoami) == ${GAME_USER} ]]; then SUDO_CMD="" else - SUDO_CMD="sudo -u ${MC_USER}" + SUDO_CMD="sudo -u ${GAME_USER}" +fi + +# Choose which flavor of netcat is to be used +if which netcat &> /dev/null; then + NETCAT_CMD="netcat" +elif which ncat &> /dev/null; then + NETCAT_CMD="ncat" +else + NETCAT_CMD="" fi # Check for sudo rigths -if [[ $(${SUDO_CMD} whoami) != ${MC_USER} ]]; then - echo -e "You have \e[39;1mno permission\e[0m to run commands as $MC_USER user." - exit 1 +if [[ $(${SUDO_CMD} whoami) != ${GAME_USER} ]]; then + >&2 echo -e "You have \e[39;1mno permission\e[0m to run commands as $GAME_USER user." + exit 21 fi -# Pipe any given argument to the minecraft server console -mc_command() { +# Pipe any given argument to the game server console +game_command() { ${SUDO_CMD} screen -S "${SESSION_NAME}" -X stuff "`printf \"$*\r\"`" } +# Check whether the server is visited by a player otherwise shut it down +idle_server_daemon() { + # This function is run within a screen session of the GAME_USER therefore SUDO_CMD can be omitted + if [[ $(whoami) != ${GAME_USER} ]]; then + >&2 echo "Somehow this hidden function was not executed by the ${GAME_USER} user." + >&2 echo "This should not have happend. Are you messing around with this script? :P" + exit 22 + fi + + # Time in seconds for which no player was on the server + no_player=0 + + while true; do + screen -S "${SESSION_NAME}" -Q select . > /dev/null + if [[ $? -eq 0 ]]; then + # Game server is up and running + screen -S "${SESSION_NAME}" -X stuff "`printf \"list\r\"`" + # The "player_delimiter" in awk print needs to be 6 for the spigot server + # since the according information is contained in the 6th not in th 4th column + if [[ -z $(tail -n 1 "${LOGPATH}/latest.log" | awk '{ print $4 }') ]]; then + # No player was seen on the server through list + no_player=$((no_player + CHECK_PLAYER_TIME)) + # Stop the game server if no player was active for at least ${IDLE_IF_TIME} + [[ "${no_player}" -ge "${IDLE_IF_TIME}" ]] && IDLE_SERVER="false" ${myname} stop + else + no_player=0 + # Retry in ${CHECK_PLAYER_TIME} seconds + sleep ${CHECK_PLAYER_TIME} + fi + else + # Game server is down, listen on port ${GAME_PORT} for incoming connections + echo "Netcat is listening on port ${GAME_PORT} for incoming connections..." + ${NETCAT_CMD} -v -l ${GAME_PORT} + echo "Netcat caught an connection. The server is coming up again...." + IDLE_SERVER="false" ${myname} start + sleep ${CHECK_PLAYER_TIME} + fi + done +} + # Start the server if it is not already running server_start() { + # Start the game server ${SUDO_CMD} screen -S "${SESSION_NAME}" -Q select . > /dev/null if [[ $? -eq 0 ]]; then echo "A screen ${SESSION_NAME} session is already running. Please close it first." else echo -en "Starting server... " - ${SUDO_CMD} screen -dmS "${SESSION_NAME}" /bin/bash -c "cd '${SERVER_ROOT}'; java ${JAVA_PARMS} -jar '${SERVER_ROOT}/${MAIN_JAR}' nogui" + ${SUDO_CMD} screen -dmS "${SESSION_NAME}" /bin/bash -c "cd '${SERVER_ROOT}'; java ${JAVA_PARMS} -jar '${SERVER_ROOT}/${MAIN_EXECUTABLE}' nogui" echo -e "\e[39;1m done\e[0m" fi + + if [[ "${IDLE_SERVER}" == "true" ]]; then + # Check for the availability of the netcat (nc) binaries + if [[ -z "${NETCAT_CMD}" ]]; then + >&2 echo "The netcat binaries are needed for suspending an idle server." + exit 12 + fi + + # Start the idle server daemon + ${SUDO_CMD} screen -S "${IDLE_SESSION_NAME}" -Q select . > /dev/null + if [[ $? -eq 0 ]]; then + echo "An idles server screen session called ${IDLE_SESSION_NAME} is already running. Please close it first." + else + echo -en "Starting idle server daeomon... " + ${SUDO_CMD} screen -dmS "${IDLE_SESSION_NAME}" /bin/bash -c "${myname} idle_server_daemon" + echo -e "\e[39;1m done\e[0m" + fi + else + # Though IDLE_SERVER is not set to true it could still be running and just have not noticed that the + # server was started, e.g. by manually triggering server_start again. Therefore reset the idle daemon. + ${SUDO_CMD} screen -S "${IDLE_SESSION_NAME}" -Q select . > /dev/null + if [[ $? -eq 0 ]]; then + ${SUDO_CMD} screen -S "${IDLE_SESSION_NAME}" -X quit + fi + ${SUDO_CMD} screen -dmS "${IDLE_SESSION_NAME}" /bin/bash -c "${myname} idle_server_daemon" + fi } # Stop the server gracefully by saving everything prior and warning the users server_stop() { + # Quit the idle daemon + if [[ "${IDLE_SERVER}" == "true" ]]; then + # Check for the availability of the netcat (nc) binaries + if [[ -z "${NETCAT_CMD}" ]]; then + >&2 echo "The netcat binaries are needed for suspending an idle server." + exit 12 + fi + + ${SUDO_CMD} screen -S "${IDLE_SESSION_NAME}" -Q select . > /dev/null + if [[ $? -eq 0 ]]; then + echo -en "Stopping idle server daemon... " + ${SUDO_CMD} screen -S "${IDLE_SESSION_NAME}" -X quit + echo -e "\e[39;1m done\e[0m" + else + echo "The corresponding screen session for ${IDLE_SESSION_NAME} was already dead." + fi + fi + + # Gracefully exit the game server ${SUDO_CMD} screen -S "${SESSION_NAME}" -Q select . > /dev/null if [[ $? -eq 0 ]]; then - mc_command save-all - mc_command say "Server is going down in 10 seconds! HURRY UP WITH WATHEVER YOU ARE DOING!" # Warning the users + game_command save-all + game_command say "Server is going down in 10 seconds! HURRY UP WITH WHATEVER YOU ARE DOING!" # Warning the users echo -en "Server is going down in... " for i in $(seq 1 10); do - mc_command say "down in... $(expr 10 - $i)" + game_command say "down in... $(expr 10 - $i)" echo -n " $(expr 10 - $i)" sleep 1 done - mc_command stop + game_command stop echo -e "\e[39;1m done\e[0m" else echo "The corresponding screen session for ${SESSION_NAME} was already dead." @@ -75,12 +180,29 @@ server_stop() { # Print whether the server is running and if so give some information about memory usage and threads server_status() { + # Print status information about the idle daemon + if [[ "${IDLE_SERVER}" == "true" ]]; then + # Check for the availability of the netcat (nc) binaries + if [[ -z "${NETCAT_CMD}" ]]; then + >&2 echo "The netcat binaries are needed for suspending an idle server." + exit 12 + fi + + ${SUDO_CMD} screen -S "${IDLE_SESSION_NAME}" -Q select . > /dev/null + if [[ $? -eq 0 ]]; then + echo -e "Idle server daemon status:\e[39;1m running\e[0m" + else + echo -e "Idle server daemon status:\e[39;1m stopped\e[0m" + fi + fi + + # Print status information for the game server ${SUDO_CMD} screen -S "${SESSION_NAME}" -Q select . > /dev/null if [[ $? -eq 0 ]]; then echo -e "Status:\e[39;1m running\e[0m" # Calculating memory usage - for p in $(${SUDO_CMD} pgrep -f "${MAIN_JAR}"); do + for p in $(${SUDO_CMD} pgrep -f "${MAIN_EXECUTABLE}"); do ps -p${p} -O rss | tail -n1; done | gawk '{ count ++; sum += $2 }; END {count --; print "Number of processes =", count, "(screen, bash,", count-2, "x java)"; print "Total memory usage =", sum/1024, "MB" ;};' else @@ -100,34 +222,34 @@ server_restart() { fi } -# Backup the directories specified in $WORLDPATHS +# Backup the directories specified in BACKUP_PATHS backup_files() { # Check for the availability of the tar binaries which tar &> /dev/null if [[ $? -ne 0 ]]; then - echo "The tar binaries are needed for a backup." - exit 2 + >&2 echo "The tar binaries are needed for a backup." + exit 11 fi echo "Starting backup..." FILE="$(date +%Y_%m_%d_%H.%M.%S).tar.gz" - ${SUDO_CMD} mkdir -p "${BACKUPPATH}" + ${SUDO_CMD} mkdir -p "${BACKUP_DEST}" ${SUDO_CMD} screen -S "${SESSION_NAME}" -Q select . > /dev/null if [[ $? -eq 0 ]]; then - mc_command save-off - mc_command save-all + game_command save-off + game_command save-all sync && wait - ${SUDO_CMD} tar -C "${SERVER_ROOT}" -czf "${BACKUPPATH}/${FILE}" --totals ${WORLDPATHS} 2>&1 | grep -v "tar: Removing leading " - mc_command save-on + ${SUDO_CMD} tar -C "${SERVER_ROOT}" -czf "${BACKUP_DEST}/${FILE}" --totals ${BACKUP_PATHS} 2>&1 | grep -v "tar: Removing leading " + game_command save-on else - ${SUDO_CMD} tar -C "${SERVER_ROOT}" -czf "${BACKUPPATH}/${FILE}" --totals ${WORLDPATHS} 2>&1 | grep -v "tar: Removing leading " + ${SUDO_CMD} tar -C "${SERVER_ROOT}" -czf "${BACKUP_DEST}/${FILE}" --totals ${BACKUP_PATHS} 2>&1 | grep -v "tar: Removing leading " fi echo -e "\e[39;1mbackup completed\e[0m\n" echo -n "Only keeping the last ${KEEP_BACKUPS} backups and removing the other ones..." - BACKUP_COUNT=$(for f in "${BACKUPPATH}"/[0-9_.]*; do echo ${f}; done | wc -l) + BACKUP_COUNT=$(for f in "${BACKUP_DEST}"/[0-9_.]*; do echo ${f}; done | wc -l) if [[ $(expr ${BACKUP_COUNT} - ${KEEP_BACKUPS}) -gt 0 ]]; then - ${SUDO_CMD} rm $(for f in "${BACKUPPATH}"/[0-9_.]*; do echo ${f}; done | head -n$(expr ${BACKUP_COUNT} - ${KEEP_BACKUPS})) + ${SUDO_CMD} rm $(for f in "${BACKUP_DEST}"/[0-9_.]*; do echo ${f}; done | head -n$(expr ${BACKUP_COUNT} - ${KEEP_BACKUPS})) echo -e "\e[39;1m done\e[0m ($(expr ${BACKUP_COUNT} - ${KEEP_BACKUPS}) backup(s) pruned)" else echo -e "\e[39;1m done\e[0m (no backups pruned)" @@ -139,14 +261,14 @@ backup_restore() { # Check for the availability of the tar binaries which tar &> /dev/null if [[ $? -ne 0 ]]; then - echo "The tar binaries are needed for a backup." - exit 2 + >&2 echo "The tar binaries are needed for a backup." + exit 11 fi # Only allow the user to restore a backup if the server is down ${SUDO_CMD} screen -S "${SESSION_NAME}" -Q select . > /dev/null if [[ $? -eq 0 ]]; then - echo -e "The \e[39;1mserver should be down\e[0m in order to restore the world data." + >&2 echo -e "The \e[39;1mserver should be down\e[0m in order to restore the world data." exit 3 fi @@ -154,7 +276,7 @@ backup_restore() { if [[ $# -lt 1 ]]; then echo "Please enter the corresponding number for the backup to be restored: " i=1 - for f in "${BACKUPPATH}"/[0-9_.]*; do + for f in "${BACKUP_DEST}"/[0-9_.]*; do echo -e " \e[39;1m$i)\e[0m\t$f" i=$((i+1)) done @@ -166,16 +288,16 @@ backup_restore() { # Interpeting the input if [[ $user_choice =~ ^-?[0-9]+$ ]]; then n=1 - for f in "${BACKUPPATH}"/[0-9_.]*; do + for f in "${BACKUP_DEST}"/[0-9_.]*; do [[ ${n} -eq $user_choice ]] && FILE="$f" n=$((n+1)) done if [[ -z $FILE ]]; then - echo -e "\e[39;1mFailed\e[0m to interpret your input. Please enter the digit of the presented options." + >&2 echo -e "\e[39;1mFailed\e[0m to interpret your input. Please enter the digit of the presented options." exit 5 fi else - echo -e "\e[39;1mFailed\e[0m to interpret your input. Please enter a valid digit for one of the presented options." + >&2 echo -e "\e[39;1mFailed\e[0m to interpret your input. Please enter a valid digit for one of the presented options." exit 6 fi elif [[ $# -eq 1 ]]; then @@ -183,17 +305,17 @@ backup_restore() { if [[ -f "$1" ]]; then FILE="$1" else - if [[ -f "${BACKUPPATH}"/"$1" ]]; then - FILE="${BACKUPPATH}"/"$1" + if [[ -f "${BACKUP_DEST}"/"$1" ]]; then + FILE="${BACKUP_DEST}"/"$1" else - echo -e "Sorry, but '$1', is \e[39;1mnot a valid file\e[0m, neither in your current directory nor in the backup folder." + >&2 echo -e "Sorry, but '$1', is \e[39;1mnot a valid file\e[0m, neither in your current directory nor in the backup folder." exit 4 fi fi elif [[ $# -gt 1 ]]; then - echo -e "\e[39;1mToo many arguments.\e[0m Please pass only the filename for the world data as an argument." - echo "Or alternatively no arguments at all to chose from a list of available backups." - exit 1 + >&2 echo -e "\e[39;1mToo many arguments.\e[0m Please pass only the filename for the world data as an argument." + >&2 echo "Or alternatively no arguments at all to chose from a list of available backups." + exit 7 fi echo "Restoring backup..." @@ -205,10 +327,10 @@ backup_restore() { fi } -# Run the given comman at the minecraft server console +# Run the given comman at the game server console server_command() { if [[ $# -lt 1 ]]; then - echo "No server command specified. Try 'help' for a list of commands." + >&2 echo "No server command specified. Try 'help' for a list of commands." exit 1 fi @@ -216,14 +338,14 @@ server_command() { if [[ $? -eq 0 ]]; then sleep 0.1s & sleep_pid=$! - mc_command "$@" & + game_command "$@" & tail -f --pid=${sleep_pid} -n 0 "${LOGPATH}/latest.log" else echo "There is no ${SESSION_NAME} session to connect to." fi } -# Enter the screen minecraft session +# Enter the screen game session server_console() { ${SUDO_CMD} screen -S "${SESSION_NAME}" -Q select . > /dev/null if [[ $? -eq 0 ]]; then @@ -236,17 +358,17 @@ server_console() { # Help function, no arguments required help() { cat <<-EOF - This script was design to easily control any minecraft server. Quite every parameter for a given - minecraft server derivative can be altered by editing the variables in the configuration file. + This script was design to easily control any ${game} server. Quite every parameter for a given + ${game} server derivative can be altered by editing the variables in the configuration file. - Usage: $myname {start|stop|status|backup|restore|command <command>|console} - start Start the minecraft server - stop Stop the minecraft server - restart Restart the minecraft server + Usage: ${myname} {start|stop|status|backup|restore|command <command>|console} + start Start the ${game} server + stop Stop the ${game} server + restart Restart the ${game} server status Print some status information backup Backup the world data restore [filename] Restore the world data from a backup - command <command> Run the given comman at the minecraft server console + command <command> Run the given comman at the ${game} server console console Enter the server console through a screen session Copyright (c) Gordian Edenhofer <gordian.edenhofer@gmail.com> @@ -286,8 +408,14 @@ case "$1" in backup_restore "${@:2}" ;; + idle_server_daemon) + # This shell be a hidden function which should only be invoced internally + idle_server_daemon + ;; + *|-h|--help) help + exit 0 esac exit 0 |