Skip to content

Instantly share code, notes, and snippets.

@Korko
Last active January 4, 2016 22:09
Show Gist options
  • Save Korko/8686056 to your computer and use it in GitHub Desktop.
Save Korko/8686056 to your computer and use it in GitHub Desktop.
Minecraft management script : sudo /etc/init.d/minecraft Legendary start => will start /home/minecraft/Legendary/minecraft.jar
#!/bin/bash
# /etc/init.d/minecraft
# version 2015-01-20 (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 Minecraft server
### END INIT INFO
# minecraft-init-script - An initscript to start Minecraft or CraftBukkit
# Copyright (C) 2011-2014 Super Jamie <[email protected]>
# https://github.com/superjamie/minecraft-init-script
#
# 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 the 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
mc_list() {
echo " List of running servers"
ps -ef | grep -v grep | grep -i screen | awk '{print "* "$12}'
}
if [ $1 == "list" -a $# -eq 1 ]
then
mc_list
exit $?
fi
## Settings for minecraft-init-script
# Name of Screen session
SCRNAME=$1
# Username of non-root user who will run the server
USERNAME="minecraft"
# Path of server binary and world
MCPATH="/home/minecraft/$SCRNAME"
# Number of CPU cores to thread across if using multithreaded garbage collection
CPU_COUNT="4"
# Where backups go
BACKUPPATH="/home/minecraft/backups/$SCRNAME"
# How many days worth of backups to keep (default 7)
BACKUPS_TO_KEEP="7"
if [ -f $MCPATH/server_config ]
then
. $MCPATH/server_config
fi
# Nice looking name of service for script to report back to users
SERVERNAME=${SERVERNAME:-$SCRNAME}
# Filename of server binary
SERVICE=${SERVICE:-"minecraft_server.jar"}
# Memory allocated (min/max)
MAXHEAP=${MAXHEAP:-1024}
MINHEAP=${MINHEAP:-1024}
## The Java command to run the server, uncomment one INVOCATION line only!
# Nothing special, just start the server with 1Gb RAM
# INVOCATION="java -Xms1024M -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 -Xms${MINHEAP}M -Xmx${MAXHEAP}M -Djava.net.preferIPv4Stack=true -XX:MaxPermSize=256M -XX:UseSSE=3 -XX:-DisableExplicitGC -XX:+UseParallelOldGC -XX:ParallelGCThreads=$CPU_COUNT -XX:+AggressiveOpts -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
# You should be using HugePages if you give more than 4Gb on the Java invocation line
# -XX:+UseLargePages
## End of settings file
## ---------------------------------------------------------
### ### ### ### ### ### ### ### ### ### ### ### ### ###
### You shouldn't need to edit anything below here! ###
### ### ### ### ### ### ### ### ### ### ### ### ### ###
# Find the world name from the existing server file
WORLDNAME="$(grep -E 'level-name' $MCPATH/server.properties | sed -e s/.*level-name=//)"
# Find the port number from the existing server file
SERVERPORT="$(grep -E 'server-port' $MCPATH/server.properties | sed -e s/.*server-port=//)"
## 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 $SCRNAME to differentiate between multiple servers, awk out everything but the pid
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
# 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 get the parent pid, which is the actual pid of the java process, check that process 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 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!"
return 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"
echo " * Checking $SERVERNAME is running..."
# start a counter and check for 15 seconds if the server is running or not
COUNT=0
while [ $COUNT -lt 15 ]; do
if server_running
then
echo " * [OK] $SERVERNAME is now running (pid $JAVAPID)."
return 0
else
let COUNT=COUNT+1
sleep 1
fi
done
# if we've reached here, the server hasn't started in 15 seconds
echo " * [ERROR] Could not start $SERVERNAME."
return 1
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 -n " * Saving map named \"$WORLDNAME\" to disk"
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"save-all\"\015'"
# start a counter and check for 20 seconds for when the world has saved
COUNT=0
while [ $COUNT -lt 10 ]; do
echo -n "."
sleep 2
if [ -f "$MCPATH/logs/latest.log" ]
then
if [[ "$(tail -n 20 $MCPATH/logs/latest.log | grep -E 'Save complete|Saved the world' | wc -l)" -gt 0 ]]; then
COUNT=99
fi
fi
let COUNT=COUNT+1
done
echo ""
echo -n " * Stopping $SERVERNAME"
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"stop\"\015'"
# start a counter and check for 15 seconds if the server is running or not
COUNT=0
while [ $COUNT -lt 15 ]; do
if server_running
then
echo -n "."
let COUNT=COUNT+1
sleep 1
else
echo ""
echo " * [OK] $SERVERNAME is shut down."
return 0
fi
done
echo ""
# if we've reached here, the server hasn't stopped in 15 seconds
echo " * [ERROR] $SERVERNAME is still running (pid $JAVAPID). Could not be shutdown!"
return 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
}
## Checks for update, exits if update not required, updates if the server is not running
mc_update() {
echo " * Checking latest $SERVERNAME version against existing server..."
## Get MC last server file name and compare with the local one
if [ -f "$MCPATH/lastVersion.sh" ]
then
SERVER_URL=$(bash "$MCPATH/lastVersion.sh" get)
else
SERVER_URL=$(wget -q -O - "https://minecraft.net/download" | grep -o "\"https.*minecraft_server.[0-9]*.[0-9]*.[0-9]*.jar\"" | tr -d "\"")
fi
SERVER_FILE=$(basename "$SERVER_URL")
SERVER_FILE=${SERVER_FILE%%\?*}
if [ -f "$MCPATH/$SERVER_FILE" ]
then
echo " * You are already running the latest version of $SERVERNAME!"
return 0; # keep this exit in as we don't need to do anything
else
echo " * Downloading latest $SERVERNAME executable..."
as_user "cd \"$MCPATH\" && curl -# -L -o \"$SERVER_FILE\" \"$SERVER_URL\""
fi
RESTART=server_running
if $RESTART
then
echo " * $SERVERNAME is running (pid $JAVAPID). Shutting down for update..."
mc_stop
fi
mc_backupmap
if server_running
then
echo " * [ERROR] $SERVICE is still running (pid $JAVAPID). Cannot update!"
return 1
else
if [ -f "$MCPATH/lastVersion.sh" ]
then
as_user "cd \"$MCPATH\" && bash \"lastVersion.sh\" update $SERVER_FILE"
else
echo " * Removing the old executable"
as_user "reallink -f \"$MCPATH/$SERVICE\" | xargs rm"
echo " * Linking to the new version"
as_user "rm \"$MCPATH/$SERVICE\" && ln -s \"$MCPATH/$SERVER_FILE\" \"$MCPATH/$SERVICE\""
fi
echo " * [OK] $SERVERNAME successfully updated."
fi
if $RESTART
then
echo " * Restarting $SERVERNAME"
mc_start
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 server_running
then
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\""
return 1
fi
fi
else
echo " * [ERROR] $SERVERNAME is not running."
return 1
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 "mkdir -p \"$BACKUPPATH/$WORLDNAME\""
as_user "rsync --checksum --group --human-readable --copy-links --owner --perms --recursive --times --update --delete \"$MCPATH/$WORLDNAME\" \"$BACKUPPATH\""
# if the nether exists, back it up
WORLDNETHER="$WORLDNAME""_nether"
if [ -d "$MCPATH/$WORLDNETHER" ]
then
echo " * Syncing \"$MCPATH/$WORLDNETHER\" to \"$BACKUPPATH/$WORLDNETHER\""
as_user "rsync --checksum --group --human-readable --copy-links --owner --perms --recursive --times --update --delete \"$MCPATH/$WORLDNETHER\" \"$BACKUPPATH\""
else
echo " * \"$MCPATH/$WORLDNETHER\" doesn't exist, skipping."
fi
# if the end exists, back it up
WORLDTHEEND="$WORLDNAME""_the_end"
if [ -d "$MCPATH/$WORLDTHEEND" ]
then
echo " * Syncing \"$MCPATH/$WORLDTHEEND\" to \"$BACKUPPATH/$WORLDTHEEND\""
as_user "rsync --checksum --group --human-readable --copy-links --owner --perms --recursive --times --update --delete \"$MCPATH/$WORLDTHEEND\" \"$BACKUPPATH\""
else
echo " * \"$MCPATH/$WORLDTHEEND\" doesn't exist, skipping."
fi
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\" &"
echo " * [OK] Backed up map \"$WORLDNAME\"."
# if we backed up the nether, create a backup of that too
if [ -d "$BACKUPPATH/$WORLDNETHER" ]
then
as_user "cd "$BACKUPPATH" && tar cfz \""$WORLDNETHER"_backup_"$NOW".tar.gz\" \"$WORLDNETHER\" && md5sum \""$WORLDNETHER"_backup_"$NOW".tar.gz\" > \""$WORLDNETHER"_backup_"$NOW".tar.gz.md5\" &"
echo " * [OK] Backed up map \"$WORLDNETHER\"."
fi
# if we backed up the end, create a backup of that too
if [ -d "$BACKUPPATH/$WORLDTHEEND" ]
then
as_user "cd "$BACKUPPATH" && tar cfz \""$WORLDTHEEND"_backup_"$NOW".tar.gz\" \"$WORLDTHEEND\" && md5sum \""$WORLDTHEEND"_backup_"$NOW".tar.gz\" > \""$WORLDTHEEND"_backup_"$NOW".tar.gz.md5\" &"
echo " * [OK] Backed up map \"$WORLDTHEEND\"."
fi
# we can safely background the above commands and get back to restarting the server
}
## Removes any backups older than $BACKUPS_TO_KEEP days, designed to be called by daily cron job
mc_removeoldbackups() {
echo " * Removing backups older than $BACKUPS_TO_KEEP days..."
as_user "cd \"$BACKUPPATH\" && find . -name \"*backup*\" -type f -mtime +$BACKUPS_TO_KEEP | 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() {
# Define a function to copy the old logfile to the new
mc_copylog() {
as_user "/bin/cp $logfile $MCPATH/$LOGNEW"
}
# 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"
mc_copylog
# Otherwise, check if the file number is under $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))"
mc_copylog
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."
return 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 available, otherwise skip
if [ -n "$(grep HugePages_Total /proc/meminfo | awk '{print $2}')" -a "$(grep HugePages_Total /proc/meminfo | awk '{print $2}')" ]
then
HP_SIZE="$(grep Hugepagesize /proc/meminfo | awk '{print $2}')"
HP_TOTAL="$(grep HugePages_Total /proc/meminfo | awk '{print $2}')"
HP_FREE="$(grep HugePages_Free /proc/meminfo | awk '{print $2}')"
HP_RSVD="$(grep HugePages_Rsvd /proc/meminfo | 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."
return 1
fi
}
## Connect to the active Screen session, disconnect with Ctrl+a then d
mc_console() {
if server_running
then
chmod g+rwx $(tty)
as_user "screen -S $SCRNAME -dr"
else
echo " * [ERROR] $SERVERNAME was not running! Unable to console."
return 1
fi
}
## Broadcasts a message
mc_say() {
say_string="${@:-1}"
if [[ -z "$say_string" ]]
then
echo " * You need to enter your message. Usage; \"minecraft say message\""
elif server_running
then
echo " * Broadcasting \"$say_string\""
as_user "screen -p 0 -S $SCRNAME -X eval 'stuff \"say $say_string\"\015'"
else
echo " * [ERROR] $SERVERNAME was not running!"
return 1
fi
}
usage() {
echo "Usage: $0 <minecraftDirectoryName> {start|stop|restart|backup (worldname)|save-on|save-off|update|status|info|console|say}"
return 1
}
# Don't know why, the tty is alway write only for group
chmod g+rw $(tty)
## These are the parameters passed to the script
case "$2" in
start)
mc_start
;;
stop)
mc_stop
;;
restart)
mc_stop && sleep 1 && mc_start
;;
update)
mc_update
;;
backup)
mc_checkbackup "$3" && mc_saveoff && mc_backupmap && 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
;;
save-off)
mc_saveoff
;;
save-on)
mc_saveon
;;
say)
mc_say "$3"
;;
*)
usage
;;
esac
exit $?
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment