#!/bin/bash

clear -x
echo "This script should be run as root on a clean install of a Debian x64 system."
echo "You can choose to do a full install, or update a server which was previously"
echo "installed with the same script."
echo ""
echo "This script does: create a user and a script to start the server."
echo ""
echo "This script is able to: update existing servers, install Java, download the"
echo "Fabric installer, install a Minecraft server, create a systemd unit with a"
echo "socket, and create a cmd.sh script and 'cmd' command to run console commands."
echo ""
echo "NOTICE: The Minecraft Fabric installer supports versions from 1.14 to latest."
echo "        For another Minecraft version, download the desired server installer"
echo "        file manually before running this script."
echo "NOTICE: The available options for Java are: 8, 16, 17, and 21."
echo "        For another Java version, install it manually."
while true; do
	echo ""
	read -p "  Are you ready to proceed? [Y/n] " proceed
	case $proceed in
		[yY]|"" )
			break;;
		[nN] )
			echo "=> Exiting."; exit;;
		* )
			echo "=> Invalid response.";;
	esac
done

echo ""
echo "=> Running compatibility tests..."

echo -n "=> Checking curl... "
curl --version &> /dev/null && echo "PASS" || { test1="failed"; echo "FAIL"; }

echo -n "=> Checking systemd... "
systemd-notify --booted && echo "PASS" || { test2="failed"; echo "FAIL"; }

echo -n "=> Checking root privileges... "
if [ "$EUID" == 0 ]; then echo "PASS"; else { test3="failed"; echo "FAIL"; } fi

echo -n "=> Checking /etc/systemd/system/ permissions... "
touch /etc/systemd/system/permission.test &> /dev/null && rm /etc/systemd/system/permission.test &> /dev/null && echo "PASS" || { test4="failed"; echo "FAIL"; }

echo -n "=> Checking internet connection... "
ping -c 1 8.8.8.8 &> /dev/null && echo "PASS" || { test5="failed"; echo "FAIL"; }

echo -n "=> Checking DNS resolution... "
ping -c 1 google.com &> /dev/null&& echo "PASS" || { test6="failed"; echo "FAIL"; }

if [ "$test1" == "failed" ]; then
	echo "WARNING: curl command error, downloads will fail."
	testFailed="true"; fi
if [ "$test2" == "failed" ]; then
	echo "WARNING: this system is not using systemd, please skip creating unit files."
	testFailed="true"; fi
if [ "$test3" == "failed" ]; then
	echo "WARNING: root privileges are not detected, please run again as root."
	testFailed="true"; fi
if [ "$test4" == "failed" ]; then
	echo "WARNING: cannot write to systemd directory, please skip creating unit files."
	testFailed="true"; fi
if [ "$test5" == "failed" ]; then
	echo "WARNING: cannot connect to the internet, downloads will fail."
	testFailed="true"; fi
if [ "$test6" == "failed" ]; then
	echo "WARNING: cannot resolve domain names, downloads will fail."
	testFailed="true"; fi

if [ "$testFailed" == "true" ]; then
	while true; do
		echo ""
		read -p "  At least one check failed, are you sure you want to proceed? [y/N] " skipwarn
		case $skipwarn in
			[yY] ) echo "=> Ignoring warnings..."; break;;
			[nN]|"" ) echo "=> Exiting."; exit;;
			* ) echo "=> Invalid response.";;
		esac
	done
else
	echo ""
	echo "=> All checks complete."
fi

echo ""
echo "Choose to install a new server or update an existing one."
echo "WARNING: Only update an existing server if it was initially installed using this"
echo "         script, or if you know that it's been set up in the same way."
echo "NOTICE: This will replace any existing /home/minecraft-old/ directory."
while true; do
echo ""
read -p "  Update an existing server? [y/N] " update
case $update in
	[yY] )
		update="true"
		echo "=> Updating..."
		break;;
	[nN]|"" )
		echo "=> Skipping..."
		break;;
	* )
		echo "=> Invalid response.";;
