Running nodes
Configuration and files
A node’s settings are in one file, agent.yml. The controller sets most of them. It writes the
file during setup. Later it can change parts of the file over the socket, such as the images when
it updates the agent or the plugin. You edit the rest, such as the memory cap, on the host.
agent.yml
The file is /srv/ankor/agent.yml. Its schema is AgentConfigSchema in
agent-config.ts.
token: 3f1c…
controller:
host: cs.example.com
tls: true
pinned_ip: 203.0.113.7
cs2_image: ghcr.io/ankorcs/ankor-cs2:0.9
agent_image: ghcr.io/ankorcs/ankor-agent:0.9
memory_mb: 1536
demo_upload:
url: https://demos.example.com/upload
header:
name: Authorization
value: Bearer 9b2e…
limit_mbit: 40
slots:
- id: s1
port: 27015
tv_port: 27020
gslt: 0123456789ABCDEF0123456789ABCDEF
- id: s2
port: 27016
tv_port: 27021
gslt: FEDCBA9876543210FEDCBA9876543210
| Key | Required | Value |
|---|---|---|
| token | yes | The node token from the controller, 32 to 128 letters and digits. Node tokens explains how one is made. Keep the file secret. |
| controller | yes | Where the agent dials the controller. |
| ├─ host | yes | The controller’s host with an optional port, without scheme or path, such as cs.example.com, 192.168.0.10:3000 or [fd00::1]. The agent may dial over IPv6, but players need IPv4. Rejected when it contains whitespace, a control character, /, \, ? or #. It is also rejected unless http:// followed by the value parses as a URL that has a host, an optional port and nothing else, such as a user name. |
| ├─ tls | yes | Whether the agent dials wss:// and https:// rather than ws:// and http://. |
| ├─ socket_path | no | The agent socket’s path on host, a / followed by URL path characters. At most 255 characters. Default /ws/agent. |
| └─ events_path | no | The path on host that event batches are posted to, a / followed by URL path characters. At most 255 characters. Default /api/node/events. |
| pinned_ip | no | The IPv4 address players connect to, in place of the one the agent looks up. Rejected unless it is four numbers from 0 to 255 joined by dots, each without leading zeros. |
| cs2_image | yes | The ankor-cs2 image future game servers run, as a Docker image reference. |
| agent_image | yes | The agent’s image, which node-setup.sh --update runs and the agent replaces itself from, as a Docker image reference. |
| memory_mb | no | MiB of memory each game container may use. Without it, game containers are not capped. CS2 installs and updates are exempt. Rejected when it is not a whole number from 1024 to 65536. A whole number from 1024 to 65536. |
| auto_update | no | Whether the agent keeps the node up to date on its own. It updates CS2 to the newest public build and refuses launches while it is behind that build. It also moves cs2_image and agent_image to the newest release or rebuild on their release line. An image tagged latest stays latest and follows every release, across lines. It reads their tags from the registry every 60 minutes, and leaves an image named by digest or by a custom tag alone. When false, only update_cs2, update_cs2_image and update_agent change the build and the images. Default true. |
| official_images_only | no | Whether the node runs only the official images: agent_image from ghcr.io/ankorcs/ankor-agent and cs2_image from ghcr.io/ankorcs/ankor-cs2, with any tag or digest. While it is on, the agent refuses an update_agent or update_cs2_image to any other image with image_not_allowed, and node-setup.sh refuses to run one. Only the node’s operator can turn it off, by setting it to false in the file, to run images of their own. set_config cannot change it. If the agent.yml that node-setup.sh fetches from the controller turns it off, the script stops. Default true. |
| slots[] | yes | The node’s game server slots. Empty for a node that runs no servers yet. Such a node connects and reports, but accepts no launch. A list. Each entry has the keys below. At most 64 entries. |
| ├─ id | yes | The slot’s id, 1 to 32 letters, digits, _ and -. It is unique on its node. |
| ├─ port | yes | The game port, UDP. Every port and tv_port on a node is different, free on the host, and not 4500, the port the agent listens on for plugins. Host networking explains why. A whole number from 1 to 65535. |
| ├─ tv_port | yes | The GOTV port, UDP, under the same rules as port. A whole number from 1 to 65535. |
| └─ gslt | yes | The slot’s Game Server Login Token, which every server in the slot logs in with. It is 10 to 64 letters and digits. Game Server Login Tokens explains them. |
| demo_upload | no | Where finished demos are uploaded. Without it, demos stay on the node. Demo uploads describes the request. |
| ├─ url | yes | The http or https URL finished demos are posted to. Rejected when it carries a user name or a password, which belong in header. At most 255 characters. |
| ├─ header | no | One header sent with every upload. |
| │ ├─ name | yes | The header’s name, up to 64 letters, digits and -. Rejected when it is, in any case, one of host, content-type, content-length, transfer-encoding, connection, keep-alive, upgrade, expect, te, trailer, ankor-protocol, ankor-file-name, ankor-match-id and ankor-allocation-id, which the agent sets itself or which change how the request is framed. |
| │ └─ value | yes | The header’s value, sent verbatim, without line breaks. At most 1024 characters. |
| └─ limit_mbit | no | Megabits a second an upload may send while a game server runs on the node. Without it, uploads run at full speed. A whole number from 1 to 10000. |
- A nested key is required only when its parent is present.
- The agent warns about keys it does not know and ignores them.
- A file that fails validation stops the agent, and its log lists each invalid key.
- A controller on a private address or
localhost, including127.0.0.1:<port>on the node’s host, usually needstls: falseundercontroller. No certificate authority issues certificates for those addresses.
Changing settings
The controller can change slots, pinned_ip, auto_update, controller, demo_upload and
memory_mb while the node is connected, with set_config.
- The agent refuses an edit that would disturb an occupied slot. It also refuses any change to the
controller address while a slot is occupied. The
AgentErrorCodein its reply names the case. Stop new launches on the node in the controller, wait for the slots to go idle, then edit again. - Demo settings and changes to idle slots apply while other slots are occupied.
- An offline node cannot receive a corrected address. Fix
controller.hostin the file and restart the agent. - A slot’s login token can be changed while the slot runs a server. The server keeps the token it started with until it ends. Revoking that token at Steam drops the server, so replace a token once its slot is idle.
Edit the other keys in the file on the host, then restart the agent.
File layout
Everything a node keeps is under /srv/ankor/ or in the Docker volume ankor_cs2_data.
In the game container
Node paths are relative to /srv/ankor/, and container paths to /home/steam/cs2-dedicated/game/.
Each column is a tree. A row names its path relative to the row it is indented under. The rest of
the install is the Docker volume ankor_cs2_data. Every server shares it read-only, and only a CS2
update writes to it.
| On the node | From | In the game container | Access |
|---|---|---|---|
overlay/<image id>/ |
CS2 image | csgo/ |
|
├─ addons/ |
CS2 image | ├─ addons/ |
Read-only |
│ └─ swiftlys2/ |
│ └─ swiftlys2/ |
||
│ ├─ plugins/Ankor/ |
CS2 image | │ ├─ plugins/Ankor/ |
Read-only |
│ └─ configs/plugins/Ankor/ |
CS2 image | │ └─ configs/plugins/Ankor/ |
Read-only |
└─ cfg/ankor/ |
CS2 image | └─ cfg/ankor/ |
Read-only |
slots/<slot id>/game/csgo/ |
Slot | csgo/ |
|
├─ addons/swiftlys2/ |
├─ addons/swiftlys2/ |
||
│ ├─ configs/plugins/Ankor/config.jsonc |
Launch | │ ├─ configs/plugins/Ankor/config.jsonc |
Read-only |
│ ├─ data/ |
Slot | │ ├─ data/ |
Writable |
│ ├─ logs/ |
Slot | │ ├─ logs/ |
Writable |
│ └─ dumps/ |
Slot | │ └─ dumps/ |
Writable |
├─ cfg/ |
├─ cfg/ |
||
│ └─ ankor_launch.cfg |
Launch | │ └─ ankor_launch.cfg |
Read-only |
└─ ankor/ |
Slot | └─ ankor/ |
Writable |
├─ match.json |
Launch | ├─ match.json |
Read-only |
├─ restore.txt |
Launch | ├─ restore.txt |
Read-only |
├─ backups/ |
Launch | ├─ backups/ |
Writable |
└─ demos/<allocation id>/ |
Launch | └─ demos/ |
Writable |
cfg/ |
Node cfgs | csgo/cfg/node/ |
Read-only |
swiftlys2/ |
Node plugins | csgo/addons/swiftlys2/ |
|
├─ plugins/<plugin>/ |
Node plugins | ├─ plugins/<plugin>/ |
Read-only |
└─ configs/plugins/<plugin>/ |
Node plugins | └─ configs/plugins/<plugin>/ |
Writable |
shared/game/bin/linuxsteamrt64/steamapps/ |
Every slot | bin/linuxsteamrt64/steamapps/ |
Writable |
A launch runs your cfgs from cfg/ by naming them in exec. The protocol guide lists
when they run, and their order among the game mode’s cfg and
Ankor’s.
SwiftlyS2 gives a plugin its paths through these functions. They map to these directories on the node:
| SwiftlyS2 | On the node |
|---|---|
Core.PluginPath |
/srv/ankor/swiftlys2/plugins/<plugin>/ |
Core.Configuration.GetConfigPath() |
/srv/ankor/swiftlys2/configs/plugins/<plugin>/ |
Core.PluginDataDirectory |
/srv/ankor/slots/<slot id>/game/csgo/addons/swiftlys2/data/<plugin>/ |
Data and logs stay across the slot’s launches.
Only on the node
No game container mounts these. Only the agent reads and writes them.
| On the node | Contents |
|---|---|
/srv/ankor/agent.yml |
The node’s configuration, including its node token. |
/srv/ankor/agent-update-failed.json |
Why the last agent update failed. The agent reports it to the controller until an agent update succeeds or the setup script starts the agent. |
/srv/ankor/events/ |
The agent’s SQLite database of gameplay events and ended servers that wait for the controller. Its layout is internal to the agent. An update upgrades it in place. |
/srv/ankor/gslt-refused.json |
The SHA-256 of each login token Steam refused, so the agent never logs one on again. Deleting it and restarting the agent makes it check every token once more. |