From a4fd601ff8a707886bfa6f0243c63cc72025f9fb Mon Sep 17 00:00:00 2001 From: cwwalb Date: Mon, 15 Dec 2025 10:21:13 -0600 Subject: [PATCH] docs: Unreal Horde source build instructions --- modules/unreal/horde/README.md | 142 +++++++++++++++++++++++++++++- modules/unreal/horde/ecs.tf | 25 ++++-- modules/unreal/horde/variables.tf | 26 ++++++ 3 files changed, 183 insertions(+), 10 deletions(-) diff --git a/modules/unreal/horde/README.md b/modules/unreal/horde/README.md index 2a223aaa..0ba54358 100644 --- a/modules/unreal/horde/README.md +++ b/modules/unreal/horde/README.md @@ -19,6 +19,143 @@ For example configurations, please see the [examples](https://github.com/aws-gam +# Horde Server Source Build Deployment Guide + +This guide documents how to build Horde Server from source and use the build as part of your Horde deployment with the Cloud Game Development Toolkit. + +## Prerequisites + +- **Docker**: Docker Desktop installed and running +- **Git**: For cloning repositories +- **.NET SDK**: .NET 8.0 or later + +## Fork and clone the Unreal Engine Github repository + +You can follow the steps documented in the [Unreal Engine Source Code](https://github.com/EpicGames/UnrealEngine/tree/release). Be sure to run both Setup and GenerateProjectFiles scripts before proceeding. + +## Authenticate to your ECR registry +In a terminal window, run the following command to authenticate to your ECR registry: +```bash + aws ecr get-login-password --region | docker login --username AWS --password-stdin .dkr.ecr..amazonaws.com +``` + +## Create a repsoitory in Amazon ECR for storing your Horde Server container image: + +In a terminal window, run the following command: +```bash +aws ecr create-repository --repository-name horde-server --image-scanning-configuration scanOnPush=true +``` + +## Build Horde Server container image from source and publish to Amazon ECR + +### For devices using ARM64 architecture + +1. In an IDE, open the Horde Server Dockerfile located at `/Engine/Source/Programs/Horde/HordeServer/Dockerfile`. +2. Replace the code between `COPY --from=redis /usr/local/bin/redis-server /usr/local/bin/redis-server` and `COPY Source/Programs/Shared/EpicGames.Core/*.csproj ./Source/Programs/Shared/EpicGames.Core/` with the following: + + ```dockerfile + # Since the .deb does not install in this image, just download it and extract the static binary + RUN ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "arm64" ]; then \ + wget https://repo.mongodb.org/apt/ubuntu/dists/jammy/mongodb-org/7.0/multiverse/binary-arm64/mongodb-org-server_7.0.4_arm64.deb && \ + dpkg -x mongodb-org-server_7.0.4_arm64.deb /tmp/mongodb; \ + elif [ "$ARCH" = "i386" ]; then \ + wget https://repo.mongodb.org/apt/debian/dists/bookworm/mongodb-org/7.0/main/binary-i386/mongodb-org-server_7.0.4_i386.deb && \ + dpkg -x mongodb-org-server_7.0.4_i386.deb /tmp/mongodb; \ + else \ + wget https://repo.mongodb.org/apt/debian/dists/bookworm/mongodb-org/7.0/main/binary-amd64/mongodb-org-server_7.0.4_amd64.deb && \ + dpkg -x mongodb-org-server_7.0.4_amd64.deb /tmp/mongodb; \ + fi && \ + cp /tmp/mongodb/usr/bin/mongod /usr/local/bin/mongod + ``` +3. Locate the code block with the comment `# Remove native libs not used on Linux x86_64`. We need to modify this so that the build has the libraries associated with the operating system and CPU architecture we are using. To do this, replace that block with the following: +```dockerfile +# Remove native libs not used for current architecture +RUN ARCH=$(dpkg --print-architecture) && \ + if [ "$ARCH" = "amd64" ]; then \ + rm -rf /app/out/runtimes/osx* && \ + rm -rf /app/out/runtimes/win-x86 && \ + rm -rf /app/out/runtimes/win-arm* && \ + rm -rf /app/out/runtimes/linux-arm* && \ + rm -rf /app/out/runtimes/linux-x86 && \ + rm -rf /app/out/runtimes/linux/native/libgrpc_csharp_ext.x86.so; \ + elif [ "$ARCH" = "arm64" ]; then \ + rm -rf /app/out/runtimes/osx-x64 && \ + rm -rf /app/out/runtimes/win-x64 && \ + rm -rf /app/out/runtimes/win-x86 && \ + rm -rf /app/out/runtimes/linux-x64* && \ + rm -rf /app/out/runtimes/linux-x86 && \ + rm -rf /app/out/runtimes/linux/native/libgrpc_csharp_ext.x64.so && \ + rm -rf /app/out/runtimes/linux/native/libgrpc_csharp_ext.x86.so; \ + elif [ "$ARCH" = "i386" ]; then \ + rm -rf /app/out/runtimes/osx* && \ + rm -rf /app/out/runtimes/win-x64 && \ + rm -rf /app/out/runtimes/win-arm* && \ + rm -rf /app/out/runtimes/linux-x64* && \ + rm -rf /app/out/runtimes/linux-arm* && \ + rm -rf /app/out/runtimes/linux/native/libgrpc_csharp_ext.x64.so; \ + fi +``` +## Update server.json (Optional) + + +## Update the Horde BuildGraph + +1. In an IDE, open the file `Engine/Source/Programs/Horde/BuildHorde.xml `. +2. Create a new build target to build the Hord Server with the Dashboard and push the image to your Amazon ECR repository. Be sure to replace the values for `` and `` with your own values. + ```xml + + + + + + + + ``` + +4. In your terminal window, and navigate to `Engine/Build/BatchFiles`. + ```bash + cd Engine/Build/BatchFiles + ``` + +6. Run the following command to build the Horde Server and Dashboard, then publish your docker image to Amazon ECR. + ### Windows + ```powershell + RunUAT.bat BuildGraph -Script=Engine/Source/Programs/Horde/BuildHorde.xml -Target="Build and Publish HordeServer to ECR" + ``` + + ### Linux and Mac + ```bash + ./RunUAT.sh BuildGraph -Script=Engine/Source/Programs/Horde/BuildHorde.xml -Target="Build and Publish HordeServer to ECR" + ``` + +7. Note the URI of the image that was published to Amazon ECR. It should be in the format of `.dkr.ecr..amazonaws.com/horde/horde-server:` + +8. You can also locate the URI of the image in the AWS Console under Amazon ECR, or using the AWS CLI command: + ```bash + aws ecr describe-images --repository-name horde-server + ``` + +## Update your Cloud Game Development Toolkit configuration + +In order to use your newly created container image as your Horde Server image, you will need to specify the URI of the image as the value for the `image` variable. For this example we will be using the example deployment located at `modules/unreal/horde/examples/complete` +1. In your IDE, open the main.tf located at `modules/unreal/horde/examples/complete/main.tf`. +2. Under the module declaration `"unreal_engine_horde"` add the following line with the value of the image URI you noted earlier. Be sure to also set the value of `is_source_build` to `true` and set the value of `horde_server_architecture` to `ARM64` or `X_86` by passing the value with the `var.horde_server_architecture` variable as shown below: + ```python + module "unreal_engine_horde" { + ... + image = "" + is_source_build = true + horde_server_architecture = "" + ... + } + ``` +3. In a new terminal window, navigate to the directory `modules/unreal/horde/examples/complete` and run the following commands: + ```bash + terraform init + terraform apply + ``` + ## Requirements @@ -189,18 +326,21 @@ No modules. | [environment](#input\_environment) | The current environment (e.g. Development, Staging, Production, etc.). This will tag ressources and set ASPNETCORE\_ENVIRONMENT variable. | `string` | `"Development"` | no | | [existing\_security\_groups](#input\_existing\_security\_groups) | A list of existing security group IDs to attach to the Unreal Horde load balancer. | `list(string)` | `[]` | no | | [github\_credentials\_secret\_arn](#input\_github\_credentials\_secret\_arn) | A secret containing the Github username and password with permissions to the EpicGames organization. | `string` | `null` | no | +| [horde\_server\_architecture](#input\_horde\_server\_architecture) | The CPU architecture for Horde server container. Valid values: x86 or arm64 | `string` | `"X86_64"` | no | | [image](#input\_image) | The Horde Server image to use in the ECS service. | `string` | `"ghcr.io/epicgames/horde-server:latest-bundled"` | no | +| [is\_source\_build](#input\_is\_source\_build) | Set this flag to true if you are using a custom built Horde Server image from source. | `bool` | `false` | no | | [name](#input\_name) | The name attached to Unreal Engine Horde module resources. | `string` | `"unreal-horde"` | no | | [oidc\_audience](#input\_oidc\_audience) | The audience used for validating externally issued tokens. | `string` | `null` | no | | [oidc\_authority](#input\_oidc\_authority) | The authority for the OIDC authentication provider used. | `string` | `null` | no | | [oidc\_client\_id](#input\_oidc\_client\_id) | The client ID used for authenticating with the OIDC provider. | `string` | `null` | no | | [oidc\_client\_secret](#input\_oidc\_client\_secret) | The client secret used for authenticating with the OIDC provider. | `string` | `null` | no | | [oidc\_signin\_redirect](#input\_oidc\_signin\_redirect) | The sign-in redirect URL for the OIDC provider. | `string` | `null` | no | +| [operating\_system](#input\_operating\_system) | The operating system for the Horde server container. Valid values: linux or windows | `string` | `"WINDOWS_SERVER_2019_CORE"` | no | | [p4\_port](#input\_p4\_port) | The Perforce server to connect to. | `string` | `null` | no | | [p4\_super\_user\_password\_secret\_arn](#input\_p4\_super\_user\_password\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user password. | `string` | `null` | no | | [p4\_super\_user\_username\_secret\_arn](#input\_p4\_super\_user\_username\_secret\_arn) | Optionally provide the ARN of an AWS Secret for the p4d super user username. | `string` | `null` | no | | [project\_prefix](#input\_project\_prefix) | The project prefix for this workload. This is appeneded to the beginning of most resource names. | `string` | `"cgd"` | no | -| [tags](#input\_tags) | Tags to apply to resources. | `map(any)` |
{
"iac-management": "CGD-Toolkit",
"iac-module": "unreal-horde",
"iac-provider": "Terraform"
}
| no | +| [tags](#input\_tags) | Tags to apply to resources. | `map(any)` |
{
"iac-management": "CGD-Toolkit",
"iac-module": "unreal-horde",
"iac-provider": "Terraform"
}
| no | | [unreal\_horde\_alb\_access\_logs\_bucket](#input\_unreal\_horde\_alb\_access\_logs\_bucket) | ID of the S3 bucket for Unreal Horde ALB access log storage. If access logging is enabled and this is null the module creates a bucket. | `string` | `null` | no | | [unreal\_horde\_alb\_access\_logs\_prefix](#input\_unreal\_horde\_alb\_access\_logs\_prefix) | Log prefix for Unreal Horde ALB access logs. If null the project prefix and module name are used. | `string` | `null` | no | | [unreal\_horde\_cloudwatch\_log\_retention\_in\_days](#input\_unreal\_horde\_cloudwatch\_log\_retention\_in\_days) | The log retention in days of the cloudwatch log group for Unreal Horde. | `string` | `365` | no | diff --git a/modules/unreal/horde/ecs.tf b/modules/unreal/horde/ecs.tf index 987b3a98..c64c0e06 100644 --- a/modules/unreal/horde/ecs.tf +++ b/modules/unreal/horde/ecs.tf @@ -31,20 +31,27 @@ resource "aws_ecs_task_definition" "unreal_horde_task_definition" { cpu = var.container_cpu memory = var.container_memory + runtime_platform { + operating_system_family = var.operating_system + cpu_architecture = var.horde_server_architecture + } + volume { name = "unreal-horde-config" } container_definitions = jsonencode(concat([ - { - name = var.container_name - image = var.image - repositoryCredentials = var.github_credentials_secret_arn != null ? { - "credentialsParameter" : var.github_credentials_secret_arn - } : null + merge({ + name = var.container_name + image = var.image cpu = var.container_cpu memory = var.container_memory essential = true + }, var.github_credentials_secret_arn != null && !var.is_source_build ? { + repositoryCredentials = { + "credentialsParameter" : var.github_credentials_secret_arn + } + } : {}, { portMappings = [ { containerPort = var.container_api_port @@ -111,10 +118,10 @@ resource "aws_ecs_task_definition" "unreal_horde_task_definition" { condition = "SUCCESS" }] : [] ) - }, + }), { name = "unreal-horde-docdb-cert", - image = "public.ecr.aws/docker/library/bash:5.3", + image = "public.ecr.aws/docker/library/bash:latest", essential = false command = ["wget", "https://truststore.pki.rds.amazonaws.com/global/global-bundle.pem", "-P", "/app/config/"] readonly_root_filesystem = false @@ -135,7 +142,7 @@ resource "aws_ecs_task_definition" "unreal_horde_task_definition" { }], local.need_p4_trust ? [{ name = "unreal-horde-p4-trust", - image = "ubuntu:noble" + image = "public.ecr.aws/ubuntu/ubuntu:noble" essential = false command = ["bash", "-exc", <<-EOF apt-get update diff --git a/modules/unreal/horde/variables.tf b/modules/unreal/horde/variables.tf index 727dc862..35f50b47 100644 --- a/modules/unreal/horde/variables.tf +++ b/modules/unreal/horde/variables.tf @@ -60,6 +60,32 @@ variable "image" { default = "ghcr.io/epicgames/horde-server:latest-bundled" } +variable "is_source_build" { + type = bool + description = "Set this flag to true if you are using a custom built Horde Server image from source." + default = false +} + +variable "horde_server_architecture" { + type = string + description = "The CPU architecture for Horde server container. Valid values: x86 or arm64" + default = "X86_64" + validation { + condition = contains(["X86_64", "ARM64"], var.horde_server_architecture) + error_message = "horde_server_architecture must be either 'X_86' or 'ARM64'" + } +} + +variable "operating_system" { + type = string + description = "The operating system for the Horde server container. Valid values: linux or windows" + default = "WINDOWS_SERVER_2019_CORE" + validation { + condition = contains(["LINUX", "WINDOWS_SERVER_2025_FULL", "WINDOWS_SERVER_2025_CORE", "WINDOWS_SERVER_2022_FULL", "WINDOWS_SERVER_2022_CORE", "WINDOWS_SERVER_2019_FULL", "WINDOWS_SERVER_2019_CORE"], var.operating_system) + error_message = "Operating_system must be either LINUX, WINDOWS_SERVER_2025_FULL, WINDOWS_SERVER_2025_CORE, WINDOWS_SERVER_2022_FULL, WINDOWS_SERVER_2022_CORE, WINDOWS_SERVER_2019_FULL, and WINDOWS_SERVER_2019_CORE." + } +} + variable "cluster_name" { type = string description = "The name of the cluster to deploy the Unreal Horde into. Defaults to null and a cluster will be created."