Skip to content

Commit

Permalink
Initial revision: cloudformation template with UserData for calling i…
Browse files Browse the repository at this point in the history
…n further setup from this repository.
  • Loading branch information
TSheahan committed Apr 1, 2024
1 parent 029a2ad commit 07a4ddd
Show file tree
Hide file tree
Showing 4 changed files with 297 additions and 0 deletions.
125 changes: 125 additions & 0 deletions cloudformation_server_stack.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
AWSTemplateFormatVersion: '2010-09-09'
Description: AWS CloudFormation Template for a hosted game server

Parameters:
ServerPortNumberStart:
Type: Number
Description: First TCP port for game server traffic

ServerPortNumberEnd:
Type: Number
Description: Last TCP port for game server traffic

SetupCommand:
Type: String
Description: Defines the setup invocation. Permits code execution. Direct secrets prohibited.

ExistingVolumeId:
Type: String
Description: ID of an existing EBS volume to attach. Leave empty to create a new volume.
Default: ""

Conditions:
CreateNewVolume: !Equals [ !Ref ExistingVolumeId, "" ]

Resources:
ServerInstance:
Type: AWS::EC2::Instance
Properties:
InstanceType: t3.large # 8GB, 2vcpu, 0.1056 USD/hour
ImageId: ami-02765a2a1f276edff # Amazon Linux 2 AMI (HVM) - Kernel 5.10, SSD Volume Type, 64-bit (x86) / https://ap-southeast-4.console.aws.amazon.com/ec2/home?region=ap-southeast-4#AMICatalog:
KeyName: tim_ssh_to_game_server # EC2 / key pairs
SecurityGroupIds:
- !Ref ServerSecurityGroup
UserData:
Fn::Base64: !Sub |
#!/bin/bash

# Australia/Melbourne should be all that anyone will ever need... right?
timedatectl set-timezone Australia/Melbourne

echo "!! Check whether the attached volume is formatted.."
REAL_DEVICE=$(sudo readlink -f /dev/sdf)
echo "/dev/sdf maps to $REAL_DEVICE by symlink check"
if [ "$(sudo file -s $REAL_DEVICE | awk '{print $2}')" == "data" ]; then
echo "$REAL_DEVICE is not formatted, formatting as ext4..."
sudo mkfs -t ext4 $REAL_DEVICE
else
echo "$REAL_DEVICE is already formatted."
fi

echo "!! Mount the attached volume to /mnt/persist"
sudo mkdir -p /mnt/persist
sudo mount $REAL_DEVICE /mnt/persist
echo "$REAL_DEVICE /mnt/persist ext4 defaults,nofail 0 2" | sudo tee -a /etc/fstab > /dev/null

echo "!! Clone and use AWS-Games"
yum install -y git
cd /home/ec2-user
git clone https://github.com/TSheahan/AWS-Games.git
chown -R ec2-user:ec2-user AWS-Games
cd AWS-Games

echo "!! Execute SetupCommand"
# as root, invoke the setup command, expecting it to handle system & user setup tasks
# use due caution for code execution capability
${SetupCommand}


NewVolume:
Type: AWS::EC2::Volume
Condition: CreateNewVolume
Properties:
Size: 10 # Adjust as needed for world files - 1.13GB after 1 months minecraft play
VolumeType: gp3
AvailabilityZone: !GetAtt ServerInstance.AvailabilityZone
Tags:
- Key: "Purpose"
Value: "GameServerPersistentFiles"
DeletionPolicy: Retain

PersistentVolumeAttachment:
Type: AWS::EC2::VolumeAttachment
Properties:
InstanceId: !Ref ServerInstance
VolumeId: !If [CreateNewVolume, !Ref NewVolume, !Ref ExistingVolumeId]
Device: /dev/sdf

ServerEIP:
Type: AWS::EC2::EIP
Properties:
InstanceId: !Ref ServerInstance

ServerSecurityGroup:
Type: AWS::EC2::SecurityGroup
Properties:
GroupDescription: Allow SSH and game traffic
SecurityGroupIngress:
- IpProtocol: tcp
FromPort: 22
ToPort: 22
CidrIp: 0.0.0.0/0
- IpProtocol: tcp
FromPort: !Ref ServerPortNumberStart # Destination port range start
ToPort: !Ref ServerPortNumberEnd # Destination port range end
CidrIp: 0.0.0.0/0

Outputs:
ServerIP:
Description: IP Address of the server
Value: !Ref ServerEIP
ServerPortStart:
Description: First TCP port used for the server
Value: !Ref ServerPortNumberStart
ServerPortEnd:
Description: Last TCP port used for the server
Value: !Ref ServerPortNumberEnd
SetupCommand:
Description: Displays the SetupCommand which was used at instance instantiation
Value: !Ref SetupCommand
NewVolumeId:
Description: The ID of the newly created EBS volume (if created).
Value: !If [ CreateNewVolume, !Ref NewVolume, "" ]
ExistingVolumeId:
Description: Mirrors the input for optional existing EBS volume ID.
Value: !Ref ExistingVolumeId
141 changes: 141 additions & 0 deletions minecraft/setup.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
#!/bin/env bash

cat <<'EOF'
======================================================================
This script sets up a Minecraft server with the following configurations:
- Server Folder: The directory where the server files are stored.
- Server Version: The version of the Minecraft server to be deployed.
- used to compose jar filename upon download
- JAR URL: The URL from which the Minecraft server JAR file is downloaded.
Usage:
minecraft/setup.sh --server-folder=<path> --server-version=<version> --jar-url=<url>
The script is designed to be invoked as root via the UserData script of a
linux game server instance (defined in a CloudFormation template).
It expects to be executed from the repository root folder.
The SetupCommand parameter must include the needed arguments.
======================================================================
EOF

