#!/bin/bash
# /etc/init.d/minecraft
# version 2013-04-19 (YYYY-MM-DD)
### 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: Starts the CraftBukkit Minecraft server
### END INIT INFO
# minecraft-init-script - An initscript to start Minecraft or CraftBukkit
# Copyright (C) 2011 - Super Jamie <[email protected]>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
# Source function library
## CentOS/Fedora
if [ -f /etc/rc.d/init.d/functions ]
then
. /etc/rc.d/init.d/functions
fi
## Ubuntu
if [ -f /lib/lsb/init-functions ]
then
. /lib/lsb/init-functions
fi
## Settings
# Nice looking name of service for script to report back to users
SERVERNAME="obscurity"
# Filename of server binary
SERVICE="FTBServer-1.7.10-1448.jar"
# URL of server executable for update checking - http://cbukk.it/craftbukkit.jar is the recommended latest Craftbukkit URL
SERVER_URL="http://cbukk.it/craftbukkit.jar"
# Username of non-root user who will run the server
USERNAME="minecraft"
# Path of server binary and world
MCPATH="/home/minecraft/servers/obscurity"
# Number of CPU cores to thread across if using multithreaded garbage collection
CPU_COUNT="4"
# Where backups go
BACKUPPATH="/home/minecraft/Dropbox/ovh_backup/obscurity"
# Find the world name from the existing server file
WORLDNAME="$(cat $MCPATH/server.properties | grep -E 'level-name' | sed -e s/.*level-name=//)"
# Find the port number from the existing server file
SERVERPORT="$(cat $MCPATH/server.properties | grep -E 'server-port' | sed -e s/.*server-port=//)"
# Name of Screen session
SCRNAME="obscurity"
## The Java command to run the server
# Nothing special, just start the server with 1Gb RAM
# INVOCATION="java -Xms2048M -Xmx1024M -Djava.net.preferIPv4Stack=true -jar $SERVICE nogui"
# This is what I run my server with. Tune your RAM usage accordingly
# Tested fastest GC - Default parallel new gen collector, plus parallel old gen collector
#INVOCATION="java -Xmx12288M -Xms12288M -XX:PermSize=256m -XX:NewRatio=3 -XX:SurvivorRatio=3 -XX:TargetSurvivorRatio=80 -XX:MaxTenuringThreshold=8 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=10 -XX:GCPauseIntervalMillis=50 -XX:MaxGCMinorPauseMillis=7 -XX:+ExplicitGCInvokesConcurrent -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+BindGCTaskThreadsToCPUs -Xnoclassgc -jar $SERVICE nogui"
#INVOCATION="java -Xms8G -Xmx8G -XX:PermSize=256m -XX:NewRatio=3 -XX:SurvivorRatio=3 -XX:TargetSurvivorRatio=80 -XX:MaxTenuringThreshold=8 -XX:+UseParNewGC -XX:+UseConcMarkSweepGC -XX:MaxGCPauseMillis=10 -XX:GCPauseIntervalMillis=50 -XX:MaxGCMinorPauseMillis=7 -XX:+ExplicitGCInvokesConcurrent -XX:+UseCMSInitiatingOccupancyOnly -XX:CMSInitiatingOccupancyFraction=60 -XX:+BindGCTaskThreadsToCPUs -Xnoclassgc -jar $SERVICE nogui"
INVOCATION="java -Xms512M -Xmx4096M -jar $SERVICE nogui"
# I removed these "performance" commands as I don't see any difference with them
# -XX:+UseFastAccessorMethods -XX:+UseAdaptiveGCBoundary
# I've had a suggestion from a Java tuning engineer to use these
# -XX:+AggressiveOpts -XX:+UseCompressedStrings
# Add HugePage support if you have it configured on the OS
# -XX:+UseLargePages
# I have no idea if it helps, but you can add SSE support depending on your CPU (check /proc/cpuinfo):
# -XX:UseSSE=3
## Runs all commands as the non-root user
as_user() {
ME="$(whoami)"
if [ "$ME" == "$USERNAME" ]
then
bash -c "$1"
else
su - "$USERNAME" -c "$1"
fi
}
## Check if the server is running or not, and get the Java Process ID if it is
server_running() {
# Get the PID of the running Screen session:
# ps, remove grep, look for Screen, look for the Screen session named $SCRNAME to differentiate between multiple servers, awk out everything but the PID of Screen
SCREENPID=""
SCREENPID="$(ps -ef | grep -v grep | grep -i screen | grep $SCRNAME | awk '{print $2}')"
# If the Screen session with $SCRNAME is not running, then the server is not running, so we return 1 and exit the function
if [ -z "$SCREENPID" ]
then
return 1
fi
JAVAPID=""
# PID="$(ps -ef | grep -v grep | grep -i screen | grep $SCRNAME | awk '{print $2}' | xargs ps -f --ppid | grep $SERVICE | awk '{print $2}')"
# Use the Screen session PID to see what processes it is parent to, which is the actual PID of the Java process, check that that PID is actually $SERVICE
JAVAPID="$(ps -f --ppid $SCREENPID | grep $SERVICE | awk '{print $2}')"
# If the Java process is not running, then the server is not running, so we return 1 to exit the function
if [ -z "$JAVAPID" ]
then
return 1
fi
# If we haven't failed those two tests, we must have a running server, so we return success
return 0
}
## Start the server executable as a service
mc_start() {
if server_running
then
echo " * [ERROR] $SERVERNAME was already running (pid $JAVAPID). Not starting!"
exit 1
else
echo " * $SERVERNAME was not already running. Starting..."
echo " * Using map named \"$WORLDNAME\"..."
as_user "cd \"$MCPATH\" && screen -c /dev/null -dmS $SCRNAME $INVOCATION"
sleep 10
echo " * Checking $SERVERNAME is running..."
if server_running
then
echo " * [OK] $SERVERNAME is now running (pid $JAVAPID)."
else
echo " * [ERROR] Could not start $SERVERNAME."
exit 1
fi
fi
}
## Stop the executable
mc_stop() {
if server_running
then
echo " * $SERVERNAME is running (pid $JAVAPID). Commencing shutdown..."
echo " * Notifying users of shutdown..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"say SERVER SHUTTING DOWN IN 10 SECONDS. Saving map...\"\015'"
echo " * Saving map named \"$WORLDNAME\" to disk..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"save-all\"\015'"
sleep 10
echo " * Stopping $SERVERNAME..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"stop\"\015'"
sleep 10
else
echo " * [ERROR] $SERVERNAME was not running. Unable to stop!"
##exit 1
fi
if server_running
then
echo " * [ERROR] $SERVERNAME is still running (pid $JAVAPID). Could not be shutdown!"
exit 1
else
echo " * [OK] $SERVERNAME is shut down."
fi
}
## Set the server read-only, save the map, and have Linux sync filesystem buffers to disk
mc_saveoff() {
if server_running
then
echo " * $SERVERNAME is running. Commencing save..."
echo " * Notifying users of save..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"say SERVER BACKUP STARTING. Server going read-only...\"\015'"
echo " * Setting server read-only..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"save-off\"\015'"
echo " * Saving map named \"$WORLDNAME\" to disk..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"save-all\"\015'"
sync
sleep 10
echo " * [OK] Map saved."
else
echo " * [INFO] $SERVERNAME was not running. Not suspending saves."
fi
}
## Set the server read-write
mc_saveon() {
if server_running
then
echo " * $SERVERNAME is running. Re-enabling saves..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"say SERVER BACKUP ENDED. Server going read-write...\"\015'"
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"save-on\"\015'"
else
echo " * [INFO] $SERVERNAME was not running. Not resuming saves."
fi
}
## Notify everyone that the server will be rebooting in 5 minutes
mc_rbnotify() {
if server_running
then
echo " * $SERVERNAME is running. Notifying users of pending reboot..."
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"say SERVER REBOOT in 5 minutes...\"\015'"
else
echo " * [INFO] $SERVERNAME was not running. No notification sent."
fi
}
## Checks for update, exits if update not required, updates if the server is not running
mc_updatecheck() {
echo " * Downloading latest $SERVERNAME executable..."
as_user "cd \"$MCPATH\" && curl -# -L -o \"$MCPATH/$SERVICE.update\" \"$SERVER_URL\""
if [ -f "$MCPATH/$SERVICE.update" ]
then
echo " * Checking downloaded update against existing server..."
if $(diff "$MCPATH/$SERVICE" "$MCPATH/$SERVICE.update" >/dev/null)
then
echo " * You are already running the latest version of $SERVERNAME!"
exit 0; # keep this exit in as we don't need to do anything
fi
else
echo " * [ERROR] $SERVERNAME update could not be downloaded."
exit 1
fi
if server_running
then
echo " * $SERVERNAME is running (pid $JAVAPID). Shutting down for update..."
else
as_user "/bin/cp \"$MCPATH/$SERVICE.update\" \"$MCPATH/$SERVICE\""
echo " * [OK] $SERVERNAME successfully updated."
fi
}
## Actually do the executable update
mc_updatedo() {
if server_running
then
echo " * [ERROR] $SERVICE is still running (pid $JAVAPID). Cannot update!"
exit 1
else
as_user "/bin/cp \"$MCPATH/$SERVICE.update\" \"$MCPATH/$SERVICE\""
echo " * [OK] $SERVERNAME successfully updated."
fi
}
## Check and see if a worldname was given to the backup command. Use the default world, or check the optional world exists and exit if it doesn't.
mc_checkbackup() {
if [ -n "$1" ]
then
WORLDNAME="$1"
if [ -d "$MCPATH/$WORLDNAME" ]
then
echo " * Found world named \"$MCPATH/$WORLDNAME\""
else
echo " * Could not find world named \"$MCPATH/$WORLDNAME\""
exit 1
fi
fi
}
## Backs up map by rsyncing current world to backup location, creates tar.gz with datestamp
mc_backupmap() {
echo " * Backing up $SERVERNAME map named \"$WORLDNAME\"..."
echo " * Syncing \"$MCPATH/$WORLDNAME\" to \"$BACKUPPATH/$WORLDNAME\""
as_user "rsync --checksum --group --human-readable --copy-links --owner --perms --recursive --times --update --delete \"$MCPATH/$WORLDNAME\" \"$BACKUPPATH\""
sleep 10
echo " * Creating compressed backup..."
NOW="$(date +%Y-%m-%d.%H-%M-%S)"
# Create a compressed backup file and background it so we can get back to restarting the server
# You can tell when the compression is done as it makes an md5sum file of the backup
as_user "cd "$BACKUPPATH" && tar cfz \""$WORLDNAME"_backup_"$NOW".tar.gz\" \"$WORLDNAME\" && md5sum \""$WORLDNAME"_backup_"$NOW".tar.gz\" > \""$WORLDNAME"_backup_"$NOW".tar.gz.md5\" &"
# we can safely background the above command and get back to restarting the server
echo " * [OK] Backed up map \"$WORLDNAME\"."
}
## Backs up executable by copying it to backup location
mc_backupexe() {
echo " * Backing up the $SERVERNAME server executable..."
NOW="$(date +%Y-%m-%d.%H-%M-%S)"
as_user "cp \""$MCPATH"/"$SERVICE"\" \""$BACKUPPATH"/"$SERVICE"_backup_"$NOW".jar\""
echo " * [OK] Backed up executable."
}
## Removes any backups older than 7 days, designed to be called by daily cron job
mc_removeoldbackups() {
echo " * Removing backups older than 7 days..."
as_user "cd \"$BACKUPPATH\" && find . -name \"*backup*\" -type f -mtime +7 | xargs rm -fv"
echo " * Removed old backups."
}
## Rotates logfile to server.1 through server.7, designed to be called by daily cron job
mc_logrotate() {
# Server logfiles in chronological order
LOGLIST="$(ls -r $MCPATH/server.log* | grep -v lck)"
# How many logs to keep
COUNT="6"
# Look at all the logfiles
for logfile in $LOGLIST; do
LOGTMP="$(basename $logfile | cut -d '.' -f 3)"
# If we're working with server.log then append .1
if [ -z "$LOGTMP" ]
then
LOGNEW="server.log.1"
as_user "/bin/cp $logfile $MCPATH/$LOGNEW"
# Otherwise, check if the file number is greater than $COUNT
elif [ "$LOGTMP" -gt "$COUNT" ]
then
# If so, delete it
as_user "rm -f $logfile"
else
# Otherwise, add one to the number
LOGBASE="$(basename $logfile | cut -d '.' -f 1-2)"
LOGNEW="$LOGBASE.$(($LOGTMP+1))"
as_user "/bin/cp $logfile $MCPATH/$LOGNEW"
fi
done
# Blank the existing logfile to renew it
as_user "echo -n \"\" > $MCPATH/server.log"
}
## Check if server is running and display PID
mc_status() {
if server_running
then
echo " * $SERVERNAME status: Running (pid $JAVAPID)."
else
echo " * $SERVERNAME status: Not running."
exit 1
fi
}
## Display some extra environment informaton
mc_info() {
if server_running
then
RSS="$(ps --pid $JAVAPID --format rss | grep -v RSS)"
echo " - Java Path : $(readlink -f $(which java))"
echo " - Start Command : $INVOCATION"
echo " - Server Path : $MCPATH"
echo " - World Name : $WORLDNAME"
echo " - Process ID : $JAVAPID"
echo " - Screen Session : $SCRNAME"
echo " - Memory Usage : $[$RSS/1024] Mb ($RSS kb)"
# Check for HugePages support in kernel, display statistics if HugePages are in use, otherwise skip
if [ -n "$(cat /proc/meminfo | grep HugePages_Total | awk '{print $2}')" -a "$(cat /proc/meminfo | grep HugePages_Total | awk '{print $2}')" -gt 0 ]
then
HP_SIZE="$(cat /proc/meminfo | grep Hugepagesize | awk '{print $2}')"
HP_TOTAL="$(cat /proc/meminfo | grep HugePages_Total | awk '{print $2}')"
HP_FREE="$(cat /proc/meminfo | grep HugePages_Free | awk '{print $2}')"
HP_RSVD="$(cat /proc/meminfo | grep HugePages_Rsvd | awk '{print $2}')"
HP_USED="$[$HP_TOTAL-$HP_FREE+$HP_RSVD]"
TOTALMEM="$[$RSS+$[$HP_USED*$HP_SIZE]]"
echo " - HugePage Usage : $[$HP_USED*$[$HP_SIZE/1024]] Mb ($HP_USED HugePages)"
echo " - Total Memory Usage : $[$TOTALMEM/1024] Mb ($TOTALMEM kb)"
fi
echo " - Active Connections : "
netstat --inet -tna | grep -E "Proto|$SERVERPORT"
else
echo " * $SERVERNAME is not running. Unable to give info."
exit 1
fi
}
## Connect to the active Screen session, disconnect with Ctrl+a then d
mc_console() {
if server_running
then
as_user "screen -S $SCRNAME -dr"
else
echo " * [ERROR] $SERVERNAME was not running! Unable to console."
exit 1
fi
}
## These are the parameters passed to the script
case "$1" in
start)
mc_start
;;
stop)
mc_stop
;;
restart)
mc_stop
sleep 1
mc_start
;;
update)
mc_updatecheck
mc_stop
mc_backupmap
mc_backupexe
mc_updatedo
mc_start
;;
backup)
mc_checkbackup "$2"
mc_saveoff
mc_backupmap
mc_backupexe
mc_saveon
;;
status)
mc_status
;;
info)
mc_info
;;
console)
mc_console
;;
# These are intended for cron usage, not regular users.
removeoldbackups)
mc_removeoldbackups
;;
logrotate)
mc_logrotate
;;
rbnotify)
mc_rbnotify
;;
# Debug usage only
justbackup) # don't use this while the server is running!!!
mc_checkbackup
mc_backupmap
mc_backupexe
;;
*)
echo " * Usage: minecraft {start|stop|restart|backup (worldname)|update|status|info|console}"
exit 1
;;
esac
exit 0