esac
done
if [ "$update" == "true" ]; then
	echo -n " -> Removing previous minecraft-old directory... "
	rm -rf /home/minecraft-old/ && echo "DONE"
	echo -n " -> Backing up current minecraft home directory... "
	mv /home/minecraft/ /home/minecraft-old/ && echo "DONE"
	echo -n " -> Creating new minecraft home directory... "
	mkdir -p /home/minecraft && echo "DONE"
	echo -n " -> Copying world... "
	cp -R /home/minecraft-old/world/ /home/minecraft/world/ && echo "DONE"
	echo -n " -> Copying mods... "
	cp -R /home/minecraft-old/mods/ /home/minecraft/mods/ && echo "DONE"
	echo -n " -> Copying config... "
	cp -R /home/minecraft-old/config/ /home/minecraft/config/ && echo "DONE"
	echo -n " -> Copying defaultconfigs... "
	cp -R /home/minecraft-old/defaultconfigs/ /home/minecraft/defaultconfigs/ && echo "DONE"
	echo -n " -> Copying properties and player lists... "
	cp /home/minecraft-old/banned-ips.json /home/minecraft-old/banned-players.json /home/minecraft-old/ops.json /home/minecraft-old/server.properties /home/minecraft-old/whitelist.json /home/minecraft/ && echo "DONE"
	echo "=> Finished copying data."
	echo "NOTICE: After completing the installation, please check for other config files"
	echo "        that you may want to keep such as mod-specific folders, logs, etcetera."
	echo "NOTICE: Mods must be updated manually, please do so before starting the server."
else
	echo ""
	echo "=> Creating 'minecraft' user..."
	mkdir -p /home/minecraft
	useradd -d /home/minecraft/ minecraft &> /dev/null && {
		echo " -> Set a password for the user:"
		passwd minecraft
	}
fi

cd /home/minecraft/
echo ""
echo "Choose a valid version of Java."
echo "Minecraft requires Java 8 for 1.16.5 and earlier, Java 16 for 1.17-1.17.1,"
echo "Java 17 for 1.18-1.20.4, and Java 21 for 1.20.5 and later."
while true; do
	echo ""
	read -p "  Enter a valid version (ENTER to skip): " java
	case $java in
		"" )
			echo "=> Skipping installation..."
			break;;
		* )
			if [[ "$java" =~ ^[0-9]+$ ]]; then
				echo "=> Installing Java $java (Adoptium Eclipse Temurin)..."
				echo " -> Installing dependencies..."
				apt install -y wget apt-transport-https gpg
				echo " -> Installing GPG key..."
				wget -qO - https://packages.adoptium.net/artifactory/api/gpg/key/public | gpg --dearmor | tee /etc/apt/trusted.gpg.d/adoptium.gpg > /dev/null
				echo " -> Adding apt repository..."
				echo "deb https://packages.adoptium.net/artifactory/deb $(awk -F= '/^VERSION_CODENAME/{print$2}' /etc/os-release) main" | tee /etc/apt/sources.list.d/adoptium.list
				echo " -> Updating repositories..."
				apt update -y
				echo " -> Installing temurin-$java-jdk..."
				apt install -y temurin-$java-jdk && {
					echo "=> Installed Adoptium Eclipse Temurin $java."
					break
				} || {
					echo "=> Failed to install temurin-$java-jdk."
				}
			else
				echo "=> Invalid response."
			fi ;;
	esac
done

echo ""
echo "Choose if you want to use the Fabric server installer."
echo "The Fabric installer supports Minecraft 1.14 to latest."
while true; do
	echo ""
	read -p "  Use Fabric server installer? [Y/n] " method
	case $method in
		[yY]|"" )
			install="true"
			method="fabric"
			break;;
		[nN] )
			echo "=> Skipping Fabric installer..."
			break;;
		* )
			echo "=> Invalid response.";;
	esac
done
if [ "$install" != "true" ]; then
	echo ""
	echo "Choose if you want to use an existing server installer."
	echo "This requires an existing and accessible .jar file."
	while true; do
		echo ""
		read -p "  Use other server installer? [Y/n] " method
		case $method in
			[yY]|"" )
				install="true"
				method="manual"
				break;;
			[nN] )
				echo "=> Skipping manual installation..."
				break;;
			* )
				echo "=> Invalid response.";;
		esac
	done
fi