if [ ! -d ../AWS-Games ]; then
echo "setup must run from repository root"
exit 1
fi

# Initialize variables to hold the values of the arguments
serverFolder=""
serverVersion=""
jarUrl=""

# Error function to display an error message and exit
error() {
echo "Error: $1" >&2
echo "Usage: $0 --server-folder=<path> --server-version=<version> --jar-url=<url>" >&2
exit 1
}

# Loop through arguments and process them
for arg in "$@"
do
case $arg in
--server-folder=*)
serverFolder="${arg#*=}"
;;
--server-version=*)
serverVersion="${arg#*=}"
;;
--jar-url=*)
jarUrl="${arg#*=}"
;;
*)
# Unknown option
error "Unknown argument ${arg}"
;;
esac
done

# Check if any of the required arguments are missing
if [ -z "$serverFolder" ]; then
error "server-folder argument is required"
fi
if [ -z "$serverVersion" ]; then
error "server-version argument is required"
fi
if [ -z "$jarUrl" ]; then
error "jar-url argument is required"
fi

# If all arguments are provided, proceed with the rest of the script
echo "Server Folder: $serverFolder"
echo "Server Version: $serverVersion"
echo "JAR URL: $jarUrl"


echo "!! Install JDK"
yum update -y
yum install -y java-17-amazon-corretto-devel

echo "!! Check Java version"
java -version

echo "!! Ensure /mnt/persist/minecraft exists and is owned by ec2-user"
mkdir -p /mnt/persist/minecraft
chown ec2-user:ec2-user /mnt/persist/minecraft

echo "!! Ensure the server folder exists and is owned by ec2-user"
mkdir -p "/mnt/persist/minecraft/${serverFolder}"
chown ec2-user:ec2-user "/mnt/persist/minecraft/${serverFolder}"

echo "!! Write /etc/systemd/system/minecraft-server.service"
cat << EOF > /etc/systemd/system/minecraft-server.service
[Unit]
Description=Minecraft Server
After=network.target
[Service]
User=ec2-user
WorkingDirectory=/mnt/persist/minecraft/${serverFolder}
ExecStart=/mnt/persist/minecraft/${serverFolder}/start-minecraft.sh
ExecStop=/mnt/persist/minecraft/${serverFolder}/stop-minecraft.sh
TimeoutStopSec=60
[Install]
WantedBy=multi-user.target
EOF

startScriptPath="/mnt/persist/minecraft/${serverFolder}/start-minecraft.sh"
echo "!! Install the start-minecraft.sh wrapper script at $startScriptPath"
cp minecraft/start-minecraft.sh "$startScriptPath"
# Make the script executable
chmod +x "$startScriptPath"
# Ensure the script is owned by ec2-user
chown ec2-user:ec2-user "$startScriptPath"

stopScriptPath="/mnt/persist/minecraft/${serverFolder}/stop-minecraft.sh"
echo "!! Install the stop-minecraft.sh wrapper script at $stopScriptPath"
cp minecraft/stop-minecraft.sh "$stopScriptPath"
# Make the script executable
chmod +x "$stopScriptPath"
# Ensure the script is owned by ec2-user
chown ec2-user:ec2-user "$stopScriptPath"

jarPath="/home/ec2-user/minecraft_server_${serverVersion}.jar"
echo "!! Download Minecraft server JAR to $jarPath"
sudo -u ec2-user wget -O "$jarPath" "$jarUrl"

symlinkPath="/mnt/persist/minecraft/${serverFolder}/minecraft_server.jar"
echo "!! Symlink the jar to $symlinkPath"
sudo -u ec2-user ln -s "$jarPath" "$symlinkPath"

echo "!! reload systemd"
# Reload systemd to recognize the new service and enable it to start on boot
systemctl daemon-reload
systemctl enable minecraft-server.service

# echo "!! start minecraft-server"
# systemctl start minecraft-server.service
# ? consider rebooting here..
28 changes: 28 additions & 0 deletions minecraft/start-minecraft.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
#!/bin/bash

if [ ! -e server.properties ]; then
echo "server.properties not found, aborting."
exit 1
fi

# Flag indicating if the installed version of screen is < 4.06
# that does not support the -Logfile argument.
# Set legacyScreen=1 for true, and legacyScreen=0 for false.
legacyScreen=1

logFile="console_$(date +"%Y-%m-%d_%H-%M-%S").log"
# TODO:
# - investigate how closely this mirrors the minecraft logs in /logs
# - verify the stop workflow vs instance shutdown - does the minecraft server stop properly?

echo "logfile is $logFile"

if [ "$legacyScreen" -eq 1 ]; then
cat << EOF >/tmp/screenrc.$$
logfile $logFile
EOF
/usr/bin/screen -DmS minecraft -L -c /tmp/screenrc.$$ java -Xmx4092M -Xms4092M -jar minecraft_server.jar
rm /tmp/screenrc.$$
else
/usr/bin/screen -DmS minecraft -L -Logfile "$logFile" java -Xmx4092M -Xms4092M -jar minecraft_server.jar
fi
3 changes: 3 additions & 0 deletions minecraft/stop-minecraft.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/usr/bin/screen -S minecraft -p 0 -X stuff "/say systemd is shutting down this service.^M"
sleep 5
/usr/bin/screen -S minecraft -p 0 -X stuff "/stop^M"

0 comments on commit 07a4ddd

Please sign in to comment.