Add dedicated server bootstrap target

This commit is contained in:
2026-05-18 15:09:25 -07:00
parent 6178e77705
commit 1367f5963a
4 changed files with 208 additions and 1 deletions
+5 -1
View File
@@ -699,7 +699,11 @@ Target deliverable: A small group can join a server, spawn into one biome, gathe
## 0.1.L Basic Multiplayer ## 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. - [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. - [ ] Add server travel flow.
- [x] Define server authority over streamed terrain tiles. - [x] Define server authority over streamed terrain tiles.
- [x] Define server response when a client requests a missing tile. - [x] Define server response when a client requests a missing tile.
+37
View File
@@ -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 interactive Unreal build machine, but the server artifact should run on an
Ubuntu cloud VM or other Linux host. 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 ## Build Machine
Use Windows-Builder for the first repeatable build lane: 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 The server target is `AgrarianGameServer`, defined in
`Source/AgrarianGameServer.Target.cs`. `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 ## Build Command
From an elevated or normal Command Prompt on Windows-Builder: 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 ./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: Open firewall ports only as needed:
- `7777/udp` for Unreal gameplay traffic. - `7777/udp` for Unreal gameplay traffic.
@@ -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 <<UNIT
[Unit]
Description=Agrarian Unreal dedicated gameplay server
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=${SERVICE_USER}
Group=${SERVICE_USER}
WorkingDirectory=${INSTALL_DIR}
ExecStart=${INSTALL_DIR}/${SERVER_BINARY} ${MAP_NAME} -log -port=${GAME_PORT}
Restart=on-failure
RestartSec=10
LimitNOFILE=1048576
StandardOutput=append:/var/log/agrarian/game-server.log
StandardError=append:/var/log/agrarian/game-server.log
[Install]
WantedBy=multi-user.target
UNIT
systemctl daemon-reload
systemctl enable agrarian-game-server.service
if command -v ufw >/dev/null 2>&1; then
ufw allow "${GAME_PORT}/udp" comment "Agrarian gameplay server"
fi
cat >"${INSTALL_DIR}/DEPLOY_README.txt" <<README
Agrarian gameplay server host prepared for ${SERVER_NAME}.
Copy the Linux dedicated server package contents into:
${INSTALL_DIR}
Expected binary:
${INSTALL_DIR}/${SERVER_BINARY}
Start or restart:
sudo systemctl restart agrarian-game-server
Logs:
sudo journalctl -u agrarian-game-server -f
tail -f /var/log/agrarian/game-server.log
Network:
- Point ${SERVER_NAME} DNS A/AAAA record at this VM.
- Open udp/${GAME_PORT} to the Internet or tester VPN.
- Keep SSH limited to trusted admin IPs.
README
chown "${SERVICE_USER}:${SERVICE_USER}" "${INSTALL_DIR}/DEPLOY_README.txt"
log "Host bootstrap complete."
log "Point DNS ${SERVER_NAME} at this VM and open udp/${GAME_PORT}."
log "Copy the packaged server into ${INSTALL_DIR}, then run: systemctl restart agrarian-game-server"
+78
View File
@@ -0,0 +1,78 @@
#!/usr/bin/env python3
"""Validate dedicated server target and Ubuntu gameplay host bootstrap wiring."""
from pathlib import Path
import sys
ROOT = Path(__file__).resolve().parents[1]
def read(relative_path: str) -> 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())