#!/bin/bash # Copyright 2021, Price Hiller - All Rights Reserved # # Copying, distribution, usage, or modification of this file or the following source code, via any # method is strictly prohibited without the explicit written consent of Price Hiller (philler3138@gmail.com) # The following file contains proprietary and confidential content # ### CONSTANTS ### #gigabytes MAX_MEM=8 #gigabytes INITIAL_MEM=2 #rcon password DEFAULT_RCON_PASSWORD="bqMLwxCJKRktrQoir2pg4KkTeBDQLjb4C+RYesdmeKF4sie8" echo_rgb() { # Echo a colored string to the terminal based on rgb values # # Positional Arguments: # # message # - The message to be printed to stdout # red # - The red value from 0 to 255 # green # - The green value from 0 to 255 # blue # - The blue value from 0 to 255 # # Usage: # echo_rgb "Yep" 10 8 30 # # POSIX Compliant: # N/A # local red local green local blue local input input="${1}" red="${2}" green="${3}" blue="${4}" printf "\e[0;38;2;%s;%s;%sm%s\e[m\n" "${red}" "${green}" "${blue}" "${input}" } log() { # Print a message and send it to stdout or stderr depending upon log level, also configurable with debug etc. # # Arguments: # level # - The log level, defined within a case check in this function # message # - The info message # line_number # - The line number of the calling function (${LINNO}) # # Usage: # log "info" "Could not find that directory" # # POSIX Compliant: # Yes # # Set debug status depending if a global debug variable has been set to either 1 or 0 local debug if [ ${DEBUG} ]; then debug=${DEBUG} else debug=0 fi local FORMAT FORMAT="[$(echo_rgb "$(date +%Y-%m-%dT%H:%M:%S)" 180 140 255)]" # Convert the level to uppercase local level level=$(echo "${1}" | tr '[:lower:]' '[:upper:]') local message message="${2}" case "${level}" in INFO) # Output all info log levels to stdout printf "${FORMAT}[$(echo_rgb "INFO" 0 140 255)] %s\n" "${message}" >&1 return 0 ;; WARN | WARNING) # Output all info log levels to stdout printf "${FORMAT}[$(echo_rgb "WARNING" 255 255 0)] %s\n" "${message}" >&1 return 0 ;; DEBUG) [[ ${debug} == 0 ]] && return printf "${FORMAT}[$(echo_rgb "DEBUG" 0 160 110)] %s\n" "${message}" >&1 return 0 ;; ERROR) # Output all error log levels to stderr printf "${FORMAT}[$(echo_rgb "ERROR" 255 0 0)] %s\n" "${message}" >&2 return 0 ;; # Further log levels can be added by extending this switch statement with more comparisons *) # Default case, no matches # Returns non-zero code as an improper log option was passed, this helps with using `set -e` printf "${FORMAT}[ERROR] %s\n" "Invalid log level passed, received level \"${level}\" with message \"${message}\"" >&2 return 1 ;; esac } confirmation() { # Receive confirmation from user as y, Y, n, or N # returns 0 when answer is yes and 1 when answer is no # # Arguments: # message # - The confirmation prompt sent to the user, for example: # Would you like to overwrite foobar.txt (y/N)? # # Usage: # confirmation "Some prompt" # - Sends "Some prompt" to the user and gets their input # # POSIX Compliant: # Yes # local message message="${1}" local choice while true; do read -p "${message} " -n 1 -r choice case "$choice" in y | Y) echo "" return 0 ;; n | N) echo "" return 1 ;; *) echo -e "\nInput must be either y, Y, n, or N" ;; esac done } usage() { # Print out usage instructions for the local script # # Arguments: # None # # Usage: # usage # # POSIX Compliant: # Yes # printf "Usage: %s\n" \ "$(basename "${0}") -s | $(basename "${0}") -s -r | $(basename "${0}") -s -i -s | --server Which minecraft server to start, see the ~/Minecraft directory -- each number corresponds to an ID Example: --server 1 -r | --rcon-ignore Flag that takes no parameters -- when enabled this script will not overwrite the RCON password in the targeted server Example: --rcon-ignore -i | --install Installs a minecraft server to the given server id from the -s flag with the given revision to this arguemnt. To see all the revisions visit https://www.spigotmc.org/wiki/buildtools/ Example: --install latest" } server_id="" revision="" rcon_ignore=0 parse_args() { # Parse input arguments # # Arguments: # Consult the `usage` function # # Usage: # parse_args "$@" # - All arguments should be ingested by parse_args first for variable setting # # POSIX Compliant: # Yes # while :; do case ${1} in -h | -\? | --help) usage # Display a usage synopsis. exit ;; --) # End of all options. break ;; -s | --server) shift server_id="${1}" [[ -z "${server_id}" ]] \ && log "error" "No server ID provided" \ && exit 1 [[ "${server_id}" =~ [0-9] ]] || (log "error" "Server ID must be a number, received: ${server_id}" && exit 1) ;; -i | --install) shift revision="${1}" [[ -z "${revision}" ]] \ && log "error" "The install flag was given, but no argument was received" \ && exit 1 ;; -r | --rcon-ignore) rcon_ignore=1 ;; -?*) printf 'Unknown option: %s\n' "$1" >&2 usage ;; *) # Default case: No more options, so break out of the loop. break ;; esac shift done } parse_args "$@" # Check and see that the correct variables were set, some may not be expanded as if they're empty they're useless # anyhow minecraft_directory=~/Minecraft/Server-"${server_id}" create_directories() { mkdir -p "${minecraft_directory}" mkdir -p "${minecraft_directory}/backups" cd "${minecraft_directory}" || (log "error" "Unable to change directory to ${minecraft_directory}" && exit 1) } display_minecraft_server_name() { echo_rgb "Minecraft-Server-${server_id}" 208 158 255 } if [ -n "${revision}" ]; then [[ -d "${minecraft_directory}" ]] \ && log "error" "The minecraft server $(display_minecraft_server_name) already exists in ${minecraft_directory}, delete the directory or do not pass the \"--install\" flag" \ && exit 1 create_directories cd ~/ if [ ! -d ~/Build-Tools ]; then log "info" "Spigot Build Tools was not found, installing Spigot Build Tools now..." mkdir -p Build-Tools && cd Build-Tools mkdir -p Builds log "info" "Downloading Spigot Build Tools..." curl -o BuildTools.jar https://hub.spigotmc.org/jenkins/job/BuildTools/lastSuccessfulBuild/artifact/target/BuildTools.jar \ && log "info" "Finished downloading Spigot Build Tools" fi cd ~/Build-Tools log "info" "Building Spigot revision $(echo_rgb "${revision}" 0 255 195), this may take some time..." java -jar BuildTools.jar --rev "${revision}" > ./build.log 2>&1 build_return_code="${?}" [[ "${build_return_code}" != "0" ]] \ && log "error" "Could not build the given revision, received error code ${build_return_code} see build.log" \ && exit "${build_return_code}" log "info" "Finished building Spigot" log "info" "Copying minecraft server files..." cp spigot*.jar "${minecraft_directory}"/ \ && log "info" "Finished copying files" mv spigot*.jar Builds mv "${minecraft_directory}/"spigot*.jar "${minecraft_directory}/server.jar" log "info" "Installed a minecraft server.jar to $(display_minecraft_server_name) located at ${minecraft_directory}" cd "${minecraft_directory}" java -jar "${minecraft_directory}"/server.jar nogui > setup.log 2>&1 log "info" "Successfully setup the server.jar for $(display_minecraft_server_name)" elif [ ! -f "${minecraft_directory}"/server.jar ]; then log "error" "No server.jar found within ${minecraft_directory}, to create a new server there use the \"--install\" argument" exit 1 fi server_port=$(("12000" + "${server_id}")) query_port=$(("12100" + "${server_id}")) rcon_port=$(("12200" + "${server_id}")) create_directories ## Create a backup of the config file backup_extension="$(date +%Y-%m-%dT%H:%M:%S%z)" log "info" "Making a backup of the server.properties file in ${minecraft_directory}/backups/server.properties.${backup_extension}" rm -f cp "${minecraft_directory}/server.properties" "${minecraft_directory}/backups/server.properties.${backup_extension}" \ && log "info" "Successfully created backup" ## Set the correct ports sed -i "s/server-port=.*/server-port=${server_port}/g" server.properties \ && log "info" "Set the server port to ${server_port}" || exit 1 sed -i "s/query.port=.*/query.port=${query_port}/g" server.properties \ && log "info" "Set the query port to ${query_port}" || exit 1 sed -i "s/rcon.port=.*/rcon.port=${rcon_port}/g" server.properties \ && log "info" "Set the RCON port to ${rcon_port}" || exit 1 ## Turn RCON On # set the rcon password to the default if rcon-ignore is not set if [[ "${rcon_ignore}" == 0 ]]; then log "info" "Setting the RCON password..." [[ -z "${DEFAULT_RCON_PASSWORD}" ]] \ && log "warning" "No RCON password has been defined within $(basename "${0}"), consider generating one with \"openssl rand -base64 36\"" sed -i "s/rcon.password=.*/rcon.password=${DEFAULT_RCON_PASSWORD}/g" server.properties \ && log "info" "RCON password set" sed -i "s/enable-rcon=false/enable-rcon=true/g" server.properties \ && log "info" "RCON Enabled" fi ## Accept the eula sed -i "s/eula=false/eula=true/g" eula.txt \ && log "info" "Minecraft eula accepted" ## Go ahead and start 'er on up tmux has-session -t "Minecraft-Server-${server_id}" > /dev/null 2>&1 if [ "${?}" == 0 ]; then log "warning" "Minecraft server $(display_minecraft_server_name) is currently running" confirmation "Would you like to kill it and then run the new one (y/N)?" if [ "${?}" == 0 ]; then log "info" "Ok, killing server $(display_minecraft_server_name)" tmux kill-session -t "" \ && log "info" "Successfully killed $(display_minecraft_server_name)" else log "info" "Not ending current $(display_minecraft_server_name), exiting..." exit 0 fi fi log "info" \ "Startup Arguments: -Xms${INITIAL_MEM}G -Xmx${MAX_MEM}G -jar" log "info" "Starting $(display_minecraft_server_name)" tmux new-session -d -s \ "Minecraft-Server-${server_id}" \ java -Xms"${INITIAL_MEM}G" -Xmx"${MAX_MEM}G" -XX:+UseG1GC -jar server.jar nogui \ && log "info" \ "Server $(display_minecraft_server_name) started successfully: $(echo_rgb \ "Game Port: ${server_port} Query Port: ${query_port} RCON Port: ${rcon_port}" 0 255 195)"