NGCP Game server control panel
Documentation
Contents

Reference

Writing game definitions

A game definition is a YAML file (game.yaml) that describes one game completely: platforms, install and update steps, ports, variables, processes, config forms, RCON, status query, file manager and backup rules, and hooks. NGCP renders it into concrete command lines, files and firewall rules for each service.

The format is checked strictly: unknown keys and wrong types are errors with line numbers, and every problem is reported at once. The full JSON Schema is published as docs/gamedef.schema.json in the source repository (ngcp-controller gamedef-schema prints it). Point your editor’s YAML plugin at it for completion and checks:

# yaml-language-server: $schema=./gamedef.schema.json

The shipped definitions (Shipped games) are good examples; ArmA 3 uses nearly every feature.

Uploading a definition

  1. Open Games and choose Write a new definition, or open an existing game.
  2. Paste or edit the YAML. Check for problems validates it without saving.
  3. Save. Each save needs a higher version than the last one (“The version must go up”).

Rules:

  • Shipped definitions are built-in. Saving one in the panel turns it into a custom definition, which upgrades never overwrite.
  • Every saved version is kept; Compare with an older version shows a diff.
  • Offered for new services hides or shows a game in the create dialog without touching existing services.
  • Built-in definitions cannot be deleted; custom ones only when no service uses them.
  • Changes reach services the next time their spec is sent (at once for connected nodes). Install steps only run on install, reinstall or update.
  • Limits: 256 KiB of YAML, 64 KiB per script. Managing definitions needs games:write.

Through the API, POST /api/v1/game-definitions takes {"source_yaml": "...", "scripts": {"scripts/setup.sh": "..."}}; POST /api/v1/game-definitions/validate checks without saving.

A minimal definition

id: mygame
version: 1
name: My Game
category: fps
platforms: [windows, linux]
slots: {min: 1, max: 32, default: 16}

install:
  windows:
    - {type: steamcmd, name: My Game server, app_id: 123456, login: anonymous, validate: true}
  linux:
    - {type: steamcmd, name: My Game server, app_id: 123456, login: anonymous, validate: true, executable: [server.x86_64]}

ports:
  - {role: game, proto: udp, default: 27015, offset: 0}
  - {role: query, proto: udp, default: 27016, offset: 1}

variables:
  - {key: server_name, label: Server name, type: string, default: "My server", user_editable: true}

processes:
  - role: main
    windows: {exe: server.exe}
    linux: {exe: server.x86_64}
    args: '-port {port.game} -queryport {port.query} -maxplayers {slots} "-name={var.server_name}"'
    stop: {stdin: quit, timeout_sec: 30}
    ready_regex: "Server started"

query: {type: a2s, port: "{port.query}"}

Placeholders

Templates (command-line arguments, file contents, paths, URLs, config binds) use placeholders in curly braces:

Placeholder Value Where
{var.<key>} A variable’s value Everywhere
{port.<role>} The port allocated for that role Everywhere
{slots} The service’s player slots Everywhere
{install_dir} The service’s install directory (absolute) Everywhere
{ip} The IP the service binds (the node’s primary IP, or 0.0.0.0) Everywhere
{os} windows or linux Everywhere
{index} The process’s number within its role (headless clients 1..N) Processes only
{agent_exe} The agent’s own executable (for built-in helpers) Processes only

Every placeholder must exist: a {var.x} without a variable x or a {port.y} without a port role y is an error. {cfg.*} placeholders are not supported; bind a config setting to a variable instead.

Command lines are split into arguments before placeholders are filled in, like a shell would split them. A placeholder’s value is therefore always exactly one argument, whatever it contains. Quote an argument that has a fixed prefix so it stays together: "-mod={var.mods}" is one argument even when empty.

Scripts and hooks also get the values as environment variables: NGCP_VAR_<KEY>, NGCP_PORT_<ROLE>, NGCP_INSTALL_DIR, NGCP_SLOTS, NGCP_IP, NGCP_OS, NGCP_SERVICE_ID.

Top-level keys

id, version, name, category, icon

id: arma3            # lower case, digits and _, starting with a letter or digit; never change it
version: 3           # raise on every change
name: ArmA 3
category: fps        # free text, for grouping
icon: ""             # optional

platforms

[windows], [linux] or [windows, linux]. Nodes of other OSes are not offered.

slots

slots: {min: 2, max: 256, default: 64}
# or the shorthand: default_slots: 64

ports

ports:
  - {role: game, proto: udp, default: 2302, offset: 0}
  - {role: query, proto: udp, default: 2303, offset: 1}
  - {role: rcon, proto: tcp, default: 2307, offset: 5}
Key Meaning
role Name used in {port.<role>} (lower case).
proto tcp, udp or both.
offset Distance from the service’s base port (0-100). Ports are allocated as one block.
default The game’s usual port. Its block is used when it fits inside the node’s port range and is free.

