Conveniently Managing a Linux Spigot Server (or Any Linux Server Really) from Windows

Quick Links

Two of my brothers get really into Minecraft; I mean REALLY into Minecraft. It’s hard not to. I’ve found very few people who dislike the game and only one that absolutely refuses to even try the game. The last time we all started to play we wanted the convenience of our own server so our progress would persist and we would all be able to play together. I was designated as the server host, thusly the server admin.

My primary computer does go to sleep every once and a while which would prevent everyone from playing unless I’m actively doing something. Another drawback was the friends and family that would be using the server wouldn’t want to take the time to learn how to set up and manage the server. Additionally, one thing I really like doing is off-loading computing responsibilities to computers on my network that aren’t doing anything.

It just so happens that I built a small Raspberry Pi cluster using Raspberry Pi 3 B’s at the end of the year. I did run some parallel algorithms on them but, in the end, they ended up being less productive than the i7 in my laptop (and certainly worse than the i9 in the PC I’m writing this on). So, for the most part they just sit there and act as web servers and SSH access points. I decided to use one of these as the Minecraft Server.

Disclaimer: many of the scripts/solutions here are “quick-and-dirty” and have not yet been optimized (I’m writing this shortly after creating these solutions). The philosophy was “80% of the solution in 20% of the time” which was required for this project.

The Server

The server was initially ran on a Raspberry Pi 3 B+ using Spigot as the server software. Spigot is a modified Minecraft server based on CraftBukkit which provides additional performance optimizations, configurations, and features. Because of its performance enhancements, high customizability, and plugin system it is ideal for low-powered computing environments such as the Raspberry Pi.

However, we soon learned that the old Pi 3 couldn’t quite handle 5-6 players and we experienced quite a bit of lag despite our optimizations. So my oldest brother pitched in and bought us a Raspberry Pi 4, which seemed to do the job much better (although I would have liked to experiment with the ODROID-XU4).

The Necessity

It didn’t take long to realize that managing a server (even one with so few users and demand) can be a lot of work. Frequently the server would die due to some badness or another and I would need to log in to restart the processes. Other times the Pi would restart (or be restarted) which would stop the server. I would get demands frequently enough that I said “enough is enough” and I wrote a few bash scripts to automate myself.

This worked pretty well, except when the a would fail and I wasn’t around to fix the server. Thus, I needed a way to give other users the ability to run these scripts while also keeping the tech side of things to a minimum. (I think one or two users might have been able to at least ‘cd’ into the correct directory if given access to the server.) Therefore I needed the following:

  • A simple tool that can be run via GUI/CLI that accepted very few, simple parameters.
  • Some basic functionality to start, stop and restart the server
  • A way to run the custom scripts I had written for backing up the server and making the server as self-maintaining as the situation demanded.

The Scripts

First I will describe the 4 scripts I wrote to manage the server. These worked their way into the GUI/CLI programs I created to manage the Minecraft server. For the record, I know much of this can probably be accomplished via Spigot plugins but by doing it this way I could both control what was happening exactly and use it as a learning experience.

start.sh

This script, found below, was used to start the server. A few times I ran into the problem of stray processes from previous server runs. I decided to add a command to kill the currently running spigot processes, sleep, and then try to start the server.

#!/bin/sh
echo "Killing old processes"
ps ax | grep 'spigot' | awk -F ' ' '{print $1}' | xargs sudo kill -9
echo "Sleeping..."
sleep 3
sudo java -Xms1G -XX:+UseConcMarkSweepGC -jar spigot-1.14.4.jar nogui

stop.sh

This script does just what it says: stops the server. It looks for all running processes that contain the word spigot. This, of course, could cause problems so if you plan on using it please modify if need be. The script also sleeps for 3 seconds to wait for the processes to all die, mostly for good measure.

#!/bin/sh
echo "Killing processes"
ps ax | grep 'spigot' | awk -F ' ' '{print $1}' | xargs sudo kill -9
echo "Sleeping..."
sleep 3

backup.sh

This script is used to backup the world, nether, and end content to other Pi’s on my network every 5 minutes, just in case something happens to the server. This one could probably have been optimized considerably since much of it is repeated (probably needs some intelligent looping), but this was quick and dirty.

