# Writing a game template House rules for `games//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 ` 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 && "`; 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_}`. ### The container user - Run `gbrgames inspect-image -game `. 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`, as the inspector reports it. ### 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. - Only use launch options the game's documentation lists, spelled as it spells them. Servers ignore options they don't know, so an invented one boots fine and its setting does nothing. - 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: log in to SteamCMD once (`+login anonymous +quit`) before the install, and retry the install a few times until the server's executable exists. ### Memory - `requiredMemoryMb` is the container's hard limit. A JVM or engine heap must stay about **1 GB below** it: a `3G` heap 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 ` 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_}`, `{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 ```bash go run ./cmd/gbrgames validate go run ./cmd/gbrgames inspect-image -game go run ./cmd/gbrgames first-boot -game # 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.