Reserve offsets the game uses implicitly (ArmA’s +3 voice port) as roles, so no other service gets them. The firewall opens all of a service’s ports.

variables

variables:
  - {key: server_name, label: Server name, type: string, default: "My server", user_editable: true}
  - {key: admin_password, label: Admin password, type: password, generate: true, user_editable: true}
  - {key: difficulty, label: Difficulty, type: select, options: [easy, normal, hard], default: normal, user_editable: true}
  - {key: headless_clients, label: Headless clients, type: int, default: 0, min: 0, max: 8, user_editable: true}
  - {key: accept_eula, label: I accept the EULA, type: bool, default: false, user_editable: true}
  - {key: mods, label: Mods, type: modlist, default: "", user_editable: true}
Key Meaning
key Name used in {var.<key>} (lower case).
type string, int (with min / max), bool, password, select (with options), modlist.
default Default value.
user_editable Service owners may change it. Otherwise only administrators.
generate Create a random value when the service is created (for passwords).
admin_only Only SuperAdmins and Admins may set it; a package made by an administrator can preset it.
required A game server cannot be saved without a value (0 counts as none).
rel_path The value must be a path inside the install directory.
command_line Used as a whole word of args ("{var.arguments}"), the value is split into arguments and may use the same placeholders.
label, description Shown in the panel.

password values are sealed per service and never returned by the API. A modlist holds Workshop item ids; in templates it renders as the mod folders joined with ; (mods/@cba_a3;mods/@ace). Values are limited in length and cannot contain control characters.

processes

processes:
  - role: main
    windows: {exe: arma3server_x64.exe}
    linux: {exe: arma3server_x64}
    args: >-
      -port={port.game} -config=server.cfg "-mod={var.mods}"
    env: {SteamAppId: "892970"}
    console: {mode: pipe, log_files: ["profiles/*.rpt"], tail_log_files: true}
    stop: {rcon: "#shutdown", timeout_sec: 15}
    ready_regex: "Connected to Steam servers"
  - role: headless
    count: "{var.headless_clients}"
    windows: {exe: arma3server_x64.exe}
    linux: {exe: arma3server_x64}
    args: -client -connect=127.0.0.1 -port={port.game} -profiles=hc{index} -name=hc{index}
    start_delay_sec: 15
    restart_with_main: true
Key Meaning
role main (exactly one, runs once), headless or sidecar.
count Template for how many to run (not for main); 0 runs none.
windows.exe, linux.exe Executable relative to the install directory, per OS.
args Command-line template (split into arguments first, see above).
env Extra environment variables (templates allowed).
console.mode pipe: the process’s output is the console.
console.log_files Globs of log files to show in the console too (relative paths).
console.tail_log_files Follow those files as they grow.
console.clear_logs_on_start Clear them at each start.
stop.stdin Command written to the console on stop (for example quit, stop).
stop.rcon RCON command sent on stop (needs an rcon section), for example #shutdown.
stop.timeout_sec How long to wait before terminating (0-600; default 10).
ready_regex A console line that means “running”; until it appears the service is starting. If it never appears, the service is assumed to be up after a while.
start_delay_sec Wait this long after the main process before starting (0-600).
restart_with_main Restart this process whenever the main one restarts.

install and update

Steps per OS, run in order. update has the same shape; without update, an update re-runs the install’s SteamCMD steps without validation.

install:
  windows:
    - {type: steamcmd, name: Server files, app_id: 233780, login: account, validate: true}
    - {type: http, name: Java runtime, resolver: adoptium, version: "21", dest: jre, extract: auto, strip_components: 1}
    - {type: http, name: Server jar, url: "https://example.com/server-1.2.jar", sha256: "<64 hex>", dest: server.jar, extract: none}
    - {type: git, name: Scripts, repo: "https://github.com/example/server-scripts", ref: main, dest: scripts}
    - {type: script, name: Prepare, run: "scripts/prepare.{ps1|sh}"}
    - {type: file, name: Default config, dest: server.cfg, content: "hostname = \"{var.server_name}\";\n"}
  linux:
    - {type: steamcmd, name: Server files, app_id: 233780, login: account, validate: true, executable: [server_x64]}

Common keys on every step: type, name (shown in progress), when (a condition, below) and executable (Linux: files to mark executable afterwards).

Type Keys
steamcmd app_id, login (anonymous or account = the vault account), validate, beta, beta_password, force_platform (windows / linux: download another platform’s depot)
http url (HTTPS only) with sha256, or resolver (papermc, mojang, adoptium) with version (latest = newest); dest; extract (auto, none, zip, tar.gz); strip_components (0-5)
git repo (HTTPS URL), ref, dest. The node needs git.
script run: a path under scripts/; x.{ps1|sh} picks .ps1 on Windows and .sh on Linux. env: extra variables.
file dest, content (a template), overwrite (default false: an existing file is kept, so user edits survive).