if [ "$install" == "true" ]; then
	if [ "$method" == "fabric" ]; then
		echo "=> Downloading Fabric installer..."
		curl -s -o 'fabric-installer.jar' https://maven.fabricmc.net/net/fabricmc/fabric-installer/1.0.1/fabric-installer-1.0.1.jar

		while true; do
			echo ""
			read -p "  Select valid Minecraft version to install (ENTER for latest): " mc
			case $mc in
				"" )
					mc="latest"
					echo "=> Installing latest Fabric server..."
					java -jar fabric-installer.jar server -downloadMinecraft && break;;
				* )
					echo "=> Installing $mc Fabric server..."
					java -jar fabric-installer.jar server -mcversion $mc -downloadMinecraft && break || echo "=> Invalid version.";;
			esac
		done
		echo "=> Installed $mc Fabric server."
		echo "=> Removing installer..."
		rm fabric-installer.jar
	elif [ "$method" == "manual" ]; then
		echo ""
		echo "Enter the server installer file path."
		echo "Working directory is /home/minecraft for relative file paths."
		while true; do
			echo ""
			read -p "  Enter the server installer path: " serverjar
			if [ ! -f $serverjar ]; then
				echo "=> File not found."
			else
				echo "=> Found file."
				break
			fi
		done
		echo ""
		echo "Choose server installer arguments."
		echo "Forge should use 'nogui --installServer' args."
		echo "You can use '--nogui' on newer versions."
		while true; do
			echo ""
			read -p "  Enter any installer arguments (ENTER for 'nogui'): " serverArgs
			case $serverArgs in
				"" )
					serverArgs="nogui"
			esac
			echo "=> Installing server..."
			java -jar $serverjar $serverArgs && {
				echo "=> Installed server."
				break
			} || {
				echo "=> Failed to install server."
			}
		done
		echo "=> Removing installer..."
		rm $serverjar
	fi

	echo "=> Running server once to generate files..."
	if [ "$method" == "fabric" ]; then
		runjar="fabric-server-launch.jar"
	elif [ "$method" == "manual" ]; then
		while true; do
			echo ""
			read -p "  Enter the .jar file that runs the server (ENTER for *.jar): " runjar
			case $runjar in
				"" )
					runjar=$(ls *.jar | head -1);;
			esac
			if [ ! -f $runjar ]; then
				echo " -> File not found."
			else
				break
			fi
		done
		echo " -> Selected $runjar"
		echo " -> Running server..."
	fi
	java -jar $runjar nogui
	echo "=> Server files created."

	while true; do
		echo ""
		read -p "  Agree to the EULA? [Y/n] " eula
		case $eula in
			[yY]|"" )
				if [ ! -f eula.txt ]; then
					echo "eula=true" > eula.txt
				else
					sed -i '3s/.*/eula=true/' eula.txt
				fi
				echo "=> EULA set to true."
				break;;
			[nN] )
				echo "=> Skipping..."
				break;;
			* )
				echo "=> Invalid response.";;
		esac
	done
fi

echo "=> Creating run script with Java arguments..."
touch /home/minecraft/run.sh
chmod +x /home/minecraft/run.sh
echo ""
echo "Choose allocated server memory in GB"
echo "You should leave roughly 1-2GB available for the host. E.g. use up to 7GB if"
echo "the system has 8GB. I recommend using 3-6 for best performance, and avoiding >8"
echo "unless there are many players."
while true; do
	echo ""
	read -p "  Enter allocated memory [1-24] (ENTER to use 4GB): " mem
	case $mem in
		[1-9]|1[0-9]|2[0-4] )
			echo "=> Allocating ${mem}GB..."
			break;;
		"" )
			echo "=> Allocating 4GB..."
			mem="4"
			break;;
		* )
			echo "=> Invalid response.";;
	esac
done
if [ ! "$runjar" ]; then
	echo ""
	echo "=> No .jar file selected to run server."
	while true; do
		echo ""
		read -p "  Enter the .jar file that runs the server (ENTER for *.jar): " runjar
		case $runjar in
			"" )
				runjar="*.jar";;
		esac
		if [ ! -f $runjar ]; then
			echo " -> File not found."
		else
			break
		fi
	done
fi
cat > /home/minecraft/run.sh << EOF
java -Xmx${mem}G -Xms${mem}G -XX:+UseG1GC -Dsun.rmi.dgc.server.gcInterval=2147483646 -XX:+UnlockExperimentalVMOptions -XX:G1NewSizePercent=20 -XX:G1ReservePercent=20 -XX:MaxGCPauseMillis=50 -XX:G1HeapRegionSize=32M -jar $runjar nogui
EOF
echo "=> Created run script in home directory."

if [ "$update" != "true" ]; then
	echo ""
	echo "Choose to set up autostart by creating systemd unit files."
	echo "This would allow you to start/stop the server with systemctl, view logs with"
	echo "journalctl, and send commands to Minecraft's server console in the background."
	while true; do
		echo ""
		read -p "  Do you want to set up a systemd unit? [Y/n] " sysd
		case $sysd in
			[yY]|"" )
				sysd="install"
				break;;
			[nN] )
				sysd="skip"
				echo "=> Skipping..."
				break;;
			* )
				echo "=> Invalid response.";;
		esac
	done
