msm/minecraft

659 lines
17 KiB
Bash
Executable File
Raw Blame History

This file contains invisible Unicode characters

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

#!/bin/bash
### BEGIN INIT INFO
# Provides: minecraft
# 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: Minecraft server
# Description: Init script for minecraft/bukkit server, with rolling logs and use of ramdisk for less lag.
### END INIT INFO
# Minecraft/Bukkit init script by Marcus Whybrow
# - Modified from a script created by Ahtenus (https://github.com/Ahtenus/minecraft-init)
# - Which was apparently based upon http://www.minecraftwiki.net/wiki/Server_startup_script
# The location of the configuration file for this script
CONFIG="/opt/minecraft/server1/manager.config"
### Load configuration variables
source $CONFIG
### General Utility functions
# Returns the current time as a UNIX timestamp (in seconds since 1970)
now() {
date +%s
}
# This function is used to quite the stdout of another function
# like this: quite $(other_func)
quite() {
return $(true)
}
as_user() {
if [ $(whoami) == $SERVER_USER ] ; then
bash -c "$1"
else
su - $SERVER_USER -s /bin/bash -c "$1"
fi
}
### Log Utility Functions
# Gets the time 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
}
# Watches the log
# $1: The line in the log to wait for
# $2: A UNIX timestamp (seconds since 1970) which the $1 line must be after
# returns: When the line is found
log_wait_for_line() {
# Make sure there is a server log to check
as_user "touch $SERVER_LOG"
regex="^[0-9]{4}-[0-9]{2}-[0-9]{2} [0-9]{2}:[0-9]{2}:[0-9]{2} \[.*\] ${1}"
while read LINE
do
time=$(log_line_get_time "$LINE")
# If the entry is old enough and matches the regular expression
if [[ $time -ge $2 && $LINE =~ $regex ]]
then
echo $LINE
break
fi
done < <(as_user "tail --follow --lines=100 --sleep-interval=0.1 $SERVER_LOG")
}
### Server Utility Functions
server_is_running() {
if ps ax | grep -v grep | grep "$SCREEN_NAME $INVOCATION" > /dev/null
then
return $(true)
else
return $(false)
fi
}
# Gets the process ID for the server if running, otherwise it outputs nothing
server_pid() {
ps ax | grep -v grep | grep "$SCREEN_NAME $INVOCATION" | awk '{print $1}'
}
server_wait_for_stop() {
pid=$(server_pid)
while ps -p $pid > /dev/null
do
sleep 0.1
done
}
# $1: A line of text to enter into the server console
server_eval() {
as_user "screen -p 0 -S $SCREEN_NAME -X eval 'stuff \"$1\"\015'"
}
# $1: A line of text to enter into the server console
# $2: The line of text in the log to wait for
server_eval_and_wait() {
time=$(now)
server_eval $1
log_wait_for_line "$2" "$time"
}
### World Handling Functions
worlds_get() {
a=1
for NAME in $(ls ${WORLD_STORAGE_PATH})
do
if [ -d ${WORLD_STORAGE_PATH}/$NAME ]
then
WORLDNAME[$a]=$NAME
if [ -e ${WORLD_STORAGE_PATH}/$NAME/ramdisk ]
then
WORLDRAM[$a]=true
else
WORLDRAM[$a]=false
fi
a=$a+1
fi
done
}
# Creates symbolic links in the server directory (SERVER_PATH) for each
# of the Minecraft worlds located in the world storage directory (WORD_STORAGE_PATH).
worlds_ensure_links() {
echo "Updating world symbolic links..."
worlds_get
for INDEX in ${!WORLDNAME[@]}
do
# -L checks for the path being a link rather than a file
# ! -a, since it is within double square brackets means: the negation of
# the existence of the file. In other words: true if does not exist
if [[ -L ${SERVER_PATH}/${WORLDNAME[$INDEX]} || ! -a ${SERVER_PATH}/${WORLDNAME[$INDEX]} ]]
then
# If there is a symbolic link in the SERVER_PATH to this world or there is not
# a directory in the SERVER_PATH for 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 ${SERVER_PATH}/${WORLDNAME[$INDEX]})
if ${WORLDRAM[$INDEX]}
then
# If this world is marked as loaded into RAM
if [ "${link_target}" != "${RAMDISK_PATH}/${WORLDNAME[$INDEX]}" ]
then
# If the symbolic link does not point to the RAM version of the world
# Remove the symbolic link if it exists
as_user "rm -f ${SERVER_PATH}/${WORLDNAME[$INDEX]}"
# Create a new symbolic link pointing to the RAM version of the world
as_user "ln -s ${RAMDISK_PATH}/${WORLDNAME[$INDEX]} ${SERVER_PATH}/${WORLDNAME[$INDEX]}"
echo "Created a link for ${WORLDNAME[$INDEX]} which is in RAM: ${RAMDISK_PATH}/${WORLDNAME[$INDEX]}"
fi
else
# Otherwise the world is not loaded into RAM, and is just on disk
if [ "${link_target}" != "${WORLD_STORAGE_PATH}/${WORLDNAME[$INDEX]}" ]
then
# If the symbolic link does not point to the disk version of the world
# Remove the symbolic link if it exists
as_user "rm -f ${SERVER_PATH}/${WORLDNAME[$INDEX]}"
# Create a new symbolic link pointing to the disk version of the world
as_user "ln -s ${WORLD_STORAGE_PATH}/${WORLDNAME[$INDEX]} ${SERVER_PATH}/${WORLDNAME[$INDEX]}"
echo "Created a link for ${WORLDNAME[$INDEX]} which on disk: ${WORLD_STORAGE_PATH}/${WORLDNAME[$INDEX]}"
fi
fi
else
# There was no symbolic link, and there was a directory, meaning a world
# directory has been placed in SERVER_PATH, this is not allowed:
echo "Could not process ${WORLDNAME[$INDEX]}. please move all worlds to ${WORLD_STORAGE_PATH}."
exit 1
fi
done
echo "Finished updating world symbolic links."
}
world_to_ram() {
as_user "mkdir -p ${RAMDISK_PATH}/$1 && rsync -rt --exclude 'ramdisk' ${WORLD_STORAGE_PATH}/$1/ ${RAMDISK_PATH}/$1"
}
worlds_to_ram() {
echo "Synchronising worlds to RAM..."
worlds_get
for INDEX in ${!WORLDNAME[@]}
do
if ${WORLDRAM[$INDEX]}
then
if [ -L ${SERVER_PATH}/${WORLDNAME[$INDEX]} ]
then
printf "[RAM] \"${WORLDNAME[$INDEX]}\": Synchronising to RAM... "
world_to_ram "${WORLDNAME[$INDEX]}"
echo "Done."
fi
else
echo "[DSK] \"${WORLDNAME[$INDEX]}\": Nothing to do."
fi
done
echo "Finished synchronising worlds to RAM."
}
worlds_to_disk() {
echo "Synchronising worlds in RAM to disk..."
worlds_get
for INDEX in ${!WORLDNAME[@]}
do
if [ -e ${RAMDISK_PATH}/${WORLDNAME[$INDEX]} ]
then
printf "[RAM] \"${WORLDNAME[$INDEX]}\": Synchronising to disk... "
as_user "rsync -rt --exclude 'ramdisk' ${RAMDISK_PATH}/${WORLDNAME[$INDEX]}/ ${WORLD_STORAGE_PATH}/${WORLDNAME[$INDEX]}"
echo "Done."
else
echo "[DSK] \"${WORLDNAME[$INDEX]}\": Nothing to do."
fi
done
echo "Finished synchronising worlds in RAM to disk."
}
world_toggle_ramdisk_state() {
if [ ! -e ${WORLD_STORAGE_PATH}/$1 ]
then
echo "World \"$1\" not found."
exit 1
fi
if [ -e ${WORLD_STORAGE_PATH}/$1/ramdisk ]
then
as_user "rm ${WORLD_STORAGE_PATH}/$1/ramdisk"
as_user "rm -r ${RAMDISK_PATH}/$1"
echo "Removed the RAM flag from \"$1\", and deleted it from RAM."
else
as_user "touch ${WORLD_STORAGE_PATH}/$1/ramdisk"
echo "Added the RAM flag to \"$1\"."
printf "Copying world to RAM... "
quite $(world_to_ram "$1")
echo "Done."
fi
echo "Changes will only take effect after server is restarted."
}
### Server Control Functions
server_start() {
if server_is_running
then
echo "$JAR is already running!"
else
worlds_ensure_links
worlds_to_ram
time=$(now)
printf "Starting server... "
as_user "cd $SERVER_PATH && screen -dmS $SCREEN_NAME $INVOCATION"
quite $(log_wait_for_line "$CONFIRMATIONS_START" "$time")
echo "Done."
fi
}
server_saveall() {
if server_is_running
then
# Send the "save-all" command and wait for it to finish
printf "Forcing save... "
quite $(server_eval_and_wait "save-all" "$CONFIRMATIONS_SAVE_ALL")
echo "Done."
else
echo "$JAR was not running. Not forcing save."
fi
}
server_saveoff() {
if server_is_running
then
# Send the "save-off" command and wait for it to finish
printf "Disabling level saving... "
quite $(server_eval_and_wait "save-off" "$CONFIRMATIONS_SAVE_OFF")
echo "Done."
# Also save all
server_saveall
# Not sure what this does, might be important
sync
else
echo "$JAR was not running. Not suspending saves."
fi
}
server_saveon() {
if server_is_running
then
# Send the "save-on" command and wait for it to finish
printf "Enabling level saving... "
quite $(server_eval_and_wait "save-on" "$CONFIRMATIONS_SAVE_ON")
echo "Done."
else
echo "$JAR was not running. Not resuming saves."
fi
}
server_stop() {
if server_is_running
then
server_saveall
printf "Stopping the server... "
server_eval "stop"
server_wait_for_stop
echo "Done."
else
echo "$JAR was not running."
fi
}
server_restart() {
# Restarts the server if it is already running
if server_is_running
then
# Stop the server
server_stop
# Synchronise all worlds in RAM to disk
worlds_to_disk
# Ensure world symbolic links are up to date
worlds_ensure_links
# Synchronise worlds back to RAM
worlds_to_ram
# Start the server again
server_start
else
echo "$JAR is not running. Only running servers may be restarted."
fi
}
# Not really tested this
server_update_jars() {
if server_is_running
then
echo "$JAR is running! Will not start update."
else
# Update minecraft_server.jar
echo "Updating minecraft_server.jar...."
MC_SERVER_URL=http://minecraft.net/$(wget -q -O - http://www.minecraft.net/download.jsp | grep minecraft_server.jar\</a\> | cut -d \" -f 2)
as_user "cd ${SERVER_PATH} && wget -q -O ${SERVER_PATH}/minecraft_server.jar.update $MC_SERVER_URL"
if [ -f ${SERVER_PATH}/minecraft_server.jar.update ]
then
if $(diff ${SERVER_PATH}/minecraft_server.jar ${SERVER_PATH}/minecraft_server.jar.update >/dev/null)
then
echo "You are already running the latest version of the Minecraft server."
as_user "rm ${SERVER_PATH}/minecraft_server.jar.update"
else
as_user "mv ${SERVER_PATH}/minecraft_server.jar.update $MCPATH/minecraft_server.jar"
echo "Minecraft successfully updated."
fi
else
echo "Minecraft update could not be downloaded."
fi
# Update craftbukkit
echo "Updating craftbukkit...."
as_user "cd ${SERVER_PATH} && wget -q -O ${SERVER_PATH}/craftbukkit.jar.update http://dl.bukkit.org/latest-rb/craftbukkit.jar"
if [ -f ${SERVER_PATH}/craftbukkit.jar.update ]
then
if $(diff ${SERVER_PATH}/craftbukkit-0.0.1-SNAPSHOT.jar $MCPATH/craftbukkit.jar.update > /dev/null)
then
echo "You are already running the latest version of CraftBukkit."
as_user "rm ${SERVER_PATH}/craftbukkit.jar.update"
else
as_user "mv ${SERVER_PATH}/craftbukkit.jar.update $MCPATH/craftbukkit-0.0.1-SNAPSHOT.jar"
echo "CraftBukkit successfully updated."
fi
else
echo "CraftBukkit update could not be downloaded."
fi
fi
}
### Backup Functions
backup_server() {
path=${COMPLETE_BACKUP_PATH}/$(date "+%Y-%m-%d-%H-%M-%S").zip
as_user "mkdir -p $COMPLETE_BACKUP_PATH"
zip_flags="-rq"
# Add the "y" flag if symbolic links should not be followed
if [ "$COMPLETE_BACKUP_FOLLOW_SYMLINKS" != "true" ]
then
zip_flags="${zip_flags}y"
fi
# Zip up the server directory
printf "Backing up the entire server directory... "
as_user "mkdir -p ${COMPLETE_BACKUP_PATH} && cd ${SERVER_PATH} && zip ${zip_flags} $path ."
echo "Done."
}
backup_worlds() {
worlds_get
echo "Backing up worlds..."
for INDEX in ${!WORLDNAME[@]}
do
printf "Backing up world \"${WORLDNAME[$INDEX]}\"... "
dir="${WORLD_SNAPSHOT_PATH}/${WORLDNAME[$INDEX]}"
file_name="$(date "+%Y-%m-%d-%H-%M-%S").zip"
as_user "mkdir -p ${dir} && cd ${WORLD_STORAGE_PATH}/${WORLDNAME[$INDEX]} && zip -rq ${dir}/${file_name} ."
echo "Done."
done
echo "Finished backing up worlds."
}
# An experimental function which backs up config files for all
# Bukkit plugins. Only recognises .yml files currently
# I use the backup_server function instead to get everything
# for sure.
backup_configs() {
dir=${CONFIG_BACKUP_PATH}
file_name="$(date "+%Y-%m-%d-%H-%M-%S").zip"
printf "Backing up plugin config files... "
as_user "mkdir -p ${dir} && cd ${SERVER_PLUGIN_PATH} && zip -Rq ${dir}/${file_name} '${CONFIG_BACKUP_PATTERN}'"
echo "Done."
}
### Maintenance Functions
log_roll() {
# Moves and Gzips the logfile, a big log file slows down the
# server A LOT (what was notch thinking?)
path=${LOG_ARCHIVE_PATH}/${SERVER_NAME}-$(date +%F-%H-%M-%S).log
printf "Rolling server logs... "
as_user "mkdir -p $LOG_ARCHIVE_PATH && cp $SERVER_LOG $path && gzip $path"
if [ $? -eq 0 ]
then
as_user "cp /dev/null $SERVER_LOG && echo \"Previous logs rolled to $path\" > $SERVER_LOG"
else
echo "Failed to rotate logs to $path.gz"
fi
echo "Done."
}
### Script switch statement
case "$1" in
start)
# Starts the server
server_start
;;
stop)
# Stops the server
if server_is_running
then
if [[ $2 != "now" ]]
then
server_eval "say ${MESSAGE_SERVER_STOP_WARNING}"
echo "Issued the warning \"${MESSAGE_SERVER_STOP_WARNING}\" to players."
echo "Shutting down in ${CONFIG_SERVER_STOP_DELAY} seconds..."
sleep ${CONFIG_SERVER_STOP_DELAY}
fi
server_stop
worlds_to_disk
else
echo "$JAR was not running."
fi
;;
restart)
if server_is_running
then
if [[ $2 != "now" ]]
then
server_eval "say ${MESSAGE_SERVER_RESTART_WARNING}"
echo "Issued the warning \"${MESSAGE_SERVER_RESTART_WARNING}\" to players."
echo "Restarting in ${CONFIG_SERVER_RESTART_DELAY} seconds..."
sleep ${CONFIG_SERVER_RESTART_DELAY}
fi
server_restart
else
echo "$JAR was not running. Cannot restart a stopped server. Try \"start\" instead."
fi
;;
backup)
# Backs up worlds
if server_is_running
then
server_eval "say ${MESSAGE_SERVER_WORLDS_BACKUP_STARTED}"
server_saveoff
worlds_to_disk
backup_worlds
server_saveon
server_eval "say ${MESSAGE_SERVER_WORLDS_BACKUP_FINISHED}"
else
backup_worlds
fi
;;
complete-backup)
# Backup everything
if server_is_running
then
server_eval "say ${MESSAGE_SERVER_COMPLETE_BACKUP_STARTED}"
server_saveoff
backup_server
server_saveon
server_eval "say ${MESSAGE_SERVER_COMPLETE_BACKUP_FINISHED}"
fi
;;
update)
echo "Not supported yet."
# Update minecraft_server.jar and craftbukkit.jar (thanks karrth)
#server_eval "say SERVER UPDATE IN 10 SECONDS."
#server_stop
#worlds_to_disk
#backup_server
#server_update_jars
#check_links
#server_start
;;
to-disk)
# Writes from the ramdisk to disk, in case the server crashes.
# Using ramdisk speeds things up a lot, especially if you allow
# teleportation on the server.
server_saveoff
worlds_to_disk
server_saveon
;;
connected)
# Lists connected users
# Send the list command to the server, and wait for the response
server_eval_and_wait "list" "Connected players:"
;;
log-roll)
log_roll
;;
last)
echo "Not yet supported."
# greps for recently logged in users
#echo Recently logged in users:
#cat $SERVER_LOG | awk '/entity|conn/ {sub(/lost/,"disconnected");print $1,$2,$4,$5}'
;;
status)
# Shows server status
if server_is_running
then
echo "$JAR is running."
else
echo "$JAR is not running."
fi
;;
version)
echo "Not supported yet."
;;
links)
worlds_ensure_links
;;
ramdisk)
world_toggle_ramdisk_state $2
;;
worlds)
worlds_get
for INDEX in ${!WORLDNAME[@]}
do
if ${WORLDRAM[$INDEX]}
then
echo "[RAM] ${WORLDNAME[$INDEX]}"
else
echo "[DSK] ${WORLDNAME[$INDEX]}"
fi
done
;;
console)
# This only works if the terminal invoking this script
# is logged in as the user $USER_NAME, this is a security
# measure enforced by screen.
as_user "screen -r ${SCREEN_NAME}"
;;
help)
echo "Usage: $0 command"
echo
echo "start - Starts the server"
echo "stop - stops the server gracefully, after warning players"
echo "stop now - stops the server gracefully, right now!"
echo "restart - restarts the server gracefully, after warning players"
echo "restart now - restarts the server gracefully, right now!"
echo "console - opens the screen process (Press Ctr+A then D to exit)"
echo "backup - backs up worlds in \"world storage\""
echo "complete-backup - backups the entire server folder"
echo "log-roll - Moves and gzips the logfile"
echo "to-disk - copies any worlds in RAM to disk"
echo "connected - lists connected users"
echo "status - Shows server status"
echo "links - creates nessesary symlinks"
echo "worlds - shows a list of available worlds"
echo "ramdisk WORLD - toggles ramdisk configuration for WORLD"
echo "update - Not yet supported"
echo "version - Not yet supported"
echo "last - Not yet supported"
;;
*)
echo "No such command see $0 help"
exit 1
;;
esac
exit 0