@ankorcs/protocol 0.0.0-stage → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +9 -0
- package/LICENSE +202 -0
- package/README.md +72 -2
- package/dist/agent-config.d.ts +101 -0
- package/dist/agent-config.js +144 -0
- package/dist/agent-image.d.ts +12 -0
- package/dist/agent-image.js +12 -0
- package/dist/agent-socket.d.ts +3927 -0
- package/dist/agent-socket.js +1764 -0
- package/dist/agent-yml.d.ts +21 -0
- package/dist/agent-yml.js +71 -0
- package/dist/agent-yml.schema.json +160 -0
- package/dist/asyncapi.yaml +10569 -0
- package/dist/checks.d.ts +19 -0
- package/dist/checks.js +40 -0
- package/dist/container.d.ts +190 -0
- package/dist/container.js +144 -0
- package/dist/event-batch.d.ts +857 -0
- package/dist/event-batch.js +90 -0
- package/dist/game-events.d.ts +2137 -0
- package/dist/game-events.js +1082 -0
- package/dist/helpers.d.ts +176 -0
- package/dist/helpers.js +362 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.js +26 -0
- package/dist/launch.d.ts +316 -0
- package/dist/launch.js +287 -0
- package/dist/limits.d.ts +381 -0
- package/dist/limits.js +277 -0
- package/dist/paths.d.ts +73 -0
- package/dist/paths.js +73 -0
- package/dist/plugin-socket.d.ts +1946 -0
- package/dist/plugin-socket.js +160 -0
- package/dist/session.d.ts +127 -0
- package/dist/session.js +114 -0
- package/dist/values.d.ts +186 -0
- package/dist/values.js +230 -0
- package/node-setup.sh +1061 -0
- package/package.json +57 -4
package/dist/limits.d.ts
ADDED
|
@@ -0,0 +1,381 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The limits a controller enforces on a node before it closes the socket or refuses the request,
|
|
3
|
+
* then the agent's timeouts and retry pacing as a controller sees them. An agent within the limits
|
|
4
|
+
* is never limited, and Ankor's agent stays well within them.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
/**
|
|
8
|
+
* Most slots a node may have, in `agent.yml`, {@link AgentHello | hello} and
|
|
9
|
+
* {@link ControllerSetConfig | set_config}.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MAX_SLOTS = 64;
|
|
12
|
+
/**
|
|
13
|
+
* Smallest `memory_mb`, in MiB. A game server uses about 700 MiB for a 1v1 and more for a full
|
|
14
|
+
* match, so a lower cap would stop servers mid-match.
|
|
15
|
+
*/
|
|
16
|
+
export declare const MIN_MEMORY_MB = 1024;
|
|
17
|
+
/** Largest `memory_mb`, in MiB, far above what a game server uses. */
|
|
18
|
+
export declare const MAX_MEMORY_MB = 65536;
|
|
19
|
+
/**
|
|
20
|
+
* Most demos a {@link AgentHello | hello} lists. A node that keeps more lists its newest by
|
|
21
|
+
* `modified_at`. An older demo is listed once newer ones are no longer on the node.
|
|
22
|
+
*/
|
|
23
|
+
export declare const MAX_REPORTED_DEMOS = 50;
|
|
24
|
+
/**
|
|
25
|
+
* Most stuck servers an {@link EventBacklog} lists, oldest first. Its counts include the rest. A
|
|
26
|
+
* newer server is listed once older ones are uploaded.
|
|
27
|
+
*/
|
|
28
|
+
export declare const MAX_REPORTED_BACKLOGS = 100;
|
|
29
|
+
/**
|
|
30
|
+
* Allocations a node remembers, the most recently launched, and refuses to launch again with
|
|
31
|
+
* `allocation_reused`. A controller that picks a fresh UUID for every launch never meets the
|
|
32
|
+
* limit.
|
|
33
|
+
*/
|
|
34
|
+
export declare const REMEMBERED_ALLOCATIONS = 100;
|
|
35
|
+
/**
|
|
36
|
+
* Bytes in the largest socket message the controller reads. A larger message closes the socket
|
|
37
|
+
* with code `1009`. A 64-slot {@link AgentHello | hello} listing every demo and every backlog it
|
|
38
|
+
* may carry uses under half.
|
|
39
|
+
*/
|
|
40
|
+
export declare const AGENT_FRAME_MAX_BYTES: number;
|
|
41
|
+
/**
|
|
42
|
+
* Bytes in the largest socket message the agent reads from the controller. A larger message closes
|
|
43
|
+
* the socket with code `1009`. The limit leaves room for a launch with a backup of
|
|
44
|
+
* {@link MAX_BACKUP_FILE_CHARS} characters, each of which JSON may write in two bytes.
|
|
45
|
+
*/
|
|
46
|
+
export declare const CONTROLLER_FRAME_MAX_BYTES: number;
|
|
47
|
+
/**
|
|
48
|
+
* Why the controller closed a node's socket, as the close reason. {@link SOCKET_CLOSES} lists the
|
|
49
|
+
* code of each and the agent's response. `frame_too_large` is the controller's record of a `1009`
|
|
50
|
+
* close, which carries no reason.
|
|
51
|
+
*/
|
|
52
|
+
export declare const CloseReason: z.ZodEnum<{
|
|
53
|
+
rate_limited: "rate_limited";
|
|
54
|
+
frame_too_large: "frame_too_large";
|
|
55
|
+
hello_timeout: "hello_timeout";
|
|
56
|
+
agent_deleted: "agent_deleted";
|
|
57
|
+
resync: "resync";
|
|
58
|
+
node_connected: "node_connected";
|
|
59
|
+
hello_refused: "hello_refused";
|
|
60
|
+
duplicate_hello: "duplicate_hello";
|
|
61
|
+
}>;
|
|
62
|
+
export type CloseReason = z.infer<typeof CloseReason>;
|
|
63
|
+
/**
|
|
64
|
+
* Milliseconds the agent waits for the controller to close after
|
|
65
|
+
* {@link ControllerHelloRefused | hello_refused}. Past them, the agent closes the socket itself with code
|
|
66
|
+
* `1000`.
|
|
67
|
+
*/
|
|
68
|
+
export declare const AGENT_HELLO_REFUSED_CLOSE_MS = 5000;
|
|
69
|
+
/**
|
|
70
|
+
* Milliseconds a socket has after it opens to send a valid {@link AgentHello | hello}. Past them,
|
|
71
|
+
* the controller closes it with code `1008` and `hello_timeout`.
|
|
72
|
+
*/
|
|
73
|
+
export declare const AGENT_HELLO_TIMEOUT_MS = 10000;
|
|
74
|
+
/**
|
|
75
|
+
* Milliseconds the agent waits for a {@link ControllerWelcome | welcome} after it sends its
|
|
76
|
+
* {@link AgentHello | hello}. Past them, the agent closes the socket with code `1000` and
|
|
77
|
+
* {@link AGENT_WELCOME_TIMEOUT_REASON}, and redials.
|
|
78
|
+
*/
|
|
79
|
+
export declare const AGENT_WELCOME_TIMEOUT_MS = 15000;
|
|
80
|
+
/**
|
|
81
|
+
* The close reason the agent sends, with code `1000`, when no {@link ControllerWelcome | welcome} answered
|
|
82
|
+
* its {@link AgentHello | hello} within {@link AGENT_WELCOME_TIMEOUT_MS}.
|
|
83
|
+
*/
|
|
84
|
+
export declare const AGENT_WELCOME_TIMEOUT_REASON = "welcome_timeout";
|
|
85
|
+
/**
|
|
86
|
+
* Milliseconds the controller answers a node's upgrades with `429` after it closed the node's
|
|
87
|
+
* socket with `rate_limited` or `frame_too_large`.
|
|
88
|
+
*/
|
|
89
|
+
export declare const AGENT_LIMIT_COOLDOWN_MS = 60000;
|
|
90
|
+
/**
|
|
91
|
+
* The WebSocket close codes the agent's socket is closed with on purpose, from
|
|
92
|
+
* [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1). {@link SOCKET_CLOSES} lists
|
|
93
|
+
* when each is sent.
|
|
94
|
+
*/
|
|
95
|
+
export declare const CLOSE_CODES: {
|
|
96
|
+
/** A normal close, by either side. */
|
|
97
|
+
readonly normal: 1000;
|
|
98
|
+
/** The controller ends the connection, with a {@link CloseReason} saying why. */
|
|
99
|
+
readonly policy: 1008;
|
|
100
|
+
/** A message over the size limit. */
|
|
101
|
+
readonly messageTooBig: 1009;
|
|
102
|
+
};
|
|
103
|
+
export type CloseCode = (typeof CLOSE_CODES)[keyof typeof CLOSE_CODES];
|
|
104
|
+
/** One way the agent's socket is closed on purpose. */
|
|
105
|
+
export interface SocketClose {
|
|
106
|
+
/** The WebSocket close code, one of {@link CLOSE_CODES}. */
|
|
107
|
+
code: CloseCode;
|
|
108
|
+
/** The close reason, as Markdown. Null when the close carries none. */
|
|
109
|
+
reason: string | null;
|
|
110
|
+
/** The side that closes the socket. */
|
|
111
|
+
by: 'controller' | 'agent';
|
|
112
|
+
/** When the close happens. */
|
|
113
|
+
when: string;
|
|
114
|
+
/** What the agent does next. */
|
|
115
|
+
then: string;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Every close that the controller or the agent makes on purpose, with a code from
|
|
119
|
+
* {@link CLOSE_CODES}. Any other code, or none, is a broken connection, and the agent redials.
|
|
120
|
+
*/
|
|
121
|
+
export declare const SOCKET_CLOSES: readonly SocketClose[];
|
|
122
|
+
/**
|
|
123
|
+
* The close reasons after which the controller answers upgrades with `429` for
|
|
124
|
+
* {@link AGENT_LIMIT_COOLDOWN_MS}.
|
|
125
|
+
*/
|
|
126
|
+
export declare const LimitCloseReason: z.ZodEnum<{
|
|
127
|
+
rate_limited: "rate_limited";
|
|
128
|
+
frame_too_large: "frame_too_large";
|
|
129
|
+
}>;
|
|
130
|
+
export type LimitCloseReason = z.infer<typeof LimitCloseReason>;
|
|
131
|
+
/** The close code that goes with the reason `rate_limited`. */
|
|
132
|
+
export declare const RATE_LIMITED_CLOSE_CODE: 1008;
|
|
133
|
+
/** A token bucket, which allows up to `burst` immediately and refills at `perSecond`. */
|
|
134
|
+
export interface Rate {
|
|
135
|
+
burst: number;
|
|
136
|
+
perSecond: number;
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* A node's rate, which is `base` plus `perSlot` for every slot the node has. The slots are those of
|
|
140
|
+
* its last {@link AgentHello | hello} or applied {@link ControllerSetConfig | set_config} reply, and one
|
|
141
|
+
* before its first {@link AgentHello | hello}.
|
|
142
|
+
*/
|
|
143
|
+
export interface Budget {
|
|
144
|
+
base: Rate;
|
|
145
|
+
perSlot: Rate;
|
|
146
|
+
}
|
|
147
|
+
/** The budgets of one socket connection, each starting full. A new connection starts over. */
|
|
148
|
+
export declare const AGENT_BUDGETS: {
|
|
149
|
+
/**
|
|
150
|
+
* Bytes of every message, counted before the controller reads it. Over it, the controller
|
|
151
|
+
* closes the socket with `rate_limited`. The budget holds every other budget's worth of messages
|
|
152
|
+
* with room to spare.
|
|
153
|
+
*/
|
|
154
|
+
readonly bytes: {
|
|
155
|
+
readonly base: {
|
|
156
|
+
readonly burst: number;
|
|
157
|
+
readonly perSecond: number;
|
|
158
|
+
};
|
|
159
|
+
readonly perSlot: {
|
|
160
|
+
readonly burst: number;
|
|
161
|
+
readonly perSecond: number;
|
|
162
|
+
};
|
|
163
|
+
};
|
|
164
|
+
/**
|
|
165
|
+
* {@link AgentHello | hello}, {@link AgentSlotStatus | slot_status},
|
|
166
|
+
* {@link AgentNodeStatus | node_status}, {@link AgentServerExited | server_exited},
|
|
167
|
+
* {@link AgentDemo | demo}, {@link AgentDemoRemoved | demo_removed},
|
|
168
|
+
* {@link AgentEventBacklog | event_backlog} and {@link AgentShutdown | shutdown} messages, and
|
|
169
|
+
* any message the controller cannot read. Over it, the controller closes the socket with
|
|
170
|
+
* `rate_limited`.
|
|
171
|
+
*/
|
|
172
|
+
readonly state: {
|
|
173
|
+
readonly base: {
|
|
174
|
+
readonly burst: 64;
|
|
175
|
+
readonly perSecond: 2;
|
|
176
|
+
};
|
|
177
|
+
readonly perSlot: {
|
|
178
|
+
readonly burst: 32;
|
|
179
|
+
readonly perSecond: 1;
|
|
180
|
+
};
|
|
181
|
+
};
|
|
182
|
+
/**
|
|
183
|
+
* {@link AgentLogBatch | log_batch} and {@link AgentLogIndex | log_index} messages. Over it, or
|
|
184
|
+
* over {@link AGENT_BUDGETS.logBytes}, the controller drops the message.
|
|
185
|
+
*
|
|
186
|
+
* An agent should hold its log output to these budgets, because nothing else bounds the replays a
|
|
187
|
+
* controller asks for. Ankor's agent trims a replay to the remaining budget and drops new lines
|
|
188
|
+
* beyond it.
|
|
189
|
+
*/
|
|
190
|
+
readonly logFrames: {
|
|
191
|
+
readonly base: {
|
|
192
|
+
readonly burst: 128;
|
|
193
|
+
readonly perSecond: 32;
|
|
194
|
+
};
|
|
195
|
+
readonly perSlot: Rate;
|
|
196
|
+
};
|
|
197
|
+
/**
|
|
198
|
+
* Bytes of {@link AgentLogBatch | log_batch} and {@link AgentLogIndex | log_index} messages,
|
|
199
|
+
* about four replays of a full source together.
|
|
200
|
+
*/
|
|
201
|
+
readonly logBytes: {
|
|
202
|
+
readonly base: {
|
|
203
|
+
readonly burst: number;
|
|
204
|
+
readonly perSecond: number;
|
|
205
|
+
};
|
|
206
|
+
readonly perSlot: Rate;
|
|
207
|
+
};
|
|
208
|
+
/** {@link AgentResourceBatch | resource_batch} messages. Over it, the controller drops them. */
|
|
209
|
+
readonly resources: {
|
|
210
|
+
readonly base: {
|
|
211
|
+
readonly burst: 10;
|
|
212
|
+
readonly perSecond: 1;
|
|
213
|
+
};
|
|
214
|
+
readonly perSlot: Rate;
|
|
215
|
+
};
|
|
216
|
+
/**
|
|
217
|
+
* {@link AgentCs2UpdateProgress | cs2_update_progress} messages. Over it, the controller drops
|
|
218
|
+
* them.
|
|
219
|
+
*/
|
|
220
|
+
readonly cs2UpdateProgress: {
|
|
221
|
+
readonly base: {
|
|
222
|
+
readonly burst: 50;
|
|
223
|
+
readonly perSecond: 10;
|
|
224
|
+
};
|
|
225
|
+
readonly perSlot: Rate;
|
|
226
|
+
};
|
|
227
|
+
/** {@link AgentDemoProgress | demo_progress} messages. Over it, the controller drops them. */
|
|
228
|
+
readonly demoProgress: {
|
|
229
|
+
readonly base: {
|
|
230
|
+
readonly burst: 10;
|
|
231
|
+
readonly perSecond: 2;
|
|
232
|
+
};
|
|
233
|
+
readonly perSlot: Rate;
|
|
234
|
+
};
|
|
235
|
+
/**
|
|
236
|
+
* {@link AgentPhaseChange | phase_change}, {@link AgentScore | score} and
|
|
237
|
+
* {@link AgentBackup | backup} messages. Over it, the controller drops them. A match
|
|
238
|
+
* sends a few of them a round.
|
|
239
|
+
*/
|
|
240
|
+
readonly matchReports: {
|
|
241
|
+
readonly base: Rate;
|
|
242
|
+
readonly perSlot: {
|
|
243
|
+
readonly burst: 16;
|
|
244
|
+
readonly perSecond: 1;
|
|
245
|
+
};
|
|
246
|
+
};
|
|
247
|
+
/**
|
|
248
|
+
* {@link AgentReply | reply} messages that answer no open request, such as one past its request's
|
|
249
|
+
* timeout. Replies to open requests are free. Over it, the controller closes the socket with
|
|
250
|
+
* `rate_limited`.
|
|
251
|
+
*/
|
|
252
|
+
readonly strayReplies: {
|
|
253
|
+
readonly base: {
|
|
254
|
+
readonly burst: 32;
|
|
255
|
+
readonly perSecond: 1;
|
|
256
|
+
};
|
|
257
|
+
readonly perSlot: Rate;
|
|
258
|
+
};
|
|
259
|
+
/**
|
|
260
|
+
* WebSocket pings, each of which the controller answers with a pong. Over it, the controller
|
|
261
|
+
* closes the socket with `rate_limited`.
|
|
262
|
+
*/
|
|
263
|
+
readonly pings: {
|
|
264
|
+
readonly base: {
|
|
265
|
+
readonly burst: 5;
|
|
266
|
+
readonly perSecond: 1;
|
|
267
|
+
};
|
|
268
|
+
readonly perSlot: Rate;
|
|
269
|
+
};
|
|
270
|
+
};
|
|
271
|
+
/** The name of one budget in {@link AGENT_BUDGETS}. */
|
|
272
|
+
export type BudgetName = keyof typeof AGENT_BUDGETS;
|
|
273
|
+
/**
|
|
274
|
+
* Socket upgrades a node may make per window. Past it, the controller answers `429` until the
|
|
275
|
+
* window that began with the first of them ends. Ankor's agent redials only after a lost connection
|
|
276
|
+
* or a moved address.
|
|
277
|
+
*/
|
|
278
|
+
export declare const AGENT_CONNECTS: {
|
|
279
|
+
readonly max: 10;
|
|
280
|
+
readonly windowMs: 60000;
|
|
281
|
+
};
|
|
282
|
+
/**
|
|
283
|
+
* Upgrades with a missing or unknown token per remote address and window. Past it, the controller
|
|
284
|
+
* answers every upgrade from that address with `429`, a known token too, until the window ends.
|
|
285
|
+
*/
|
|
286
|
+
export declare const AGENT_AUTH_FAILURES: {
|
|
287
|
+
readonly max: 20;
|
|
288
|
+
readonly windowMs: 60000;
|
|
289
|
+
};
|
|
290
|
+
/**
|
|
291
|
+
* Event-upload bytes a node may post per window: `base` plus `perSlot` for each configured slot,
|
|
292
|
+
* every request counting `requestBytes` more than its body. Past it, the controller answers `429`.
|
|
293
|
+
*/
|
|
294
|
+
export declare const EVENT_BATCH_BUDGET: {
|
|
295
|
+
readonly windowMs: 60000;
|
|
296
|
+
readonly base: number;
|
|
297
|
+
readonly perSlot: number;
|
|
298
|
+
readonly requestBytes: 1024;
|
|
299
|
+
};
|
|
300
|
+
/**
|
|
301
|
+
* Milliseconds that must pass between two {@link ControllerCheckCs2Build | check_cs2_build} messages the
|
|
302
|
+
* agent handles. The agent drops one that arrives sooner.
|
|
303
|
+
*/
|
|
304
|
+
export declare const CS2_BUILD_CHECK_INTERVAL_MS = 60000;
|
|
305
|
+
/**
|
|
306
|
+
* Milliseconds between an installed node's reads of the newest public CS2 build from Steam. The
|
|
307
|
+
* node also reads it on {@link ControllerCheckCs2Build | check_cs2_build}.
|
|
308
|
+
*/
|
|
309
|
+
export declare const CS2_BUILD_POLL_INTERVAL_MS: number;
|
|
310
|
+
/**
|
|
311
|
+
* Milliseconds between a node's reads of its images' tags from their registries, while its
|
|
312
|
+
* `auto_update` is on. The agent also reads them when it starts, when `auto_update` is turned on,
|
|
313
|
+
* when an update changes an image in `agent.yml`, and when maintenance that an image update waited
|
|
314
|
+
* for ends.
|
|
315
|
+
*/
|
|
316
|
+
export declare const IMAGE_UPDATE_POLL_INTERVAL_MS: number;
|
|
317
|
+
/**
|
|
318
|
+
* Seconds a server may stay `starting` after its launch, or after a restarted agent took it over.
|
|
319
|
+
* Past them, the agent stops it with `start_timeout` as its {@link ServerExitReason}.
|
|
320
|
+
*/
|
|
321
|
+
export declare const START_TIMEOUT_SECONDS = 60;
|
|
322
|
+
/**
|
|
323
|
+
* Seconds a server may keep running after its plugin sent `quitting`. Past them, the agent stops it
|
|
324
|
+
* with `quit_timeout` as its {@link ServerExitReason}.
|
|
325
|
+
*/
|
|
326
|
+
export declare const QUIT_TIMEOUT_SECONDS = 10;
|
|
327
|
+
/** Milliseconds an event upload may go unanswered before it counts as failed. */
|
|
328
|
+
export declare const EVENT_UPLOAD_TIMEOUT_MS = 15000;
|
|
329
|
+
/**
|
|
330
|
+
* Milliseconds before the first retry after a failed event upload. Each further failure doubles the
|
|
331
|
+
* wait.
|
|
332
|
+
*/
|
|
333
|
+
export declare const EVENT_RETRY_MIN_MS = 1000;
|
|
334
|
+
/**
|
|
335
|
+
* Milliseconds of the longest wait between event upload retries, before jitter. Each wait is
|
|
336
|
+
* randomised like an agent redial, by up to {@link AGENT_RECONNECT_JITTER} either way.
|
|
337
|
+
*/
|
|
338
|
+
export declare const EVENT_RETRY_MAX_MS = 30000;
|
|
339
|
+
/**
|
|
340
|
+
* Milliseconds a server's events are kept on the node after its last event or receipt. Delivered
|
|
341
|
+
* events are remembered that long so a replay of them is recognised. Events that no controller
|
|
342
|
+
* took in that time are dropped with them, and the agent logs how many, so a server whose batches
|
|
343
|
+
* are refused cannot fill the node's disk. A server whose plugin is still connected keeps its
|
|
344
|
+
* events. An undelivered {@link AgentServerExited | server_exited} is kept as long after its
|
|
345
|
+
* server ended.
|
|
346
|
+
*/
|
|
347
|
+
export declare const EVENT_SPOOL_RETENTION_MS: number;
|
|
348
|
+
/**
|
|
349
|
+
* Fewest milliseconds between two {@link AgentDemoProgress | demo_progress} messages of one upload.
|
|
350
|
+
*/
|
|
351
|
+
export declare const DEMO_PROGRESS_INTERVAL_MS = 1000;
|
|
352
|
+
/** Milliseconds a demo upload may send nothing before it is aborted and retried. */
|
|
353
|
+
export declare const DEMO_STALL_TIMEOUT_MS: number;
|
|
354
|
+
/**
|
|
355
|
+
* Milliseconds before the first retry after a failed demo upload. Each further failure doubles the
|
|
356
|
+
* wait.
|
|
357
|
+
*/
|
|
358
|
+
export declare const DEMO_RETRY_MIN_MS: number;
|
|
359
|
+
/** Milliseconds of the longest wait between demo upload retries. */
|
|
360
|
+
export declare const DEMO_RETRY_MAX_MS: number;
|
|
361
|
+
/**
|
|
362
|
+
* Milliseconds demo uploads may keep failing before they stop. They start again when a new demo is
|
|
363
|
+
* recorded, the target changes or the agent restarts.
|
|
364
|
+
*/
|
|
365
|
+
export declare const DEMO_OUTAGE_LIMIT_MS: number;
|
|
366
|
+
/**
|
|
367
|
+
* Gigabytes of free disk a CS2 update needs. steamcmd stages a whole new copy of every changed file
|
|
368
|
+
* before it swaps them in, and a content drop changes many gigabyte-sized VPKs together, so 6 GB
|
|
369
|
+
* free was not enough for one.
|
|
370
|
+
*/
|
|
371
|
+
export declare const CS2_UPDATE_MIN_FREE_GB = 20;
|
|
372
|
+
/**
|
|
373
|
+
* Gigabytes of free disk a CS2 reinstall needs, because a reinstall downloads the entire game. The
|
|
374
|
+
* space is checked after the wipe.
|
|
375
|
+
*/
|
|
376
|
+
export declare const CS2_REINSTALL_MIN_FREE_GB = 70;
|
|
377
|
+
/**
|
|
378
|
+
* Gigabytes of disk a node needs beyond its operating system: about 70 for the CS2 installation,
|
|
379
|
+
* and the rest for an update to stage changed files in. A controller may warn about less.
|
|
380
|
+
*/
|
|
381
|
+
export declare const NODE_MIN_DISK_GB = 90;
|
package/dist/limits.js
ADDED
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The limits a controller enforces on a node before it closes the socket or refuses the request,
|
|
3
|
+
* then the agent's timeouts and retry pacing as a controller sees them. An agent within the limits
|
|
4
|
+
* is never limited, and Ankor's agent stays well within them.
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
/**
|
|
8
|
+
* Most slots a node may have, in `agent.yml`, {@link AgentHello | hello} and
|
|
9
|
+
* {@link ControllerSetConfig | set_config}.
|
|
10
|
+
*/
|
|
11
|
+
export const MAX_SLOTS = 64;
|
|
12
|
+
/**
|
|
13
|
+
* Smallest `memory_mb`, in MiB. A game server uses about 700 MiB for a 1v1 and more for a full
|
|
14
|
+
* match, so a lower cap would stop servers mid-match.
|
|
15
|
+
*/
|
|
16
|
+
export const MIN_MEMORY_MB = 1024;
|
|
17
|
+
/** Largest `memory_mb`, in MiB, far above what a game server uses. */
|
|
18
|
+
export const MAX_MEMORY_MB = 65536;
|
|
19
|
+
/**
|
|
20
|
+
* Most demos a {@link AgentHello | hello} lists. A node that keeps more lists its newest by
|
|
21
|
+
* `modified_at`. An older demo is listed once newer ones are no longer on the node.
|
|
22
|
+
*/
|
|
23
|
+
export const MAX_REPORTED_DEMOS = 50;
|
|
24
|
+
/**
|
|
25
|
+
* Most stuck servers an {@link EventBacklog} lists, oldest first. Its counts include the rest. A
|
|
26
|
+
* newer server is listed once older ones are uploaded.
|
|
27
|
+
*/
|
|
28
|
+
export const MAX_REPORTED_BACKLOGS = 100;
|
|
29
|
+
/**
|
|
30
|
+
* Allocations a node remembers, the most recently launched, and refuses to launch again with
|
|
31
|
+
* `allocation_reused`. A controller that picks a fresh UUID for every launch never meets the
|
|
32
|
+
* limit.
|
|
33
|
+
*/
|
|
34
|
+
export const REMEMBERED_ALLOCATIONS = 100;
|
|
35
|
+
/**
|
|
36
|
+
* Bytes in the largest socket message the controller reads. A larger message closes the socket
|
|
37
|
+
* with code `1009`. A 64-slot {@link AgentHello | hello} listing every demo and every backlog it
|
|
38
|
+
* may carry uses under half.
|
|
39
|
+
*/
|
|
40
|
+
export const AGENT_FRAME_MAX_BYTES = 960 * 1024;
|
|
41
|
+
/**
|
|
42
|
+
* Bytes in the largest socket message the agent reads from the controller. A larger message closes
|
|
43
|
+
* the socket with code `1009`. The limit leaves room for a launch with a backup of
|
|
44
|
+
* {@link MAX_BACKUP_FILE_CHARS} characters, each of which JSON may write in two bytes.
|
|
45
|
+
*/
|
|
46
|
+
export const CONTROLLER_FRAME_MAX_BYTES = 2 * 1024 * 1024;
|
|
47
|
+
/**
|
|
48
|
+
* Why the controller closed a node's socket, as the close reason. {@link SOCKET_CLOSES} lists the
|
|
49
|
+
* code of each and the agent's response. `frame_too_large` is the controller's record of a `1009`
|
|
50
|
+
* close, which carries no reason.
|
|
51
|
+
*/
|
|
52
|
+
export const CloseReason = z.enum(['rate_limited', 'frame_too_large', 'hello_timeout', 'agent_deleted', 'resync', 'node_connected', 'hello_refused', 'duplicate_hello']);
|
|
53
|
+
/**
|
|
54
|
+
* Milliseconds the agent waits for the controller to close after
|
|
55
|
+
* {@link ControllerHelloRefused | hello_refused}. Past them, the agent closes the socket itself with code
|
|
56
|
+
* `1000`.
|
|
57
|
+
*/
|
|
58
|
+
export const AGENT_HELLO_REFUSED_CLOSE_MS = 5_000;
|
|
59
|
+
/**
|
|
60
|
+
* Milliseconds a socket has after it opens to send a valid {@link AgentHello | hello}. Past them,
|
|
61
|
+
* the controller closes it with code `1008` and `hello_timeout`.
|
|
62
|
+
*/
|
|
63
|
+
export const AGENT_HELLO_TIMEOUT_MS = 10_000;
|
|
64
|
+
/**
|
|
65
|
+
* Milliseconds the agent waits for a {@link ControllerWelcome | welcome} after it sends its
|
|
66
|
+
* {@link AgentHello | hello}. Past them, the agent closes the socket with code `1000` and
|
|
67
|
+
* {@link AGENT_WELCOME_TIMEOUT_REASON}, and redials.
|
|
68
|
+
*/
|
|
69
|
+
export const AGENT_WELCOME_TIMEOUT_MS = 15_000;
|
|
70
|
+
/**
|
|
71
|
+
* The close reason the agent sends, with code `1000`, when no {@link ControllerWelcome | welcome} answered
|
|
72
|
+
* its {@link AgentHello | hello} within {@link AGENT_WELCOME_TIMEOUT_MS}.
|
|
73
|
+
*/
|
|
74
|
+
export const AGENT_WELCOME_TIMEOUT_REASON = 'welcome_timeout';
|
|
75
|
+
/**
|
|
76
|
+
* Milliseconds the controller answers a node's upgrades with `429` after it closed the node's
|
|
77
|
+
* socket with `rate_limited` or `frame_too_large`.
|
|
78
|
+
*/
|
|
79
|
+
export const AGENT_LIMIT_COOLDOWN_MS = 60_000;
|
|
80
|
+
/**
|
|
81
|
+
* The WebSocket close codes the agent's socket is closed with on purpose, from
|
|
82
|
+
* [RFC 6455](https://www.rfc-editor.org/rfc/rfc6455#section-7.4.1). {@link SOCKET_CLOSES} lists
|
|
83
|
+
* when each is sent.
|
|
84
|
+
*/
|
|
85
|
+
export const CLOSE_CODES = {
|
|
86
|
+
/** A normal close, by either side. */
|
|
87
|
+
normal: 1000,
|
|
88
|
+
/** The controller ends the connection, with a {@link CloseReason} saying why. */
|
|
89
|
+
policy: 1008,
|
|
90
|
+
/** A message over the size limit. */
|
|
91
|
+
messageTooBig: 1009,
|
|
92
|
+
};
|
|
93
|
+
/**
|
|
94
|
+
* Every close that the controller or the agent makes on purpose, with a code from
|
|
95
|
+
* {@link CLOSE_CODES}. Any other code, or none, is a broken connection, and the agent redials.
|
|
96
|
+
*/
|
|
97
|
+
export const SOCKET_CLOSES = [
|
|
98
|
+
{ code: CLOSE_CODES.normal, reason: '`SIGTERM`, `SIGINT` or `SIGQUIT`', by: 'agent', when: 'The agent is stopping, after its `shutdown`.', then: 'The controller closes its side with the same code and reason.' },
|
|
99
|
+
{ code: CLOSE_CODES.policy, reason: '`hello_refused`', by: 'controller', when: 'The controller will not accept the node\'s `hello`, because the `hello` fails its schema or the controller refuses the node. A `hello_refused` frame just before the close says which.', then: 'Logs the frame\'s reason and detail, and stops dialling until it is restarted.' },
|
|
100
|
+
{ code: CLOSE_CODES.normal, reason: null, by: 'agent', when: `The controller sent \`hello_refused\` and has not closed within ${AGENT_HELLO_REFUSED_CLOSE_MS / 1000} seconds.`, then: 'Stays stopped until it is restarted.' },
|
|
101
|
+
{ code: CLOSE_CODES.normal, reason: null, by: 'agent', when: 'A `set_config` moved the node to another controller address, once its reply has gone.', then: 'Dials the new address immediately.' },
|
|
102
|
+
{ code: CLOSE_CODES.policy, reason: '`hello_timeout`', by: 'controller', when: `No valid \`hello\` within ${AGENT_HELLO_TIMEOUT_MS / 1000} seconds of the upgrade.`, then: 'Redials with its backoff.' },
|
|
103
|
+
{ code: CLOSE_CODES.normal, reason: '`welcome_timeout`', by: 'agent', when: `No \`welcome\` answered its \`hello\` within ${AGENT_WELCOME_TIMEOUT_MS / 1000} seconds.`, then: 'Redials with its backoff.' },
|
|
104
|
+
{ code: CLOSE_CODES.policy, reason: '`duplicate_hello`', by: 'controller', when: 'A second `hello` arrived on the same socket.', then: 'Redials with its backoff.' },
|
|
105
|
+
{ code: CLOSE_CODES.policy, reason: '`node_connected`', by: 'controller', when: 'The node is already connected on another socket.', then: 'Redials with its backoff, and connects once the other socket closes or times out.' },
|
|
106
|
+
{ code: CLOSE_CODES.policy, reason: '`rate_limited`', by: 'controller', when: 'The node went over a [budget](/protocol/limits/).', then: `Redials with its backoff. The controller answers \`429\` for ${AGENT_LIMIT_COOLDOWN_MS / 1000} seconds.` },
|
|
107
|
+
{ code: CLOSE_CODES.policy, reason: '`agent_deleted`', by: 'controller', when: 'The node was deleted in the controller.', then: 'Stops dialling until it is restarted.' },
|
|
108
|
+
{ code: CLOSE_CODES.policy, reason: '`resync`', by: 'controller', when: 'The controller lost track of the node\'s state.', then: 'Redials and sends a fresh `hello`.' },
|
|
109
|
+
{ code: CLOSE_CODES.messageTooBig, reason: null, by: 'controller', when: `A message over ${AGENT_FRAME_MAX_BYTES / 1024} KiB.`, then: 'Redials with its backoff.' },
|
|
110
|
+
{ code: CLOSE_CODES.messageTooBig, reason: null, by: 'agent', when: `The controller sent a message over ${CONTROLLER_FRAME_MAX_BYTES / (1024 * 1024)} MiB.`, then: 'Redials with its backoff.' },
|
|
111
|
+
];
|
|
112
|
+
/**
|
|
113
|
+
* The close reasons after which the controller answers upgrades with `429` for
|
|
114
|
+
* {@link AGENT_LIMIT_COOLDOWN_MS}.
|
|
115
|
+
*/
|
|
116
|
+
export const LimitCloseReason = CloseReason.extract(['rate_limited', 'frame_too_large']);
|
|
117
|
+
/** The close code that goes with the reason `rate_limited`. */
|
|
118
|
+
export const RATE_LIMITED_CLOSE_CODE = CLOSE_CODES.policy;
|
|
119
|
+
const NONE = { burst: 0, perSecond: 0 };
|
|
120
|
+
const KIB = 1024;
|
|
121
|
+
const MIB = 1024 * KIB;
|
|
122
|
+
/** The budgets of one socket connection, each starting full. A new connection starts over. */
|
|
123
|
+
export const AGENT_BUDGETS = {
|
|
124
|
+
/**
|
|
125
|
+
* Bytes of every message, counted before the controller reads it. Over it, the controller
|
|
126
|
+
* closes the socket with `rate_limited`. The budget holds every other budget's worth of messages
|
|
127
|
+
* with room to spare.
|
|
128
|
+
*/
|
|
129
|
+
bytes: { base: { burst: 32 * MIB, perSecond: 512 * KIB }, perSlot: { burst: MIB, perSecond: 16 * KIB } },
|
|
130
|
+
/**
|
|
131
|
+
* {@link AgentHello | hello}, {@link AgentSlotStatus | slot_status},
|
|
132
|
+
* {@link AgentNodeStatus | node_status}, {@link AgentServerExited | server_exited},
|
|
133
|
+
* {@link AgentDemo | demo}, {@link AgentDemoRemoved | demo_removed},
|
|
134
|
+
* {@link AgentEventBacklog | event_backlog} and {@link AgentShutdown | shutdown} messages, and
|
|
135
|
+
* any message the controller cannot read. Over it, the controller closes the socket with
|
|
136
|
+
* `rate_limited`.
|
|
137
|
+
*/
|
|
138
|
+
state: { base: { burst: 64, perSecond: 2 }, perSlot: { burst: 32, perSecond: 1 } },
|
|
139
|
+
/**
|
|
140
|
+
* {@link AgentLogBatch | log_batch} and {@link AgentLogIndex | log_index} messages. Over it, or
|
|
141
|
+
* over {@link AGENT_BUDGETS.logBytes}, the controller drops the message.
|
|
142
|
+
*
|
|
143
|
+
* An agent should hold its log output to these budgets, because nothing else bounds the replays a
|
|
144
|
+
* controller asks for. Ankor's agent trims a replay to the remaining budget and drops new lines
|
|
145
|
+
* beyond it.
|
|
146
|
+
*/
|
|
147
|
+
logFrames: { base: { burst: 128, perSecond: 32 }, perSlot: NONE },
|
|
148
|
+
/**
|
|
149
|
+
* Bytes of {@link AgentLogBatch | log_batch} and {@link AgentLogIndex | log_index} messages,
|
|
150
|
+
* about four replays of a full source together.
|
|
151
|
+
*/
|
|
152
|
+
logBytes: { base: { burst: 16 * MIB, perSecond: 256 * KIB }, perSlot: NONE },
|
|
153
|
+
/** {@link AgentResourceBatch | resource_batch} messages. Over it, the controller drops them. */
|
|
154
|
+
resources: { base: { burst: 10, perSecond: 1 }, perSlot: NONE },
|
|
155
|
+
/**
|
|
156
|
+
* {@link AgentCs2UpdateProgress | cs2_update_progress} messages. Over it, the controller drops
|
|
157
|
+
* them.
|
|
158
|
+
*/
|
|
159
|
+
cs2UpdateProgress: { base: { burst: 50, perSecond: 10 }, perSlot: NONE },
|
|
160
|
+
/** {@link AgentDemoProgress | demo_progress} messages. Over it, the controller drops them. */
|
|
161
|
+
demoProgress: { base: { burst: 10, perSecond: 2 }, perSlot: NONE },
|
|
162
|
+
/**
|
|
163
|
+
* {@link AgentPhaseChange | phase_change}, {@link AgentScore | score} and
|
|
164
|
+
* {@link AgentBackup | backup} messages. Over it, the controller drops them. A match
|
|
165
|
+
* sends a few of them a round.
|
|
166
|
+
*/
|
|
167
|
+
matchReports: { base: NONE, perSlot: { burst: 16, perSecond: 1 } },
|
|
168
|
+
/**
|
|
169
|
+
* {@link AgentReply | reply} messages that answer no open request, such as one past its request's
|
|
170
|
+
* timeout. Replies to open requests are free. Over it, the controller closes the socket with
|
|
171
|
+
* `rate_limited`.
|
|
172
|
+
*/
|
|
173
|
+
strayReplies: { base: { burst: 32, perSecond: 1 }, perSlot: NONE },
|
|
174
|
+
/**
|
|
175
|
+
* WebSocket pings, each of which the controller answers with a pong. Over it, the controller
|
|
176
|
+
* closes the socket with `rate_limited`.
|
|
177
|
+
*/
|
|
178
|
+
pings: { base: { burst: 5, perSecond: 1 }, perSlot: NONE },
|
|
179
|
+
};
|
|
180
|
+
/**
|
|
181
|
+
* Socket upgrades a node may make per window. Past it, the controller answers `429` until the
|
|
182
|
+
* window that began with the first of them ends. Ankor's agent redials only after a lost connection
|
|
183
|
+
* or a moved address.
|
|
184
|
+
*/
|
|
185
|
+
export const AGENT_CONNECTS = { max: 10, windowMs: 60_000 };
|
|
186
|
+
/**
|
|
187
|
+
* Upgrades with a missing or unknown token per remote address and window. Past it, the controller
|
|
188
|
+
* answers every upgrade from that address with `429`, a known token too, until the window ends.
|
|
189
|
+
*/
|
|
190
|
+
export const AGENT_AUTH_FAILURES = { max: 20, windowMs: 60_000 };
|
|
191
|
+
/**
|
|
192
|
+
* Event-upload bytes a node may post per window: `base` plus `perSlot` for each configured slot,
|
|
193
|
+
* every request counting `requestBytes` more than its body. Past it, the controller answers `429`.
|
|
194
|
+
*/
|
|
195
|
+
export const EVENT_BATCH_BUDGET = { windowMs: 60_000, base: 32 * MIB, perSlot: 4 * MIB, requestBytes: KIB };
|
|
196
|
+
/**
|
|
197
|
+
* Milliseconds that must pass between two {@link ControllerCheckCs2Build | check_cs2_build} messages the
|
|
198
|
+
* agent handles. The agent drops one that arrives sooner.
|
|
199
|
+
*/
|
|
200
|
+
export const CS2_BUILD_CHECK_INTERVAL_MS = 60_000;
|
|
201
|
+
/**
|
|
202
|
+
* Milliseconds between an installed node's reads of the newest public CS2 build from Steam. The
|
|
203
|
+
* node also reads it on {@link ControllerCheckCs2Build | check_cs2_build}.
|
|
204
|
+
*/
|
|
205
|
+
export const CS2_BUILD_POLL_INTERVAL_MS = 30 * 60 * 1000;
|
|
206
|
+
/**
|
|
207
|
+
* Milliseconds between a node's reads of its images' tags from their registries, while its
|
|
208
|
+
* `auto_update` is on. The agent also reads them when it starts, when `auto_update` is turned on,
|
|
209
|
+
* when an update changes an image in `agent.yml`, and when maintenance that an image update waited
|
|
210
|
+
* for ends.
|
|
211
|
+
*/
|
|
212
|
+
export const IMAGE_UPDATE_POLL_INTERVAL_MS = 60 * 60 * 1000;
|
|
213
|
+
/**
|
|
214
|
+
* Seconds a server may stay `starting` after its launch, or after a restarted agent took it over.
|
|
215
|
+
* Past them, the agent stops it with `start_timeout` as its {@link ServerExitReason}.
|
|
216
|
+
*/
|
|
217
|
+
export const START_TIMEOUT_SECONDS = 60;
|
|
218
|
+
/**
|
|
219
|
+
* Seconds a server may keep running after its plugin sent `quitting`. Past them, the agent stops it
|
|
220
|
+
* with `quit_timeout` as its {@link ServerExitReason}.
|
|
221
|
+
*/
|
|
222
|
+
export const QUIT_TIMEOUT_SECONDS = 10;
|
|
223
|
+
/** Milliseconds an event upload may go unanswered before it counts as failed. */
|
|
224
|
+
export const EVENT_UPLOAD_TIMEOUT_MS = 15_000;
|
|
225
|
+
/**
|
|
226
|
+
* Milliseconds before the first retry after a failed event upload. Each further failure doubles the
|
|
227
|
+
* wait.
|
|
228
|
+
*/
|
|
229
|
+
export const EVENT_RETRY_MIN_MS = 1_000;
|
|
230
|
+
/**
|
|
231
|
+
* Milliseconds of the longest wait between event upload retries, before jitter. Each wait is
|
|
232
|
+
* randomised like an agent redial, by up to {@link AGENT_RECONNECT_JITTER} either way.
|
|
233
|
+
*/
|
|
234
|
+
export const EVENT_RETRY_MAX_MS = 30_000;
|
|
235
|
+
/**
|
|
236
|
+
* Milliseconds a server's events are kept on the node after its last event or receipt. Delivered
|
|
237
|
+
* events are remembered that long so a replay of them is recognised. Events that no controller
|
|
238
|
+
* took in that time are dropped with them, and the agent logs how many, so a server whose batches
|
|
239
|
+
* are refused cannot fill the node's disk. A server whose plugin is still connected keeps its
|
|
240
|
+
* events. An undelivered {@link AgentServerExited | server_exited} is kept as long after its
|
|
241
|
+
* server ended.
|
|
242
|
+
*/
|
|
243
|
+
export const EVENT_SPOOL_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
244
|
+
/**
|
|
245
|
+
* Fewest milliseconds between two {@link AgentDemoProgress | demo_progress} messages of one upload.
|
|
246
|
+
*/
|
|
247
|
+
export const DEMO_PROGRESS_INTERVAL_MS = 1_000;
|
|
248
|
+
/** Milliseconds a demo upload may send nothing before it is aborted and retried. */
|
|
249
|
+
export const DEMO_STALL_TIMEOUT_MS = 2 * 60 * 1000;
|
|
250
|
+
/**
|
|
251
|
+
* Milliseconds before the first retry after a failed demo upload. Each further failure doubles the
|
|
252
|
+
* wait.
|
|
253
|
+
*/
|
|
254
|
+
export const DEMO_RETRY_MIN_MS = 30 * 1000;
|
|
255
|
+
/** Milliseconds of the longest wait between demo upload retries. */
|
|
256
|
+
export const DEMO_RETRY_MAX_MS = 15 * 60 * 1000;
|
|
257
|
+
/**
|
|
258
|
+
* Milliseconds demo uploads may keep failing before they stop. They start again when a new demo is
|
|
259
|
+
* recorded, the target changes or the agent restarts.
|
|
260
|
+
*/
|
|
261
|
+
export const DEMO_OUTAGE_LIMIT_MS = 24 * 60 * 60 * 1000;
|
|
262
|
+
/**
|
|
263
|
+
* Gigabytes of free disk a CS2 update needs. steamcmd stages a whole new copy of every changed file
|
|
264
|
+
* before it swaps them in, and a content drop changes many gigabyte-sized VPKs together, so 6 GB
|
|
265
|
+
* free was not enough for one.
|
|
266
|
+
*/
|
|
267
|
+
export const CS2_UPDATE_MIN_FREE_GB = 20;
|
|
268
|
+
/**
|
|
269
|
+
* Gigabytes of free disk a CS2 reinstall needs, because a reinstall downloads the entire game. The
|
|
270
|
+
* space is checked after the wipe.
|
|
271
|
+
*/
|
|
272
|
+
export const CS2_REINSTALL_MIN_FREE_GB = 70;
|
|
273
|
+
/**
|
|
274
|
+
* Gigabytes of disk a node needs beyond its operating system: about 70 for the CS2 installation,
|
|
275
|
+
* and the rest for an update to stage changed files in. A controller may warn about less.
|
|
276
|
+
*/
|
|
277
|
+
export const NODE_MIN_DISK_GB = 90;
|