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>
6.4 KiB
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
/serverand/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:1000unless the template setscontainerUser. 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 beforeP),bIsPvP→B_IS_PV_P. - If
configFilePathis set, the same settings are also written to that file inconfigFileFormatbefore each start. defaultStartupCommand, when set, runs asbash -c "… cd /server && <command>"; otherwise the image's own entrypoint runs.installMethod: STEAMCMDinstallsinstallConfig.appIdinto/serverwith 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:
defaultConfigvalue"", and theconfigSchemaproperty 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.yamlundersecrets, mapped to a CI secret name.
Ports
defaultPortslists 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 declaresUSER, or locks a home folder to a uid other than 1000), setcontainerUserto thatuid:gid. Example: ARK's image needs25000:25000.
The startup command
- Read settings under the names the agent gives them (see above).
${RCON_PORT}when the setting isRCONPortsilently ignores the owner's value; it arrives asRCONPORT. - Use
${VAR:-default}for anything that may be unset. - Quote text settings where they're used:
-name "${SERVER_NAME}".ARGS="-name ${SERVER_NAME}"; ./server $ARGSsplits "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
requiredMemoryMbis the container's hard limit. A JVM or engine heap must stay about 1 GB below it: Minecraft'sMEMORYis3Gfor4096MB. 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
defaultConfighas aconfigSchemaproperty, and the other way round. - Every property has a
title, a shortdescriptionfor a player, atype, adefaultequal to thedefaultConfigvalue, and acategory: one ofSecurity & 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 defaultfalse. Nevertrue; 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 toGBRHosting - {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 intest.yamlasreadyExample; 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
readyPatternout, and the boot test checks the server stays up (settleSecintest.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.timeoutSeccovers 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/rconPasswordKeypointing at thex-generatesetting). If a game has neither, mark the action"type": "UNSUPPORTED"; never invent one. playerJoinRegex/playerLeaveRegexcapture 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.