├── .gitignore ├── versions.tf ├── examples ├── complete │ ├── versions.tf │ ├── outputs.tf │ ├── main.tf │ ├── README.md │ └── LICENSE ├── defaults │ ├── versions.tf │ ├── main.tf │ ├── README.md │ ├── outputs.tf │ └── LICENSE ├── s3-exists │ ├── versions.tf │ ├── main.tf │ ├── README.md │ ├── outputs.tf │ └── LICENSE └── latest_snapshot │ ├── versions.tf │ ├── main.tf │ ├── README.md │ ├── outputs.tf │ └── LICENSE ├── LICENSE ├── outputs.tf ├── server_download.sh ├── README.md ├── variables.tf ├── user_data.sh └── main.tf /.gitignore: -------------------------------------------------------------------------------- 1 | .terraform 2 | terraform.tfstate 3 | *.tfstate* 4 | terraform.tfvars 5 | local/* 6 | .DS_Store 7 | *.pem 8 | *.jar 9 | *.json* 10 | -------------------------------------------------------------------------------- /versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 0.12.24" 3 | 4 | required_providers { 5 | aws = "~> 2.58.0" 6 | template = "~> 2.1.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/complete/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 0.12.24" 3 | 4 | required_providers { 5 | aws = "~> 2.58.0" 6 | template = "~> 2.1.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/defaults/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 0.12.24" 3 | 4 | required_providers { 5 | aws = "~> 2.58.0" 6 | template = "~> 2.1.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/s3-exists/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 0.12.24" 3 | 4 | required_providers { 5 | aws = "~> 2.58.0" 6 | template = "~> 2.1.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/defaults/main.tf: -------------------------------------------------------------------------------- 1 | // Create a server using all defaults 2 | 3 | provider "aws" { 4 | region = "us-west-2" 5 | } 6 | 7 | module "minecraft" { 8 | source = "../../" 9 | } 10 | -------------------------------------------------------------------------------- /examples/latest_snapshot/versions.tf: -------------------------------------------------------------------------------- 1 | terraform { 2 | required_version = "~> 0.12.24" 3 | 4 | required_providers { 5 | aws = "~> 2.58.0" 6 | template = "~> 2.1.2" 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /examples/s3-exists/main.tf: -------------------------------------------------------------------------------- 1 | // Create a server using all defaults 2 | 3 | provider "aws" { 4 | region = "us-west-2" 5 | } 6 | 7 | module "minecraft" { 8 | source = "../../" 9 | bucket_name = "games-minecraft-abcdef123456" 10 | } 11 | -------------------------------------------------------------------------------- /examples/latest_snapshot/main.tf: -------------------------------------------------------------------------------- 1 | // Create a server from the latest snapshot 2 | 3 | provider "aws" { 4 | region = "us-east-1" 5 | } 6 | 7 | module "minecraft" { 8 | source = "../../" 9 | 10 | mc_type = "snapshot" 11 | 12 | bucket_force_destroy = true 13 | 14 | } 15 | -------------------------------------------------------------------------------- /examples/complete/outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | value = module.minecraft.vpc_id 3 | } 4 | 5 | output "subnet_id" { 6 | value = module.minecraft.subnet_id 7 | } 8 | 9 | output "public_ip" { 10 | value = module.minecraft.public_ip 11 | } 12 | 13 | output "id" { 14 | value = module.minecraft.id 15 | } 16 | 17 | output "minecraft_server" { 18 | value = module.minecraft.minecraft_server 19 | } 20 | 21 | -------------------------------------------------------------------------------- /examples/complete/main.tf: -------------------------------------------------------------------------------- 1 | // Create a server using all defaults 2 | 3 | provider "aws" { 4 | region = "us-west-2" 5 | } 6 | 7 | module "minecraft" { 8 | source = "../../" 9 | 10 | name = "foo" 11 | namespace = "bar" 12 | environment = "baz" 13 | 14 | vpc_id = "vpc-a92a1dcc" 15 | subnet_id = "subnet-2a21194f" 16 | 17 | bucket_name = "games-minecraft-sokigzrji25e" 18 | 19 | ami = "ami-0d6621c01e8c2de2c" 20 | key_name = "dubldee@gmail.com" 21 | 22 | mc_port = 30000 23 | mc_root = "/home/mc" 24 | mc_version = "1.15.2" 25 | mc_backup_freq = 10 26 | 27 | java_ms_mem = "1G" 28 | java_mx_mem = "1G" 29 | 30 | 31 | tags = { By = "me" } 32 | } 33 | -------------------------------------------------------------------------------- /examples/defaults/README.md: -------------------------------------------------------------------------------- 1 | # Example using all defaults 2 | 3 | Note: This will create resources that are not "free tier" eligible. 4 | 5 | ## Usage 6 | 7 | By default, this will use your "default" AWS credentials profile if installed. Otherwise, set your credentials: 8 | 9 | ``` 10 | export AWS_ACCESS_KEY_ID="anaccesskey" 11 | export AWS_SECRET_ACCESS_KEY="asecretkey" 12 | ``` 13 | 14 | * Edit `region` in `main.tf` and then build: 15 | 16 | ``` 17 | terraform init 18 | terraform plan -out=/tmp/mc 19 | terraform apply /tmp/mc 20 | ``` 21 | 22 | Add the server IP to your Minecraft client and refresh until the server becomes available. The provisioner script performing the initial S3 world sync can take a few minutes depending on your Minecraft world size. 23 | 24 | ## Authors 25 | 26 | [Darrell Davis](https://github.com/darrelldavis) 27 | 28 | ## License 29 | MIT Licensed. See LICENSE for full details. 30 | 31 | -------------------------------------------------------------------------------- /examples/latest_snapshot/README.md: -------------------------------------------------------------------------------- 1 | # Example using all defaults 2 | 3 | Note: This will create resources that are not "free tier" eligible. 4 | 5 | ## Usage 6 | 7 | By default, this will use your "default" AWS credentials profile if installed. Otherwise, set your credentials: 8 | 9 | ``` 10 | export AWS_ACCESS_KEY_ID="anaccesskey" 11 | export AWS_SECRET_ACCESS_KEY="asecretkey" 12 | ``` 13 | 14 | * Edit `region` in `main.tf` and then build: 15 | 16 | ``` 17 | terraform init 18 | terraform plan -out=/tmp/mc 19 | terraform apply /tmp/mc 20 | ``` 21 | 22 | Add the server IP to your Minecraft client and refresh until the server becomes available. The provisioner script performing the initial S3 world sync can take a few minutes depending on your Minecraft world size. 23 | 24 | ## Authors 25 | 26 | [Darrell Davis](https://github.com/darrelldavis) 27 | 28 | ## License 29 | MIT Licensed. See LICENSE for full details. 30 | 31 | -------------------------------------------------------------------------------- /examples/s3-exists/README.md: -------------------------------------------------------------------------------- 1 | # Example using existing S3 bucket 2 | 3 | Note: This will create resources that are not "free tier" eligible. 4 | 5 | ## Usage 6 | 7 | By default, this will use your "default" AWS credentials profile if installed. Otherwise, set your credentials: 8 | 9 | ``` 10 | export AWS_ACCESS_KEY_ID="anaccesskey" 11 | export AWS_SECRET_ACCESS_KEY="asecretkey" 12 | ``` 13 | 14 | * Edit `region` and `bucket_name` in `main.tf` and then build: 15 | 16 | ``` 17 | terraform init 18 | terraform plan -out=/tmp/mc 19 | terraform apply /tmp/mc 20 | ``` 21 | 22 | Add the server IP to your Minecraft client and refresh until the server becomes available. The provisioner script performing the initial S3 world sync can take a few minutes depending on your Minecraft world size. 23 | 24 | ## Authors 25 | 26 | [Darrell Davis](https://github.com/darrelldavis) 27 | 28 | ## License 29 | MIT Licensed. See LICENSE for full details. 30 | 31 | -------------------------------------------------------------------------------- /examples/complete/README.md: -------------------------------------------------------------------------------- 1 | # Complete Example 2 | 3 | Note: 4 | 5 | * This will create resources that are not "free tier" eligible. 6 | * If you change `mc_port` from the default value, you'll also need to manually edit the minecraft `server.properties` file on S3 or the EC2 instance 7 | 8 | 9 | ## Usage 10 | 11 | This will use your "default" AWS credentials profile if installed. Or explicitly set your credentials: 12 | 13 | ``` 14 | export AWS_ACCESS_KEY_ID="anaccesskey" 15 | export AWS_SECRET_ACCESS_KEY="asecretkey" 16 | ``` 17 | 18 | * Edit `region` and `bucket_name` in `main.tf` and then build: 19 | 20 | ``` 21 | terraform init 22 | terraform plan -out=/tmp/mc 23 | terraform apply /tmp/mc 24 | ``` 25 | 26 | Add the server IP to your Minecraft client and refresh until the server becomes available. The provisioner script performing the initial S3 world sync can take a few minutes depending on your Minecraft world size. 27 | 28 | ## Authors 29 | 30 | [Darrell Davis](https://github.com/darrelldavis) 31 | 32 | ## License 33 | MIT Licensed. See LICENSE for full details. 34 | 35 | -------------------------------------------------------------------------------- /examples/latest_snapshot/outputs.tf: -------------------------------------------------------------------------------- 1 | #output "public_ip" { 2 | # value = aws_eip.mc.public_ip 3 | #} 4 | 5 | output "vpc_id" { 6 | value = module.minecraft.vpc_id 7 | } 8 | 9 | output "subnet_id" { 10 | value = module.minecraft.subnet_id 11 | } 12 | 13 | output "public_ip" { 14 | value = module.minecraft.public_ip 15 | } 16 | 17 | output "id" { 18 | value = module.minecraft.id 19 | } 20 | 21 | output "public_key_openssh" { 22 | value = module.minecraft.public_key_openssh 23 | } 24 | 25 | output "public_key" { 26 | value = module.minecraft.public_key 27 | } 28 | 29 | output "private_key" { 30 | value = module.minecraft.private_key 31 | } 32 | 33 | output "zzz_ec2_ssh" { 34 | value = module.minecraft.zzz_ec2_ssh 35 | } 36 | 37 | #resource "local_file" "private_key" { 38 | # content = module.minecraft.private_key 39 | # filename = "${path.module}/ec2-private-key.pem" 40 | # directory_permission = "0700" 41 | # file_permission = "0700" 42 | #} 43 | 44 | output "minecraft_server" { 45 | value = module.minecraft.minecraft_server 46 | } 47 | 48 | -------------------------------------------------------------------------------- /examples/defaults/outputs.tf: -------------------------------------------------------------------------------- 1 | #output "public_ip" { 2 | # value = aws_eip.mc.public_ip 3 | #} 4 | 5 | output "vpc_id" { 6 | value = module.minecraft.vpc_id 7 | } 8 | 9 | output "subnet_id" { 10 | value = module.minecraft.subnet_id 11 | } 12 | 13 | output "public_ip" { 14 | value = module.minecraft.public_ip 15 | } 16 | 17 | output "id" { 18 | value = module.minecraft.id 19 | } 20 | 21 | output "public_key_openssh" { 22 | value = module.minecraft.public_key_openssh 23 | } 24 | 25 | output "public_key" { 26 | value = module.minecraft.public_key 27 | } 28 | 29 | output "private_key" { 30 | value = module.minecraft.private_key 31 | } 32 | 33 | output "zzz_ec2_ssh" { 34 | value = module.minecraft.zzz_ec2_ssh 35 | } 36 | 37 | resource "local_file" "private_key" { 38 | content = module.minecraft.private_key 39 | filename = "${path.module}/ec2-private-key.pem" 40 | directory_permission = "0700" 41 | file_permission = "0700" 42 | } 43 | 44 | output "minecraft_server" { 45 | value = "${module.minecraft.public_ip[0]}:${module.minecraft.mc_port}" 46 | } 47 | 48 | -------------------------------------------------------------------------------- /examples/s3-exists/outputs.tf: -------------------------------------------------------------------------------- 1 | #output "public_ip" { 2 | # value = aws_eip.mc.public_ip 3 | #} 4 | 5 | output "vpc_id" { 6 | value = module.minecraft.vpc_id 7 | } 8 | 9 | output "subnet_id" { 10 | value = module.minecraft.subnet_id 11 | } 12 | 13 | output "public_ip" { 14 | value = module.minecraft.public_ip 15 | } 16 | 17 | output "id" { 18 | value = module.minecraft.id 19 | } 20 | 21 | output "public_key_openssh" { 22 | value = module.minecraft.public_key_openssh 23 | } 24 | 25 | output "public_key" { 26 | value = module.minecraft.public_key 27 | } 28 | 29 | output "private_key" { 30 | value = module.minecraft.private_key 31 | } 32 | 33 | output "zzz_ec2_ssh" { 34 | value = module.minecraft.zzz_ec2_ssh 35 | } 36 | 37 | resource "local_file" "private_key" { 38 | content = module.minecraft.private_key 39 | filename = "${path.module}/ec2-private-key.pem" 40 | directory_permission = "0700" 41 | file_permission = "0700" 42 | } 43 | 44 | output "minecraft_server" { 45 | value = "${module.minecraft.public_ip[0]}:${module.minecraft.mc_port}" 46 | } 47 | 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Darrell Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/complete/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Darrell Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/defaults/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Darrell Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/s3-exists/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Darrell Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /examples/latest_snapshot/LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Darrell Davis 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /outputs.tf: -------------------------------------------------------------------------------- 1 | output "vpc_id" { 2 | value = local.vpc_id 3 | } 4 | 5 | output "subnet_id" { 6 | value = local.subnet_id 7 | } 8 | 9 | output "public_ip" { 10 | value = module.ec2_minecraft.public_ip 11 | } 12 | 13 | output "id" { 14 | value = module.ec2_minecraft.id 15 | } 16 | 17 | output "public_key_openssh" { 18 | value = tls_private_key.ec2_ssh.*.public_key_openssh 19 | } 20 | 21 | output "public_key" { 22 | value = tls_private_key.ec2_ssh.*.public_key_pem 23 | } 24 | 25 | output "private_key" { 26 | value = tls_private_key.ec2_ssh.*.private_key_pem 27 | } 28 | 29 | resource "local_file" "private_key" { 30 | count = length(var.key_name) > 0 ? 0 : 1 31 | 32 | content = tls_private_key.ec2_ssh[0].private_key_pem 33 | filename = "${path.module}/ec2-private-key.pem" 34 | directory_permission = "0700" 35 | file_permission = "0700" 36 | } 37 | 38 | output "zzz_ec2_ssh" { 39 | value = length(var.key_name) > 0 ? "" : </dev/null 2>&1; then 14 | # linuxbase.org 15 | OS=$(lsb_release -si) 16 | VER=$(lsb_release -sr) 17 | elif [ -f /etc/lsb-release ]; then 18 | # For some versions of Debian/Ubuntu without lsb_release command 19 | . /etc/lsb-release 20 | OS=$DISTRIB_ID 21 | VER=$DISTRIB_RELEASE 22 | elif [ -f /etc/debian_version ]; then 23 | # Older Debian/Ubuntu/etc. 24 | OS=Debian 25 | VER=$(cat /etc/debian_version) 26 | elif [ -f /etc/SuSe-release ]; then 27 | # Older SuSE/etc. 28 | ... 29 | elif [ -f /etc/redhat-release ]; then 30 | # Older Red Hat, CentOS, etc. 31 | ... 32 | else 33 | # Fall back to uname, e.g. "Linux ", also works for BSD, etc. 34 | OS=$(uname -s) 35 | VER=$(uname -r) 36 | fi 37 | 38 | ### Thanks to https://github.com/kenoir for pointing out that as of v15 (?) we have to 39 | ### use the Mojang version_manifest.json to find java download location 40 | ### See https://minecraft.gamepedia.com/Version_manifest.json 41 | download_minecraft_server() { 42 | 43 | WGET=$(which wget) 44 | 45 | # version_manifest.json lists available MC versions 46 | $WGET -O ${mc_root}/version_manifest.json https://launchermeta.mojang.com/mc/game/version_manifest.json 47 | 48 | # Find latest version number if user wants that version (the default) 49 | if [[ "${mc_version}" == "latest" ]]; then 50 | MC_VERS=$(jq -r '.["latest"]["'"${mc_type}"'"]' ${mc_root}/version_manifest.json) 51 | fi 52 | 53 | # Index version_manifest.json by the version number and extract URL for the specific version manifest 54 | VERSIONS_URL=$(jq -r '.["versions"][] | select(.id == "'"$MC_VERS"'") | .url' ${mc_root}/version_manifest.json) 55 | # From specific version manifest extract the server JAR URL 56 | SERVER_URL=$(curl -s $VERSIONS_URL | jq -r '.downloads | .server | .url') 57 | # And finally download it to our local MC dir 58 | $WGET -O ${mc_root}/$MINECRAFT_JAR $SERVER_URL 59 | 60 | } 61 | 62 | MINECRAFT_JAR="minecraft_server.jar" 63 | 64 | mc_root="./" 65 | 66 | mc_type_default="release" 67 | read -p "Minecraft type (release or snapshot) [$mc_type_default]: " mc_type 68 | mc_type=${mc_type:-$mc_type_default} 69 | 70 | mc_version_default="latest" 71 | read -p "Minecraft version [$mc_version_default]: " mc_version 72 | mc_version=${mc_version:-$mc_version_default} 73 | 74 | download_minecraft_server 75 | 76 | exit 0 77 | 78 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # terraform-aws-minecraft 2 | 3 | Terraform module to provision an AWS EC2 instance with an S3 backing store for running the [Minecraft](https://minecraft.net/en-us/) server. 4 | 5 | ## Features 6 | * Download and play any available version of minecraft (downloaded from Mojang) 7 | * Configurable syncing (frequency in minutes) of game folder on EC2 to S3 8 | * When done, keep your game in S3 and pick up where you left off 9 | 10 | 11 | ## Usage 12 | Example from latest version, using default values for everything: 13 | 14 | ``` 15 | module "minecraft" { 16 | source = "git@github.com:darrelldavis/terraform-aws-minecraft.git?ref=master" 17 | } 18 | ``` 19 | This will create all needed resources, including an S3 bucket to persist the game state. If you subsequently use `terraform destroy` the S3 bucket will not be destroyed as it will not be empty. You can choose to delete the bucket yourself with `aws s3 rb s3://bucket-name --force` or keep the game for future play. If the latter, add the bucket as `bucket_name` in your module call, for example: 20 | 21 | ``` 22 | module "minecraft" { 23 | source = "git@github.com:darrelldavis/terraform-aws-minecraft.git?ref=master" 24 | bucket_name = "games-minecraft-1234567890" 25 | } 26 | ``` 27 | This will sync the bucket contents to the EC2 instance before starting minecraft and you can pick up play where you left off! 28 | 29 | See [examples](./examples) directory for full example(s). 30 | 31 | Example using original (legacy) version: 32 | 33 | ``` 34 | module "minecraft" { 35 | source = "git@github.com:darrelldavis/terraform-aws-minecraft.git?ref=v1.0" 36 | } 37 | ``` 38 | 39 | ## Inputs 40 | 41 | |Name|Description|Default|Required| 42 | |:--|:--|:--:|:--:| 43 | |allowed_cidrs|Allow these CIDR blocks to the server - default is the Universe|0.0.0.0/0|| 44 | |ami|AMI to use for the instance, tested with Ubuntu and Amazon Linux 2 LTS|latest Ubuntu|| 45 | |associate\_public\_ip\_address|Toggle public IP|true|| 46 | |bucket_name|Bucket name for persisting minecraft world|generated name|| 47 | |environment|Environment (for tags)|prod|| 48 | |instance_type|EC2 instance type/size|t2.medium (NOTE: **NOT** free tier!)|| 49 | |java\_ms\_mem|Java initial and minimum heap size|2G|| 50 | |java\_mx\_mem|Java maximum heap size|2G|| 51 | |key_name|EC2 key name for provisioning and access|generated|| 52 | |name|Name to use for servers, tags, etc (e.g. minecraft)|mc|| 53 | |namespace|Namespace, which could be your organization name or abbreviation (for tags)|games|| 54 | |mc\_backup\_freq|How often (mins) to sync to S3|5|| 55 | |mc_port|TCP port for minecraft. If you change this from the default, you also need to manually edit the minecraft `server.properties` file in S3 and/or EC2 instannce after the build. (todo: install custom `server.properties`)|25565|| 56 | |mc_root|Where to install minecraft|`/home/minecraft`|| 57 | |mc_version|Which version of minecraft to install|latest|| 58 | |subnet_id|VPC subnet id to place the instance|chosen from default VPC|| 59 | |tags|Any extra tags to assign to objects|{}|| 60 | |vpc_id|VPC for security group|default VPC|| 61 | 62 | ## Outputs 63 | 64 | |Name|Description| 65 | |:--|:--| 66 | |ec2\_instance\_profile|EC2 instance profile| 67 | |id|EC2 instance ID| 68 | |private_key|The private key data in PEM format| 69 | |public_ip|Instance public IP| 70 | |public\_key|The public key data in PEM format| 71 | |public\_key\_openssh| The public key data in OpenSSH authorized_keys format| 72 | |subnet\_id|Subnet ID instance is connected to| 73 | |vpc_id|VPC ID for instance| 74 | |zzz\_ec2\_ssh|SSH command to connect to instance| 75 | 76 | ## Authors 77 | 78 | [Darrell Davis](https://github.com/darrelldavis) 79 | 80 | ## License 81 | MIT Licensed. See LICENSE for full details. 82 | 83 | -------------------------------------------------------------------------------- /variables.tf: -------------------------------------------------------------------------------- 1 | variable "vpc_id" { 2 | description = "VPC for security group" 3 | type = string 4 | default = "" 5 | } 6 | 7 | variable "subnet_id" { 8 | description = "VPC subnet id to place the instance" 9 | type = string 10 | default = "" 11 | } 12 | 13 | variable "key_name" { 14 | description = "EC2 key name for provisioning and access" 15 | type = string 16 | default = "" 17 | } 18 | 19 | variable "bucket_name" { 20 | description = "Bucket name for persisting minecraft world" 21 | type = string 22 | default = "" 23 | } 24 | 25 | variable "bucket_force_destroy" { 26 | description = "A boolean that indicates all objects should be deleted from the bucket so that the bucket can be destroyed without error. This will destroy your minecraft world!" 27 | type = bool 28 | default = false 29 | } 30 | 31 | variable "bucket_object_versioning" { 32 | description = "Enable object versioning (default = true). Note this may incur more cost." 33 | type = bool 34 | default = true 35 | } 36 | 37 | // For tags 38 | variable "name" { 39 | description = "Name to use for servers, tags, etc (e.g. minecraft)" 40 | type = string 41 | default = "minecraft" 42 | } 43 | 44 | variable "namespace" { 45 | description = "Namespace, which could be your organization name or abbreviation, e.g. 'eg' or 'cp'" 46 | type = string 47 | default = "games" 48 | } 49 | 50 | variable "environment" { 51 | description = "Environment, e.g. 'prod', 'staging', 'dev', 'pre-prod', 'UAT'" 52 | type = string 53 | default = "games" 54 | } 55 | 56 | variable "tags" { 57 | description = "Any extra tags to assign to objects" 58 | type = map 59 | default = {} 60 | } 61 | 62 | // Minecraft-specific defaults 63 | variable "mc_port" { 64 | description = "TCP port for minecraft" 65 | type = number 66 | default = 25565 67 | } 68 | 69 | variable "mc_root" { 70 | description = "Where to install minecraft on your instance" 71 | type = string 72 | default = "/home/minecraft" 73 | } 74 | 75 | variable "mc_version" { 76 | description = "Which version of minecraft to install" 77 | type = string 78 | default = "latest" 79 | } 80 | 81 | variable "mc_type" { 82 | description = "Type of minecraft distribution - snapshot or release" 83 | type = string 84 | default = "release" 85 | } 86 | 87 | variable "mc_backup_freq" { 88 | description = "How often (mins) to sync to S3" 89 | type = number 90 | default = 5 91 | } 92 | 93 | // You'll want to tune these next two based on the instance type 94 | variable "java_ms_mem" { 95 | description = "Java initial and minimum heap size" 96 | type = string 97 | default = "2G" 98 | } 99 | 100 | variable "java_mx_mem" { 101 | description = "Java maximum heap size" 102 | type = string 103 | default = "2G" 104 | } 105 | 106 | // Instance vars 107 | variable "associate_public_ip_address" { 108 | description = "By default, our server has a public IP" 109 | type = bool 110 | default = true 111 | } 112 | 113 | variable "ami" { 114 | description = "AMI to use for the instance - will default to latest Ubuntu" 115 | type = string 116 | default = "" 117 | } 118 | 119 | // https://aws.amazon.com/ec2/instance-types/ 120 | variable "instance_type" { 121 | description = "EC2 instance type/size - the default is not part of free tier!" 122 | type = string 123 | default = "t2.medium" 124 | } 125 | 126 | variable "allowed_cidrs" { 127 | description = "Allow these CIDR blocks to the server - default is the Universe" 128 | type = string 129 | default = "0.0.0.0/0" 130 | } 131 | 132 | -------------------------------------------------------------------------------- /user_data.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -vx 2 | # 3 | # Install, configure and start a new Minecraft server 4 | # This supports Ubuntu and Amazon Linux 2 flavors of Linux (maybe/probably others but not tested). 5 | 6 | set -e 7 | 8 | # Determine linux distro 9 | if [ -f /etc/os-release ]; then 10 | # freedesktop.org and systemd 11 | . /etc/os-release 12 | OS=$NAME 13 | VER=$VERSION_ID 14 | elif type lsb_release >/dev/null 2>&1; then 15 | # linuxbase.org 16 | OS=$(lsb_release -si) 17 | VER=$(lsb_release -sr) 18 | elif [ -f /etc/lsb-release ]; then 19 | # For some versions of Debian/Ubuntu without lsb_release command 20 | . /etc/lsb-release 21 | OS=$DISTRIB_ID 22 | VER=$DISTRIB_RELEASE 23 | elif [ -f /etc/debian_version ]; then 24 | # Older Debian/Ubuntu/etc. 25 | OS=Debian 26 | VER=$(cat /etc/debian_version) 27 | elif [ -f /etc/SuSe-release ]; then 28 | # Older SuSE/etc. 29 | ... 30 | elif [ -f /etc/redhat-release ]; then 31 | # Older Red Hat, CentOS, etc. 32 | ... 33 | else 34 | # Fall back to uname, e.g. "Linux ", also works for BSD, etc. 35 | OS=$(uname -s) 36 | VER=$(uname -r) 37 | fi 38 | 39 | # Update OS and install start script 40 | ubuntu_linux_setup() { 41 | export SSH_USER="ubuntu" 42 | export DEBIAN_FRONTEND=noninteractive 43 | /usr/bin/apt-get update 44 | /usr/bin/apt-get -yq install -o Dpkg::Options::="--force-confdef" -o Dpkg::Options::="--force-confold" default-jre wget awscli jq 45 | /bin/cat <<"__UPG__" > /etc/apt/apt.conf.d/10periodic 46 | APT::Periodic::Update-Package-Lists "1"; 47 | APT::Periodic::Download-Upgradeable-Packages "1"; 48 | APT::Periodic::AutocleanInterval "7"; 49 | APT::Periodic::Unattended-Upgrade "1"; 50 | __UPG__ 51 | 52 | # Init script for starting, stopping 53 | cat < /etc/init.d/minecraft 54 | #!/bin/bash 55 | # Short-Description: Minecraft server 56 | 57 | start() { 58 | echo "Starting minecraft server from /home/minecraft..." 59 | start-stop-daemon --start --quiet --pidfile ${mc_root}/minecraft.pid -m -b -c $SSH_USER -d ${mc_root} --exec /usr/bin/java -- -Xmx${java_mx_mem} -Xms${java_ms_mem} -jar $MINECRAFT_JAR nogui 60 | } 61 | 62 | stop() { 63 | echo "Stopping minecraft server..." 64 | start-stop-daemon --stop --pidfile ${mc_root}/minecraft.pid 65 | } 66 | 67 | case \$1 in 68 | start) 69 | start 70 | ;; 71 | stop) 72 | stop 73 | ;; 74 | restart) 75 | stop 76 | sleep 5 77 | start 78 | ;; 79 | esac 80 | exit 0 81 | INIT 82 | 83 | # Start up on reboot 84 | /bin/chmod +x /etc/init.d/minecraft 85 | /usr/sbin/update-rc.d minecraft defaults 86 | 87 | } 88 | 89 | # Update OS and install start script 90 | amazon_linux_setup() { 91 | export SSH_USER="ec2-user" 92 | /usr/bin/yum install java-1.8.0 yum-cron wget awscli jq -y 93 | /bin/sed -i -e 's/update_cmd = default/update_cmd = security/'\ 94 | -e 's/apply_updates = no/apply_updates = yes/'\ 95 | -e 's/emit_via = stdio/emit_via = email/' /etc/yum/yum-cron.conf 96 | chkconfig yum-cron on 97 | service yum-cron start 98 | /usr/bin/yum upgrade -y 99 | 100 | cat < /etc/systemd/system/minecraft.service 101 | [Unit] 102 | Description=Minecraft Server 103 | After=network.target 104 | 105 | [Service] 106 | Type=simple 107 | User=$SSH_USER 108 | WorkingDirectory=${mc_root} 109 | ExecStart=/usr/bin/java -Xmx${java_mx_mem} -Xms${java_ms_mem} -jar $MINECRAFT_JAR nogui 110 | Restart=on-abort 111 | 112 | [Install] 113 | WantedBy=multi-user.target 114 | SYSTEMD 115 | 116 | # Start on boot 117 | /usr/bin/systemctl enable minecraft 118 | 119 | } 120 | 121 | ### Thanks to https://github.com/kenoir for pointing out that as of v15 (?) we have to 122 | ### use the Mojang version_manifest.json to find java download location 123 | ### See https://minecraft.gamepedia.com/Version_manifest.json 124 | download_minecraft_server() { 125 | 126 | WGET=$(which wget) 127 | 128 | # version_manifest.json lists available MC versions 129 | $WGET -O ${mc_root}/version_manifest.json https://launchermeta.mojang.com/mc/game/version_manifest.json 130 | 131 | # Find latest version number if user wants that version (the default) 132 | if [[ "${mc_version}" == "latest" ]]; then 133 | MC_VERS=$(jq -r '.["latest"]["'"${mc_type}"'"]' ${mc_root}/version_manifest.json) 134 | fi 135 | 136 | # Index version_manifest.json by the version number and extract URL for the specific version manifest 137 | VERSIONS_URL=$(jq -r '.["versions"][] | select(.id == "'"$MC_VERS"'") | .url' ${mc_root}/version_manifest.json) 138 | # From specific version manifest extract the server JAR URL 139 | SERVER_URL=$(curl -s $VERSIONS_URL | jq -r '.downloads | .server | .url') 140 | # And finally download it to our local MC dir 141 | $WGET -O ${mc_root}/$MINECRAFT_JAR $SERVER_URL 142 | 143 | } 144 | 145 | MINECRAFT_JAR="minecraft_server.jar" 146 | case $OS in 147 | Ubuntu*) 148 | ubuntu_linux_setup 149 | ;; 150 | Amazon*) 151 | amazon_linux_setup 152 | ;; 153 | *) 154 | echo "$PROG: unsupported OS $OS" 155 | exit 1 156 | esac 157 | 158 | # Create mc dir, sync S3 to it and download mc if not already there (from S3) 159 | /bin/mkdir -p ${mc_root} 160 | /usr/bin/aws s3 sync s3://${mc_bucket} ${mc_root} 161 | 162 | # Download server if it doesn't exist on S3 already (existing from previous install) 163 | # To force a new server version, remove the server JAR from S3 bucket 164 | if [[ ! -e "${mc_root}/$MINECRAFT_JAR" ]]; then 165 | download_minecraft_server 166 | fi 167 | 168 | # Cron job to sync data to S3 every five mins 169 | /bin/cat < /etc/cron.d/minecraft 170 | SHELL=/bin/bash 171 | PATH=/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:${mc_root} 172 | */${mc_backup_freq} * * * * $SSH_USER /usr/bin/aws s3 sync ${mc_root} s3://${mc_bucket} 173 | CRON 174 | 175 | # Update minecraft EULA 176 | /bin/cat >${mc_root}/eula.txt< 0 ? var.vpc_id : data.aws_vpc.default.id 16 | subnet_id = length(var.subnet_id) > 0 ? var.subnet_id : sort(data.aws_subnet_ids.default.ids)[0] 17 | tf_tags = { 18 | Terraform = true, 19 | By = data.aws_caller_identity.aws.arn 20 | } 21 | } 22 | 23 | // Keep labels, tags consistent 24 | module "label" { 25 | source = "git::https://github.com/cloudposse/terraform-null-label.git?ref=master" 26 | 27 | namespace = var.namespace 28 | stage = var.environment 29 | name = var.name 30 | delimiter = "-" 31 | label_order = ["environment", "stage", "name", "attributes"] 32 | tags = merge(var.tags, local.tf_tags) 33 | } 34 | 35 | // Amazon Linux2 AMI - can switch this to default by editing the EC2 resource below 36 | data "aws_ami" "amazon-linux-2" { 37 | most_recent = true 38 | 39 | owners = ["amazon"] 40 | filter { 41 | name = "name" 42 | values = ["amzn2-ami-hvm*"] 43 | } 44 | } 45 | 46 | // Find latest Ubuntu AMI, use as default if no AMI specified 47 | data "aws_ami" "ubuntu" { 48 | most_recent = true 49 | 50 | filter { 51 | name = "name" 52 | values = ["ubuntu/images/hvm-ssd/ubuntu-bionic-18.04-amd64-server-*"] 53 | } 54 | 55 | filter { 56 | name = "virtualization-type" 57 | values = ["hvm"] 58 | } 59 | 60 | owners = ["099720109477"] # Canonical 61 | } 62 | 63 | // S3 bucket for persisting minecraft 64 | resource "random_string" "s3" { 65 | length = 12 66 | special = false 67 | upper = false 68 | } 69 | 70 | #data "aws_s3_bucket" "selected" { 71 | # bucket = local.bucket 72 | #} 73 | 74 | locals { 75 | using_existing_bucket = signum(length(var.bucket_name)) == 1 76 | 77 | bucket = length(var.bucket_name) > 0 ? var.bucket_name : "${module.label.id}-${random_string.s3.result}" 78 | } 79 | 80 | module "s3" { 81 | source = "terraform-aws-modules/s3-bucket/aws" 82 | 83 | create_bucket = local.using_existing_bucket ? false : true 84 | 85 | bucket = local.bucket 86 | acl = "private" 87 | 88 | force_destroy = var.bucket_force_destroy 89 | 90 | versioning = { 91 | enabled = var.bucket_object_versioning 92 | } 93 | 94 | # S3 bucket-level Public Access Block configuration 95 | block_public_acls = true 96 | block_public_policy = true 97 | ignore_public_acls = true 98 | restrict_public_buckets = true 99 | 100 | tags = module.label.tags 101 | } 102 | 103 | // IAM role for S3 access 104 | resource "aws_iam_role" "allow_s3" { 105 | name = "${module.label.id}-allow-ec2-to-s3" 106 | assume_role_policy = < 0 ? 0 : 1 196 | 197 | algorithm = "RSA" 198 | rsa_bits = 4096 199 | } 200 | 201 | resource "aws_key_pair" "ec2_ssh" { 202 | count = length(var.key_name) > 0 ? 0 : 1 203 | 204 | key_name = "${var.name}-ec2-ssh-key" 205 | public_key = tls_private_key.ec2_ssh[0].public_key_openssh 206 | } 207 | 208 | locals { 209 | _ssh_key_name = length(var.key_name) > 0 ? var.key_name : aws_key_pair.ec2_ssh[0].key_name 210 | } 211 | 212 | // EC2 instance for the server - tune instance_type to fit your performance and budget requirements 213 | module "ec2_minecraft" { 214 | source = "git::https://github.com/terraform-aws-modules/terraform-aws-ec2-instance.git?ref=master" 215 | name = "${var.name}-public" 216 | 217 | # instance 218 | key_name = local._ssh_key_name 219 | ami = var.ami != "" ? var.ami : data.aws_ami.ubuntu.image_id 220 | instance_type = var.instance_type 221 | iam_instance_profile = aws_iam_instance_profile.mc.id 222 | user_data = data.template_file.user_data.rendered 223 | 224 | # network 225 | subnet_id = local.subnet_id 226 | vpc_security_group_ids = [ module.ec2_security_group.this_security_group_id ] 227 | associate_public_ip_address = var.associate_public_ip_address 228 | 229 | tags = module.label.tags 230 | } 231 | 232 | --------------------------------------------------------------------------------