fi

if [ "$sysd" == "install" ]; then
echo "=> Creating systemd unit files..."

cat > /etc/systemd/system/minecraft.service << EOF
[Unit]
Description=A dedicated Minecraft server
After=network.target

[Service]
User=minecraft
Type=simple
KillSignal=SIGCONT
Sockets=minecraft.socket
WorkingDirectory=/home/minecraft/
ExecStart=/bin/sh -c "/home/minecraft/run.sh < /run/minecraft.control"
ExecStop=/bin/sh -c "echo '/stop' > /run/minecraft.control"
Type=exec
Restart=on-failure

[Install]
WantedBy=multi-user.target
EOF

cat > /etc/systemd/system/minecraft.socket << EOF
[Unit]
BindsTo=minecraft.service

[Socket]
ListenFIFO=/run/minecraft.control
FileDescriptorName=control
RemoveOnStop=true
SocketMode=0660
SocketUser=minecraft
EOF

echo "=> Created systemd unit files."
systemctl enable minecraft.service &> /dev/null
echo "=> Enabled systemd service."
echo "=> The server is set up to autostart and restart automatically."

echo "=> Creating cmd script in home directory..."
cat > /home/minecraft/cmd.sh << EOF
#!/bin/bash

sendCommand() {
	sleep 0.125
	echo "\$*" > /run/minecraft.control
}

sendCommand "\$*" &
journalctl -o cat -fn 0 -u minecraft.service
EOF
chmod +x /home/minecraft/cmd.sh
echo "=> Created console script in home directory."
echo "INFO: The console script passes all arguments to the server, and returns the"
echo "      output until stopped with Ctrl+C."

echo ""
echo "Choose to install a cmd.sh link in /usr/bin/."
echo "Creates a link to /home/minecraft/cmd.sh, allowing you to run server commands"
echo "more easily using just 'cmd'. E.g. 'cmd /gamerule keepInventory true'"
while true; do
	echo ""
	read -p "  Add cmd.sh link at /usr/bin/cmd? [Y/n] " cmd
	case $cmd in
		[yY]|"" )
			if which cmd &> /dev/null; then
				cmd="false"
				echo "=> Command 'cmd' already exists. Skipping..."
				break
			else
				cmd="true"
				ln -s /home/minecraft/cmd.sh /usr/bin/cmd
				echo "=> Created 'cmd' link."
				break
			fi ;;
		[nN] )
			cmd="false"
			echo "=> Skipping..."
			break;;
		* )
			echo "=> Invalid response.";;
	esac
done
fi

chown -R minecraft /home/minecraft
echo "=> User 'minecraft' now owns all files in home directory."

if [ "$install" == "true" ]; then
	echo ""
	echo "Installation complete, server is not running."
	echo "NOTICE: Please configure server.properties before starting the server."
	if [ "$fabric" == "install" ]; then
		echo "NOTICE: It's recommended to download performance mods such as Lithium,"
		echo "        FerriteCore, and ModernFix."
	fi
	if [ "$sysd" == "install" ]; then
		echo ""
		echo "Systemd is in use, the server will automatically start on boot."
		echo "NOTICE: The server can be stopped by using either systemd, the /stop command,"
		echo "        or shutting down the host. Each method performs a clean shutdown, so the"
		echo "        server won't lose data. You may notice a delay shutting down the host as"
		echo "        the Minecraft server stops."

		echo ""
		echo "Many commands are available with systemd, some important ones are:"
		echo "Executing commands in the Minecraft server console using a script:"
		echo "  $ ./cmd.sh <command here>"
		if [ "$cmd" == "true" ]; then
			echo "Or by using the cmd command:"
			echo "  # cmd <command here>"
		fi
		echo "View the server logs with journalctl: (Shift+G jumps to the end)"
		echo "  $ journalctl -u minecraft"
		echo "Or follow them as they update with:"
		echo "  $ journalctl -fu minecraft"
		echo "And easily perform a clean start/stop of the server with:"
		echo "  # systemctl {start,stop,restart} minecraft"
	else
		echo ""
		echo "Start the server with the run script, or set up a user crontab for autostart."
		echo "  $ ./run.sh"
	fi
else
	echo "=> Finished."
fi