Downloads are verified before anything is unpacked; archives cannot write outside the install directory. git and scripts run as the service account.

Conditions: when: "{var.flavor} == paper" (or !=) skips a step unless it holds. The Minecraft definition uses this to offer Paper and Vanilla in one definition.

A steamcmd step (and the workshop section) can take its id and login from variables instead: app_id_var names an int variable and login_var a select variable with the options anonymous and account. The shipped steam_server definition works this way.

preserve_on_reinstall

Globs (relative, ** allowed) that a reinstall keeps:

preserve_on_reinstall: [server.cfg, "profiles/**", "mpmissions/**"]

workshop

workshop:
  app_id: 107410          # the game's Workshop app id (not the server's)
  login: account          # anonymous or account
  install_to: mods        # folder for the mods
  name_strategy: "@{title_slug}"   # folder name: {title_slug} and/or {id}
  keys_dir: keys          # copy *.bikey files here
  lowercase_on_linux: true

config_files

Forms for the Configs tab:

config_files:
  - path: server.cfg
    label: Server
    format: arma_cfg
    settings:
      - {key: hostname, label: Server name, type: string, bind: "{var.server_name}"}
      - {key: maxPlayers, label: Max players, type: int, bind: "{slots}", min: 2, max: 256}
      - {key: BattlEye, label: BattlEye anti-cheat, type: bool, default: true, user_editable: false}
      - {key: vonCodecQuality, label: Voice quality, type: slider, min: 0, max: 30, step: 1, default: 10}
      - {key: "motd[]", label: Message of the day, type: list}
  - path: battleye/launch/battleye/beserver_x64.cfg
    label: BattlEye RCon
    format: keyvalue_space
    platforms: [linux]
    settings:
      - {key: RConPassword, label: RCon password, type: password, bind: "{var.be_password}", user_editable: false}
File key Meaning
path Relative to the install directory (templates allowed).
format ini, keyvalue, keyvalue_space, properties, env, json, yaml, xml, arma_cfg, regex.
label Card title.
platforms Only on these OSes.
Setting key Meaning
key The setting’s name in the file. INI: Key or [Section]Key (a repeated key is a list). key=value formats: key. arma_cfg: hostname, arrays motd[], nested classes with dots (Missions.Mission1.template). JSON / YAML: a dot path (server.maxPlayers). XML: Root/Child/Leaf, or Root/Child@attr for an attribute.
type string, text, int, float, bool, password, select, slider, list.
bind Keep the setting equal to this template. {var.x} or {slots} alone: the form edits that value. Anything else: read-only.
default Shown when the key is not in the file.
options, min, max, step For selects, numbers and sliders.
true_value, false_value How a bool is spelled (default 1 / 0 for arma_cfg, true / false elsewhere).
user_editable false: only administrators change it.
pattern For regex files: a regular expression whose first group is the value.
label, description Shown in the form.

rcon

rcon: {type: battleye, port: "{port.rcon}", password: "{var.be_password}"}

type: source, battleye, minecraft or ts3query; user for ts3query (default serveradmin). The agent always connects to 127.0.0.1; a host key is accepted for compatibility but not used. Make sure the game’s own config enables RCON with the same port and password (bind them in config_files).

query

query: {type: a2s, port: "{port.query}"}

type: a2s, minecraft or generic (TCP connect only). See RCON and player counts.

file_manager

file_manager:
  deny: ["*.exe", "*.dll", "*.so", "battleye/**", ".ngcp/**"]

Globs hidden from non-administrators in the file manager and SFTP.

backup

backup:
  include: ["saves/**"]        # empty or absent: everything
  exclude: ["*.log", "mods/**"]

hooks

Scripts under scripts/ run as the service account at points in the lifecycle:

hooks:
  after_install: scripts/after_install.{ps1|sh}
  before_start: scripts/before_start.{ps1|sh}
  before_backup: scripts/save_world.{ps1|sh}
Hook Runs If it fails
after_install After a successful install The install fails
before_start Before every start; stopped after 5 minutes The service stays stopped; the console says why
before_backup Before every backup (for example a save command); stopped after 5 minutes The backup runs anyway; the console says why

Scripts

Scripts live next to game.yaml under scripts/ (shipped definitions) or are uploaded with the definition (scripts in the API). Each referenced script must exist for every platform the game supports. On Windows they run with PowerShell, on Linux with bash, as the service account, in the install directory, with the NGCP_* environment variables.

Checking your work

  • Check for problems in the editor, or POST /api/v1/game-definitions/validate.
  • Create a test service on each OS, install it, start it and check the console, the ports on the overview, the config forms and a backup.
  • Raise version with every change you want nodes to pick up.