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, including 127.0.0.1:<port> on the node’s host, usually needs tls: false under controller. 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 AgentErrorCode in 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.host in 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.

Edit this page