From 1367f5963a5be4e6889e77fc24ba11cdb1fe54ed Mon Sep 17 00:00:00 2001 From: nathan Date: Mon, 18 May 2026 15:09:25 -0700 Subject: [PATCH] Add dedicated server bootstrap target --- AGRARIAN_DEVELOPMENT_ROADMAP.md | 6 +- Docs/Ops/DedicatedServerBuildRunbook.md | 37 ++++++++ .../bootstrap_ubuntu_game_server.sh | 88 +++++++++++++++++++ Scripts/verify_dedicated_server_target.py | 78 ++++++++++++++++ 4 files changed, 208 insertions(+), 1 deletion(-) create mode 100755 Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh create mode 100644 Scripts/verify_dedicated_server_target.py diff --git a/AGRARIAN_DEVELOPMENT_ROADMAP.md b/AGRARIAN_DEVELOPMENT_ROADMAP.md index 3db4ff2..e363662 100644 --- a/AGRARIAN_DEVELOPMENT_ROADMAP.md +++ b/AGRARIAN_DEVELOPMENT_ROADMAP.md @@ -699,7 +699,11 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe ## 0.1.L Basic Multiplayer - [x] Confirm listen server vs dedicated server for MVP. Decision: listen server is acceptable for quick internal testing, but dedicated server is the preferred closed-test target. -- [ ] Create dedicated server build target if needed. +- [x] Create dedicated server build target if needed. Confirmed the + `AgrarianGameServer` Linux server target and Windows cross-compile package + script already exist, then added a reusable Ubuntu gameplay-server bootstrap + script and standardized the MVP endpoint as `play.agrariangame.com` on + `7777/udp`. - [ ] Add server travel flow. - [x] Define server authority over streamed terrain tiles. - [x] Define server response when a client requests a missing tile. diff --git a/Docs/Ops/DedicatedServerBuildRunbook.md b/Docs/Ops/DedicatedServerBuildRunbook.md index cd6aa81..57c9114 100644 --- a/Docs/Ops/DedicatedServerBuildRunbook.md +++ b/Docs/Ops/DedicatedServerBuildRunbook.md @@ -4,6 +4,19 @@ Agrarian's multiplayer server target is Linux. Windows-Builder remains the interactive Unreal build machine, but the server artifact should run on an Ubuntu cloud VM or other Linux host. +## Public Endpoint Convention + +Use this MVP gameplay endpoint unless a later deployment requires a different +host name: + +- DNS name: `play.agrariangame.com` +- Unreal gameplay traffic: `7777/udp` +- SSH: admin IPs only + +When the first Ubuntu gameplay VM is created, point the `play` A/AAAA record at +that VM. Do not point this name at the tile server; tile delivery and gameplay +should remain separate until a deliberate combined-host decision is made. + ## Build Machine Use Windows-Builder for the first repeatable build lane: @@ -16,6 +29,23 @@ Use Windows-Builder for the first repeatable build lane: The server target is `AgrarianGameServer`, defined in `Source/AgrarianGameServer.Target.cs`. +## Ubuntu Host Bootstrap + +Prepare a new Ubuntu gameplay host with: + +```bash +sudo Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh play.agrariangame.com +``` + +The script installs baseline packages, creates an `agrarian` service user, +creates `/opt/agrarian/server`, writes a systemd unit named +`agrarian-game-server`, and opens `7777/udp` with `ufw` when available. + +For DigitalOcean or another cloud provider later, create an Ubuntu LTS droplet +or VM, copy this repository or just the script onto the host, run the bootstrap +script, then copy the `Builds/LinuxServerDevelopment` package contents into +`/opt/agrarian/server`. + ## Build Command From an elevated or normal Command Prompt on Windows-Builder: @@ -48,6 +78,13 @@ First cloud/server launch target: ./AgrarianGameServer L_GroundZeroTerrain_Test?listen -log -port=7777 ``` +Systemd launch target created by the bootstrap script: + +```bash +sudo systemctl restart agrarian-game-server +sudo journalctl -u agrarian-game-server -f +``` + Open firewall ports only as needed: - `7777/udp` for Unreal gameplay traffic. diff --git a/Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh b/Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh new file mode 100755 index 0000000..5a9539a --- /dev/null +++ b/Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh @@ -0,0 +1,88 @@ +#!/usr/bin/env bash +set -euo pipefail + +SERVER_NAME="${1:-play.agrariangame.com}" +GAME_PORT="${AGRARIAN_GAME_PORT:-7777}" +INSTALL_DIR="${AGRARIAN_INSTALL_DIR:-/opt/agrarian/server}" +SERVICE_USER="${AGRARIAN_SERVICE_USER:-agrarian}" +MAP_NAME="${AGRARIAN_MAP_NAME:-L_GroundZeroTerrain_Test?listen}" +SERVER_BINARY="${AGRARIAN_SERVER_BINARY:-AgrarianGameServer}" + +log() { + printf '[agrarian-game-server] %s\n' "$*" +} + +if [[ "${EUID}" -ne 0 ]]; then + printf 'Run as root or with sudo.\n' >&2 + exit 1 +fi + +log "Preparing Ubuntu host for ${SERVER_NAME} on udp/${GAME_PORT}." + +export DEBIAN_FRONTEND=noninteractive +apt-get update +apt-get install -y ca-certificates curl rsync tar unzip ufw + +if ! id "${SERVICE_USER}" >/dev/null 2>&1; then + useradd --system --home-dir "${INSTALL_DIR}" --create-home --shell /usr/sbin/nologin "${SERVICE_USER}" +fi + +install -d -o "${SERVICE_USER}" -g "${SERVICE_USER}" "${INSTALL_DIR}" +install -d -o "${SERVICE_USER}" -g "${SERVICE_USER}" /var/log/agrarian + +cat >/etc/systemd/system/agrarian-game-server.service </dev/null 2>&1; then + ufw allow "${GAME_PORT}/udp" comment "Agrarian gameplay server" +fi + +cat >"${INSTALL_DIR}/DEPLOY_README.txt" < str: + path = ROOT / relative_path + if not path.exists(): + raise AssertionError(f"Missing required file: {relative_path}") + return path.read_text(encoding="utf-8") + + +def require(content: str, needle: str, context: str) -> None: + if needle not in content: + raise AssertionError(f"Missing {needle!r} in {context}") + + +def main() -> int: + errors: list[str] = [] + checks = { + "Source/AgrarianGameServer.Target.cs": [ + "public class AgrarianGameServerTarget", + "Type = TargetType.Server", + 'ExtraModuleNames.Add("AgrarianGame")', + ], + "Scripts/BuildLinuxDedicatedServer-Windows.bat": [ + "AgrarianGameServer Linux Development", + "Builds\\LinuxServerDevelopment", + "-serverplatform=Linux", + "-serverconfig=Development", + "/Game/Agrarian/Maps/L_GroundZeroTerrain_Test", + ], + "Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh": [ + "play.agrariangame.com", + "AGRARIAN_GAME_PORT:-7777", + "/opt/agrarian/server", + "agrarian-game-server.service", + "ufw allow", + "systemctl restart agrarian-game-server", + ], + "Docs/Ops/DedicatedServerBuildRunbook.md": [ + "play.agrariangame.com", + "7777/udp", + "Operations/cloud-game-server/bootstrap_ubuntu_game_server.sh", + "DigitalOcean", + "agrarian-game-server", + ], + "AGRARIAN_DEVELOPMENT_ROADMAP.md": [ + "[x] Create dedicated server build target if needed.", + "AgrarianGameServer", + "play.agrariangame.com", + "7777/udp", + ], + } + + for relative_path, needles in checks.items(): + try: + content = read(relative_path) + for needle in needles: + require(content, needle, relative_path) + except AssertionError as exc: + errors.append(str(exc)) + + if errors: + for error in errors: + print(f"ERROR: {error}", file=sys.stderr) + return 1 + + print("PASS: dedicated server target and Ubuntu gameplay bootstrap are wired.") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())