while :
do
	echo "Copying to pi1"
	scp -r /home/pi/mc/world/* pi@pi1:/home/pi/mc_back/world/
	scp -r /home/pi/mc/world_the_end/* pi@pi1:/home/pi/mc_back/world_the_end/
	scp -r /home/pi/mc/world_nether/* pi@pi1:/home/pi/mc_back/world_nether/

	echo "Copying to pi2"
	scp -r /home/pi/mc/world/* pi@pi2:/home/pi/mc_back/world/
	scp -r /home/pi/mc/world_the_end/* pi@pi2:/home/pi/mc_back/world_the_end/
	scp -r /home/pi/mc/world_nether/* pi@pi2:/home/pi/mc_back/world_nether/

	echo "Copying to pi3"
	scp -r /home/pi/mc/world/* pi@pi3:/home/pi/mc_back/world/
	scp -r /home/pi/mc/world_the_end/* pi@pi3:/home/pi/mc_back/world_the_end/
	scp -r /home/pi/mc/world_nether/* pi@pi3:/home/pi/mc_back/world_nether/

	echo "Sleeping"
	sleep 300
done

server_fix.sh

The final script I created to automatically restart the server if the script thinks it has crashed. This one I wrote as an attempt to automate the restarting process, it was meant as a be-all-end-all solution to the server crashing. Obviously, there’s probably no such thing but the script actually did a pretty good job.

tries=1
sleep 300
while :
do
SERVER=server.address
PORT=228899
nc -z -v -w5 $SERVER $PORT > /dev/null
result=$?
	if [ "$result" != 0 ]; then 
		if [ $tries == 1 ]; then 
			tries=2
			echo "Could not connect to server, retrying in 5 minutes..."
		else 
			tries=1
			echo "Server not running, restarting..."
			sudo /home/pi/mc/start.sh
		fi
	else
		echo "Server is running..."
	fi
sleep 300
done

Essentially what’s done here is the script checks if a process is listening on the Mincraft port (nc -z -v -w5 $SERVER $PORT) and, if there is nothing listening, the server waits 5 minutes and tries again. If after two tries there are still no processes listening on the Minecraft server port, the server is started via start.sh, described above (which also kills any other spigot processes).

The sleep command at the start of the script is because this and start.sh both start when the pie boots. I didn’t want server_fix.sh to try to start the server while start.sh was still firing up.

Managing the Server (batch)

The idea behind the software is that there would be at least two scripts required for every server: a script to start the server and a script to stop it (note these can be used in combination for a restart). My first stab at this was a basic Windows batch script that would take as parameters the server to be managed (we were running both a vanilla Minecraft 1.14 and Skyfactory server, but just one at a time due to limited resources) and the action to perform. This looked something like the following (NOTE: I am terrible with batch scripts):

@echo OFF
setlocal enabledelayedexpansion
REM args supplied?
IF [%1]==[] (
	goto USAGE
)
IF [%2]==[] (
	goto USAGE
)

REM args proper?
for %%g in ("skyfactory", "m114") DO (
	if /I "%~1"=="%%~g" (GOTO MATCH1)
)
GOTO USAGE

:MATCH1
for %%g in ("start", "stop", "backup", "fix") DO (
	if /I "%~2"=="%%~g" (GOTO MATCH2)
)
GOTO USAGE

:USAGE
echo "Usage: manageCraft.bat <skyfactory, m114> <start, stop, backup, fix>
echo "Note: fix, backup only work on m114. They not implemented on skyfactory."
pause
GOTO :EOF

:MATCH2
echo "Executing command %~2 on %~1"

set user_host="pi@server.address"
set pw="pwd"
set dir=""
set cmd=""
if "%~1" == "m114" ( 
	set dir="/home/pi/mc/"
	if "%~2" == "start" (
		set cmd="start.sh"
	)
	if "%~2" == "stop" (
		set cmd="stop.sh"
	)
	if "%~2" == "backup" (
		set cmd="backup.sh"	
	)
	if "%~2" == "fix" (
		set cmd="server_fix.sh"	
	)
) else (
	set dir="/home/pi/skyfactory/"
	if "%~2" == "start" (
		set cmd="ServerStart.sh"
	)
	if "%~2" == "stop" (
		set cmd="stop.sh"
	)
)
Set cmd_path=%cmd_path:"=%
set dir=%dir:"=%
set do="plink -ssh !user_host! -pw !pw! "
set do=%do:"=%
set command="cd !dir! && ./!cmd!"
set do=!do!!command!
echo !do!
start !do!

Notice that this relies on the plink command provided by PuTTY which is a free implementation of SSH and Telnet for Windows. plink is a command-line application that allows non-interactive sessions and is mostly used for automated operations. Essentially, plink is used here to execute a command on the Linux server without actually creating an interactive session in a console window.

This worked OK for the day or two that it was the only solution. However, I thought I could make it even more convenient and extendable by providing a GUI and not hard-coding the operations, username, password, and other such data. This led to the development of the Java/Swing GUI application (I do admit I should be using JavaFX but stuck with Swing for familiarity).

Managing the Server (Java GUI)

The Java application is pretty basic but contains some nifty features. The first being its ability to create, delete, update, and read configurations stored as JSON. The JSON files are stored in a directory, ‘./config‘, relative to the application exe or jar file. The app will automatically load these when started and allows the user to switch between different configurations via combobox. The JSON files must look something like this (or contain the following fields):

{
  "name": "<server name>",
  "address": "<server IP or URL>",
  "username": "<server username>",
  "serverAndScriptDirectory": "<absolute path to directory where server/scripts are stored>",
  "startScriptName": "<script used for starting (not path, e.g. 'start.sh')>",
  "stopScriptName": "<script used for stopping (again no path unless relative)>",
  "customScripts": [
    {"scriptName": "<custom script file name>", "scriptNickname": "<what you call it>"},
    {"scriptName": "backup.sh", "scriptNickname": "backup"},
    {"scriptName": "server_fix.sh", "scriptNickname": "server fix"}
  ]
}

The name field is the name of the server, pretty much whatever you want to call it. The others are explained pretty well in the snippet above and here is an example,

{
  "name": "m114",
  "address": "server.org",
  "username": "pi",
  "serverAndScriptDirectory": "/home/pi/mc",
  "startScriptName": "start.sh",
  "stopScriptName": "stop.sh",
  "customScripts": [
    {"scriptName": "backup.sh", "scriptNickname": "backup"},
    {"scriptName": "server_fix.sh", "scriptNickname": "server fix"}
  ]
}

Below is a look at the part of the application used to maintain these scripts,

Configuration tab of the application. The user is able to create, update, and delete configurations which are stored as JSON files. Notice the Current Configuration combobox at the top of the screen used to switch between configs on the fly. Also of note is the table used to maintain custom scripts, described below.

A second useful feature is the ability to run custom scripts that are sitting in the server directory. As mentioned above, the idea is that every server would at least have a start and stop script, anything else is considered ‘fluff’. These are stored in the JSON file, there are examples above and the table where these are managed can be seen in the configuration tab screenshot.

Here is what the user interface looks like for running commands:

The user interface used for running commands on the server.

The password has to be entered whenever the application is started. I didn’t want to go through the hassle of safely storing the passwords in the text files.

The Custom Script dropdown allows you to select which custom script to run. Note that this dropdown could have been used to execute all of the scripts. As well as populating it with the names in the table on the configuration tab the dropdown could also include the start and stop scripts, which could be executed when desired. However, the decision was made to have these as their own buttons for ease of use. Again, as mentioned above, the idea was that these are considered the basic commands for the server which should always be present. Thusly, they are assumed to be the most used.

Some Problems That Could be Addressed

One major drawback of this application is that it requires all of the scripts to be in the same directory. However, the directory can be updated every time you want to run a script that is located somewhere else, but that’s rather inconvenient. This could be changed to store the scripts’ locations and execute them in those locations.

A second drawback is, I believe, the configuration file has to be saved or modified before the Commands tab will pick up any changes. This is because the Java configuration object is only updated on saves of the config file (remember “quick-and-dirty”). However, this could easily be fixed by watching for the data to change on the configuration fields or adding an ‘Update‘ or ‘Set‘ button that updates this Java object.

Despite these glaring issues, the application does its job decently. It is also available for use and extension on my GitHub. Keep in mind that it was written in a short amount of time (a few hours) and sparsely tested so it might be littered with bugs.

Using This App for General Management

In essence, this is an application that just runs on a Windows machine (it might also work on MacOS and Linux but you would probably need to use something other than plink) and executes commands on another (Linux) machine. Therefore, if desired, someone could take advantage of the simple directory and script fields to run scripts on any server. They would just need to create the scripts on the server, set the directory to wherever they are, and set the names of the start, stop or custom scripts.

This allows the software to be used by anyone that needs to, for any reason, run scripts remotely. It would probably be most useful for those unfamiliar with Linux and the Linux commands used to operate a headless machine. Additionally, I’m sure there are probably better solutions to this problem (extensively tested solutions for example) but this offers a simple, easy way to perform some basic tasks.

Leave a Reply

Your email address will not be published.