Skip to content

Commit cd340f2

Browse files
authored
Merge pull request #2 from g-otn/feat/terraria
feat: finish terraform support, misc fixes
2 parents 439bb5e + 1ce7e2f commit cd340f2

File tree

11 files changed

+219
-66
lines changed

11 files changed

+219
-66
lines changed

README.md

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ https://github.com/user-attachments/assets/e2e63d59-3a4e-4aaa-8513-30243aafa6c4
3232
- [Registering interaction endpoint](#registering-interaction-endpoint)
3333
- [Creating the guild commands](#creating-the-guild-commands)
3434
- [Automatic backups](#automatic-backups)
35+
- [Game specific post-setup](#game-specific-post-setup)
3536
- [Recommendations and notes](#recommendations-and-notes)
3637
- [**Game-specific notes**](#game-specific-notes)
3738
- [Regions](#regions)
@@ -54,10 +55,11 @@ https://github.com/user-attachments/assets/e2e63d59-3a4e-4aaa-8513-30243aafa6c4
5455
**Supported**
5556

5657
- Minecraft (via [itzg/docker-minecraft-server](https://github.com/itzg/docker-minecraft-server))
58+
- Terraria (via [ryshe/terraria](https://hub.docker.com/r/ryshe/terraria))
5759

5860
**Others:**
5961

60-
You can run other game servers Docker containers using the `custom` server module option. See [Custom game](#custom-game).
62+
You can run other game servers Docker containers using the `custom` server module option. See [Custom game](#custom-game) and custom game [example](#examples).
6163

6264
## Strategy
6365

@@ -264,7 +266,7 @@ Other "Common values" are also required but are the same between servers or/and
264266

265267
#### Examples
266268

267-
For a full example, check the [`servers.tf`](servers.tf) and [`regions.tf`](regions.tf) files themselves.
269+
For a full example, check the [`servers.tf`](servers.tf) and [`regions.tf`](regions.tf) files themselves. See [ryshe/terraria](https://hub.docker.com/r/ryshe/terraria/).
268270

269271
<details>
270272

@@ -382,6 +384,37 @@ EOT
382384

383385
<summary>Custom game</summary>
384386

387+
Creating a TShock Terraria server using the `custom` game option.
388+
See [ryshe/terraria](https://hub.docker.com/r/ryshe/terraria/).
389+
390+
```tf
391+
module "terraria" {
392+
source = "./server"
393+
394+
# Change these to desired values
395+
id = "CustomExample"
396+
game = "custom"
397+
custom_game_name = "Terraria"
398+
hostname = "gsed-example.duckdns.org"
399+
400+
instance_type = "m7g.medium"
401+
arch = "arm64"
402+
data_volume_size = 1
403+
404+
main_port = 7777
405+
compose_services = {
406+
main : {
407+
image : "ryshe/terraria"
408+
ports : ["7777:7777"]
409+
command : "-world ${local.terraria_workdir_path}/Worlds/CustomExample.wld -autocreate 3"
410+
volumes : ["/srv/terraria:${local.terraria_workdir_path}"]
411+
}
412+
}
413+
414+
// ...
415+
}
416+
```
417+
385418
</details>
386419

387420
### Applying
@@ -473,14 +506,16 @@ Daily snapshots of the data volume are taken via Data Lifecycle Manager. However
473506

474507
See also [Restoring a backup](#restoring-a-backup)
475508

476-
### Protecting your server from the internet
509+
### Game specific post-setup
477510

478-
Since you're running a public server, techinically **anyone on the internet** can join your server and do anything (grief, cheat, troll, etc).
511+
Since you're running a public server, techinically **anyone on the internet** can join your server and do anything (grief, cheat, crash the server, etc).
479512
This is most likely not desirable and you might want to do game-specific configuration
480-
to limit the server for you and your friends. (one of the purposes of this project)
513+
to limit the server for you and your friends.
481514

482515
These are done by SSH-ing into your instance and then running commands or modifying some game server configuration files. (See also [SSH](#ssh))
483516

517+
You'll also want to do this to load an existing save / world, depending on the game.
518+
484519
15. Please **check the "post-setup" section on each games' [Game-specific notes](#game-specific-notes)** for things you may want to do.
485520

486521
Check also [Useful info and commands](#useful-info-and-commands).
@@ -502,7 +537,7 @@ You can do that by op-ing yourself by creating an whitelist on the Minecraft ser
502537

503538
1. Connect to your running server instance using SSH (See [SSH](#ssh))
504539
2. Attach your terminal to the Minecraft server terminal by running `docker attach minecraft-mc-1`
505-
3. Run `whitelist <player name>` to whitelist someone. You could also give yourself admin using `op <your player name>` to run more commands from within your game chat.
540+
3. Run `whitelist add <player name>` to whitelist someone. You could also give yourself admin using `op <your player name>` to run more commands from within your game chat.
506541

507542
If you're running an offline server, you could also consider setup an auth plugin such as [AuthMeReloaded](https://www.spigotmc.org/resources/authmereloaded.6269/) (Spigot).
508543

@@ -535,6 +570,22 @@ Finally, save around 600MiB-1GiB for the JVM / Off-heap memory. Examples:
535570
536571
</details>
537572

573+
<details>
574+
<summary>Terraria</summary>
575+
576+
### Terraria post-setup
577+
578+
You should set up a server password so only you and your friends can join the server.
579+
580+
1. Connect to your running server instance using SSH (See [SSH](#ssh))
581+
2. Edit the file at `/srv/terraria/config.json`
582+
3. Set a password in the `Settings.ServerPassword` field
583+
4. Restart the server by running `docker compose restart terraria-terraria-1` or by restarting the whole instance.
584+
585+
See also TShock [Config Settings](https://tshock.readme.io/docs/config-settings) and [Setting Up Your Server](https://tshock.readme.io/docs/setting-up-your-server).
586+
587+
</details>
588+
538589
### Regions
539590

540591
Sometimes you want to change the AWS region you server is located at, due to latency and/or price. By default this project
@@ -612,6 +663,7 @@ To help choose a instance type different from the defaults, check out:
612663
- Grouping "Cost" by "Daily" can facilitate visualize how much (the instance alone) would cost for 24h of gameplay.
613664
- [Spot Instance advisor](https://aws.amazon.com/ec2/spot/instance-advisor/)
614665
- [aws-pricing.com Instance Picker](https://aws-pricing.com/picker.html)
666+
- [Geekbench Browser](https://browser.geekbench.com/search?utf8=✓&q=amazon+ec2) (CPU single-core and multi-core performance benchmarks)
615667

616668
If you choose a burstable instance types (`t4g`, `t3a`, `t3` and `t2`), check ["Things to keep in mind"](#things-to-keep-in-mind) in Cost breakdown
617669

@@ -639,21 +691,28 @@ by using the `custom` value in the `game` variable.
639691

640692
The game server must meet the following criteria:
641693

642-
- It can run on Linux
643-
- It is containerized using Docker (for example, using [steamcmd](https://hub.docker.com/r/steamcmd/steamcmd) image as base)
644-
- It can handle rare sudden shutdowns (due to the nature of Spot instances)
645-
- This means it is able to shut down gracefully or/and auto-save if needed, also if requested via Discord slash command.
646-
- The "main port" of the game server, the one players stay connected to, uses TCP (for auto-shutdown to work properly)
694+
- It can run on Linux 64-bits
695+
- It is containerized using Docker (for example, based on [CM2.Network steamcmd](https://cm2.network) or [steamcmd/steamcmd](https://hub.docker.com/r/steamcmd/steamcmd))
696+
- The container can be run on `x86_64` or `arm64` architectures
697+
- It can handle rare sudden shutdowns
698+
- This is due to the nature of Spot instances or if someone requests it via Discord slash commands
699+
- This means it should be able to shut down gracefully and preferrably auto-save periodically
700+
- It makes sense for this project:
701+
- This project allows part of the friend group to play on the server, without
702+
requiring the host player who has the save file to be online at all play sessions.
703+
Examples are Multiplayer "Open World Survival Craft" / Sandbox games.
704+
- Some games though, can only be played or only make sense to play when the all players are together.
647705

648-
To define a custom game server:
706+
To define a custom game server (see also custom game [example](#examples)):
649707

650708
1. Copy and paste a new server module usage in the [`servers.tf`](servers.tf) file.
651709
2. Set common server-specific values such as `id`, `az`, `hostname` and other DDNS config.
652710
3. Set `game` to `custom` and define an alphanumeric `custom_game_name` (e.g `CustomGame`).
653711
4. Set the game's networking using `main_port` and add `sg_ingress_rules` as needed.
654712
5. Set the game's available storage using `data_volume_size` and backup frequency and retation using the `data_volume_snapshot_*` variables.
655713
- If your volume is too big and/or the game data changes too much between snapshots (e.g big save files are compressed each time), consider lowering snapshot retention. Check AWS pricing calculator.
656-
6. Configure the Docker Compose file which will be used within the instance by setting up the `compose_*` variables.
714+
6. Configure the Docker Compose file which will be used within the instance by setting up at least the `compose_services` variable.
715+
- Create a service with the name `main`
657716
- Networking: Define the container `ports` to match `main_port` and `sg_ingress_rules`.
658717
- Storage: Define a volume matching `server_data_path`, which is based from lowercase value of `custom_game_name`
659718
(e.g `/srv/customgame`, See [server/main.tf](server/main.tf)).
@@ -667,6 +726,8 @@ To define a custom game server:
667726

668727
### SSH
669728

729+
You most likely will want to SSH into your instance at least once to maybe upload an existing save / world or to modify server configuration.
730+
670731
I recommend using some UI application to help navigate and manage files via SSH like [VS Code Remote Explorer](https://marketplace.visualstudio.com/items?itemName=ms-vscode.remote-explorer).
671732

672733
SSH into your instance using the `ec2-user` user. Example:

base_region/vpc.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
locals {
22
base_cidr_block = "10.0.0.0/16"
33
public_subnets = [for i in range(length(var.azs)) : cidrsubnet(local.base_cidr_block, 8, 101 + i)]
4-
public_subnet_names = [for i in range(length(var.azs)) : "${local.prefix_sm} Public Subnet ${i + 1}"]
4+
public_subnet_names = [for i in range(length(var.azs)) : "${local.prefix_sm} Public Subnet ${i + 1} (${var.azs[i]})"]
55
}
66

77
module "vpc" {

example.tfvars

Lines changed: 0 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -7,47 +7,3 @@ discord_bot_token = "MTIabcdefg.12345.hijklm"
77

88
duckdns_domain = "mydomain"
99
duckdns_token = "abcdefg-2057-4bd2-a6f5-4716e6d20516"
10-
11-
extra_ingress_rules = {
12-
// See https://registry.terraform.io/providers/hashicorp/aws/latest/docs/resources/security_group_rule#argument-reference
13-
"Simple Voice Chat" : {
14-
description = "Simple Voice Chat mod server"
15-
from_port = 24454
16-
to_port = 24454
17-
ip_protocol = "udp"
18-
cidr_ipv4 = "0.0.0.0/0"
19-
}
20-
}
21-
22-
instance_key_pair_public_key = "ssh-ed25519 AA...a minecraft-spot-discord"
23-
instance_timezone = "America/Bahia"
24-
25-
minecraft_port = 23456
26-
minecraft_compose_ports = ["23456:25565", "24454:24454/udp"]
27-
28-
minecraft_compose_environment = {
29-
"INIT_MEMORY" = "6000M"
30-
"MAX_MEMORY" = "6000M"
31-
32-
"ICON" = "https://picsum.photos/300/300"
33-
"MOTD" = " \u00A7b\u00A7l\u00A7kaaaaaaaa\u00A7r \u00A75\u00A7lGame EC2 Spot Discord\u00A7r \u00A7b\u00A7l\u00A7kaaaaaaaa\u00A7r"
34-
35-
"VERSION" = "1.20.4"
36-
"ONLINE_MODE" = false
37-
"PLUGINS" = <<EOT
38-
https://cdn.modrinth.com/data/9eGKb6K1/versions/AyVUPPCX/voicechat-bukkit-2.5.15.jar
39-
40-
https://cdn.modrinth.com/data/UmLGoGij/versions/mr2CijyC/DiscordSRV-Build-1.27.0.jar
41-
42-
https://cdn.modrinth.com/data/cUhi3iB2/versions/Ua2p3xKG/tabtps-spigot-1.3.23.jar
43-
44-
https://cdn.modrinth.com/data/MubyTbnA/versions/vbGiEu4k/FreedomChat-Paper-1.6.0.jar
45-
https://github.com/SkinsRestorer/SkinsRestorer/releases/download/15.0.13/SkinsRestorer.jar
46-
47-
https://download.luckperms.net/1544/bukkit/loader/LuckPerms-Bukkit-5.4.131.jar
48-
49-
https://github.com/dmulloy2/ProtocolLib/releases/download/5.2.0/ProtocolLib.jar
50-
https://ci.codemc.io/job/AuthMe/job/AuthMeReloaded/lastSuccessfulBuild/artifact/target/AuthMe-5.6.0-SNAPSHOT.jar
51-
https://ci.codemc.io/job/Games647/job/FastLogin/lastSuccessfulBuild/artifact/bukkit/target/FastLoginBukkit.jar
52-
EOT
53-
}

main.tf

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,9 @@ module "global" {
1212
discord_app_public_key = var.discord_app_public_key
1313
discord_bot_token = var.discord_bot_token
1414
}
15+
16+
17+
locals {
18+
// Helpful common strings
19+
terraria_workdir_path = "/root/.local/share/Terraria/Worlds"
20+
}

server/cloud-init.yml

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,20 @@ write_files:
6363
content: ${auto_shutdown_timer_file_content_b64}
6464
# %{ endif }
6565

66+
# %{ if watch_connections }
67+
# Connection watcher
68+
- path: /home/ec2-user/watch_conn.sh
69+
defer: true
70+
owner: ec2-user
71+
permissions: "0744"
72+
encoding: base64
73+
content: ${watch_conn_script_file_content_b64}
74+
- path: /etc/systemd/system/watch_conn.service
75+
defer: true
76+
encoding: base64
77+
content: ${watch_conn_service_file_content_b64}
78+
# %{ endif }
79+
6680
# %{ if ddns_service == "duckdns" }
6781
# Duck DNS files
6882
- path: /home/ec2-user/duck.sh
@@ -90,12 +104,15 @@ runcmd:
90104
- [
91105
curl,
92106
-SL,
93-
"https://github.com/docker/compose/releases/download/v2.27.3/docker-compose-linux-aarch64",
107+
'https://github.com/docker/compose/releases/download/v2.29.1/docker-compose-linux-%{ if arch == "x86_64" }x86_64%{ else }aarch64%{ endif }',
94108
-o,
95109
/usr/local/lib/docker/cli-plugins/docker-compose,
96110
]
97111
- chmod +x /usr/local/lib/docker/cli-plugins/docker-compose
98112

113+
# Manually install fastfetch
114+
- rpm -i 'https://github.com/fastfetch-cli/fastfetch/releases/download/2.20.0/fastfetch-linux-%{ if arch == "x86_64" }amd64%{ else }aarch64%{ endif }.rpm'
115+
99116
# Finish Docker setup
100117
- usermod -a -G docker ec2-user # Allow docker commands without sudo
101118
- systemctl enable docker
@@ -110,6 +127,12 @@ runcmd:
110127
# Start for the first time
111128
- systemctl enable compose_start
112129
- systemctl start compose_start
130+
131+
# %{ if watch_connections }
132+
- systemctl enable watch_conn.service
133+
- systemctl start watch_conn.service
134+
# %{ endif }
135+
113136
# %{ if auto_shutdown }
114137
- systemctl enable auto_shutdown.timer
115138
- systemctl start auto_shutdown.timer

server/dlm.tf

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ resource "aws_dlm_lifecycle_policy" "backup_data" {
3232
}
3333

3434
tags = {
35-
Name = "${local.prefix_id_game} Data volume backup Lifecycle Policy"
35+
Name = "${local.prefix_sm_id_game} Data volume backup Lifecycle Policy"
3636
}
3737
}

server/ec2.tf

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,13 @@ locals {
1212
compose_main_service_name = local.game.compose_main_service_name
1313
}))
1414

15+
watch_conn_service_file_content_b64 = base64encode(file("./server/systemd/watch_conn/watch_conn.service"))
16+
watch_conn_script_file_content_b64 = base64encode(templatefile("./server/systemd/watch_conn/watch_conn.sh", {
17+
main_port = local.game.main_port
18+
server_data_path = local.server_data_path
19+
compose_main_service_name = local.game.compose_main_service_name
20+
}))
21+
1522
duckdns_script_file_content_b64 = var.ddns_service == "duckdns" ? base64encode(templatefile("./server/ddns/duckdns/duck.sh", {
1623
duckdns_domain = local.duckdns_domain
1724
duckdns_token = var.duckdns_token
@@ -21,6 +28,8 @@ locals {
2128
ec2_user_data = templatefile("./server/cloud-init.yml", {
2229
instance_timezone = var.instance_timezone
2330

31+
arch = local.game.arch
32+
2433
server_data_path = local.server_data_path
2534
device_name = local.device_name
2635

@@ -34,6 +43,10 @@ locals {
3443
auto_shutdown_service_file_content_b64 = local.auto_shutdown_service_file_content_b64
3544
auto_shutdown_timer_file_content_b64 = local.auto_shutdown_timer_file_content_b64
3645

46+
watch_connections = local.game.watch_connections
47+
watch_conn_script_file_content_b64 = local.watch_conn_script_file_content_b64
48+
watch_conn_service_file_content_b64 = local.watch_conn_service_file_content_b64
49+
3750
duckdns_script_file_content_b64 = local.duckdns_script_file_content_b64
3851
duckdns_service_file_content_b64 = local.duckdns_service_file_content_b64
3952
})

server/main.tf

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ locals {
66

77
prefix = "GameServerEC2Discord"
88
prefix_sm = "GSED"
9-
prefix_id_game = "${local.prefix} ${var.id} ${var.game}"
10-
prefix_sm_id_game = "${local.prefix_sm} ${var.id} ${var.game}"
9+
prefix_id_game = "${local.prefix} ${var.id} ${local.game.game_name}"
10+
prefix_sm_id_game = "${local.prefix_sm} ${var.id} ${local.game.game_name}"
1111

1212
subnet_id = var.base_region.public_subnets[index(var.base_region.available_azs, var.az)]
1313

@@ -23,13 +23,25 @@ locals {
2323
data_volume_size = coalesce(var.data_volume_size, 10)
2424
compose_main_service_name = "mc"
2525
main_port = coalesce(var.main_port, 25565)
26+
watch_connections = coalesce(var.watch_connections, false)
27+
}
28+
terraria = {
29+
game_name = "Terraria"
30+
instance_type = coalesce(var.instance_type, "m7a.medium")
31+
arch = coalesce(var.arch, "x86_64")
32+
data_volume_size = coalesce(var.data_volume_size, 1)
33+
compose_main_service_name = "terraria"
34+
main_port = coalesce(var.main_port, 7777)
35+
watch_connections = coalesce(var.watch_connections, true)
2636
}
2737
custom = {
2838
game_name = var.custom_game_name
2939
instance_type = var.instance_type
40+
arch = var.arch
3041
data_volume_size = var.data_volume_size
3142
compose_main_service_name = "main"
3243
main_port = var.main_port
44+
watch_connections = coalesce(var.watch_connections, true)
3345
}
3446
}
3547
game = local.game_defaults_map[var.game]
@@ -76,6 +88,21 @@ locals {
7688
}
7789
}
7890
}, var.compose_top_level_elements)
91+
92+
terraria = merge({
93+
services : {
94+
"${local.game_defaults_map.terraria.compose_main_service_name}" : {
95+
image : "ryshe/terraria",
96+
ports : coalesce(var.compose_game_ports, ["7777:7777"]),
97+
environment : merge({}, var.compose_game_environment)
98+
command : "-world /root/.local/share/Terraria/Worlds/${var.id}.wld -autocreate ${var.terraria_world_size}"
99+
volumes : [
100+
"${local.server_data_path}:/root/.local/share/Terraria/Worlds"
101+
]
102+
}
103+
}
104+
}, var.compose_top_level_elements)
105+
79106
custom = merge({
80107
services : var.compose_services
81108
}, var.compose_top_level_elements)

0 commit comments

Comments
 (0)