Files
gbr-games/AUTHORING.md
T
gregandClaude Opus 5.5 c2b22d3955
Validate game templates / validate (push) Successful in 19s
Validate game templates / boot-test (push) Skipped
Validate game templates / publish (push) Skipped
feat(draft): catch split text settings and owner-editable game ports
Two problems a draft can boot fine with but break once an owner changes a
setting, both in the builder's Valheim draft:

- splitSettings: text settings used unquoted on the command line, directly
  or through ARGS="-name ${SERVER_NAME}"; ./server $ARGS, so "GBRHosting -
  My Server" reaches the game as several arguments. Suggests quoting or a
  bash array.
- portSettings: game-port settings whose default isn't in defaultPorts, or
  that owners can change although nodes always publish the same container
  port. Internal ports (RCON, REST) and {PUBLIC_PORT_n} values are left out.

Both run on drafts only; AUTHORING.md gets the matching rules.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-25 15:43:27 -04:00

6.4 KiB

Writing a game template

House rules for games/<id>/game.yaml and test.yaml. They apply to people and to the AI builder alike; the builder reads this file as its instructions. Most rules exist because breaking them broke a real server. gbrgames validate and gbrgames inspect-image -game <id> enforce what they can.

How a server actually runs

Know this before writing anything:

  • The node-agent creates one Docker volume per server and mounts it at both /server and /data. Game files, saves and config live there. Anything written elsewhere is lost when the container is recreated, which happens on every start.
  • The container runs as 1000:1000 unless the template sets containerUser. Never as root.
  • Every key in defaultConfig (merged with the owner's changes) reaches the container as an environment variable, named by the agent's rule: insert _ before an upper-case run that follows a lower-case letter, then upper-case everything. ServerName → SERVER_NAME, MaxPlayers → MAX_PLAYERS, RCONPort → RCONPORT (no underscore: no lower-case letter before P), bIsPvP → B_IS_PV_P.
  • If configFilePath is set, the same settings are also written to that file in configFileFormat before each start.
  • defaultStartupCommand, when set, runs as bash -c "… cd /server && <command>"; otherwise the image's own entrypoint runs.
  • installMethod: STEAMCMD installs installConfig.appId into /server with anonymous SteamCMD before the server starts.
  • Nodes reach a server's RCON, REST and query ports over the Docker network, never through published ports.

Rules

Passwords and secrets

  • Admin, RCON and REST passwords: defaultConfig value "", and the configSchema property gets "x-generate": {"type": "password"}. The panel then gives every server its own password. Never put a placeholder or default password in a template. Shared defaults are readable by anyone from the public game list.
  • Join passwords stay "", with "x-savedValue": {"key": "GAME_JOIN_PASSWORD", "label": "Game join password", "secret": true}.
  • No real credentials, tokens or IDs anywhere in this repo. A value a server can't start without (a player ID, a license token) goes in test.yaml under secrets, mapped to a CI secret name.

Ports

  • defaultPorts lists only what players connect to (game and query ports). RCON, REST and admin ports are never published.
  • Use "7777/udp" or "8211:8211/udp". Every port needs its protocol.
  • Nodes always publish these container ports (only the host side moves when servers share a node). So the game's own port is not a setting: write it into the startup command or config file. A game that must announce the port players use gets {PUBLIC_PORT_<port>}.

The container user

  • Run gbrgames inspect-image -game <id>. If the image is built around its own user (it declares USER, or locks a home folder to a uid other than 1000), set containerUser to that uid:gid. Example: ARK's image needs 25000:25000.

The startup command

  • Read settings under the names the agent gives them (see above). ${RCON_PORT} when the setting is RCONPort silently ignores the owner's value; it arrives as RCONPORT.
  • Use ${VAR:-default} for anything that may be unset.
  • Quote text settings where they're used: -name "${SERVER_NAME}". ARGS="-name ${SERVER_NAME}"; ./server $ARGS splits "GBRHosting - My Server" into words; build optional arguments in a bash array instead (args=(-name "$SERVER_NAME"); [ -n "$PASSWORD" ] && args+=(-password "$PASSWORD"); ./server "${args[@]}").
  • If the image installs the game itself on start, make sure a failed install is retried rather than starting a server that isn't there (see ARK's template).

Memory

  • requiredMemoryMb is the container's hard limit. A JVM or engine heap must stay about 1 GB below it: Minecraft's MEMORY is 3G for 4096 MB. A heap equal to the limit is killed on start.

Settings (defaultConfig and configSchema)

  • Take keys and default values from what the server really writes: run gbrgames first-boot -game <id> and read the settings in its report.
  • Every key in defaultConfig has a configSchema property, and the other way round.
  • Every property has a title, a short description for a player, a type, a default equal to the defaultConfig value, and a category: one of Security & Access, Gameplay & Rules, World & Environment, System & General. Mark the few that matter when deploying with "showOnDeploy": true.
  • An EULA or other agreement is a boolean with "x-consent": {"label": "...", "url": "..."} and default false. Never true; the owner accepts it themselves.
  • Keys may only use letters, digits, _, - and ..
  • Placeholders the panel fills in: {SERVER_NAME}, {PUBLIC_IP}, {PUBLIC_HOST}, {PUBLIC_PORT_<port>}, {saved:KEY}. Server names default to GBRHosting - {SERVER_NAME}.

Being ready

  • readyPattern: a regex that matches the line the server prints once players can join. Take it from a first boot (its report suggests one), and put that real line in test.yaml as readyExample; validation checks the pattern matches it.
  • failPatterns: lines that mean the server will never come up (e.g. an unaccepted EULA).
  • No reliable ready line: leave readyPattern out, and the boot test checks the server stays up (settleSec in test.yaml, default 120 s).

test.yaml

  • config: only what the server can't start without in CI (accepting the EULA, for example).
  • secrets: required values that can't be public, mapped to CI secret names.
  • timeoutSec covers install plus start; SteamCMD games need 2400 or more.

Commands and players (agnosticCommandConfig)

  • Kick, ban, save and broadcast go through RCON or REST using the server's own password key (passwordKey / rconPasswordKey pointing at the x-generate setting). If a game has neither, mark the action "type": "UNSUPPORTED"; never invent one.
  • playerJoinRegex / playerLeaveRegex capture the player name from real log lines.

Before opening a PR

go run ./cmd/gbrgames validate
go run ./cmd/gbrgames inspect-image -game <id>
go run ./cmd/gbrgames first-boot -game <id>     # on a machine with Docker and the gbr-boottest network

CI then boot-tests the changed game with the node-agent's real deploy code before anything reaches the panel.