Docker-Adapter: LAN-Modus ohne GSLT, Container starten nacheinander
Ohne CS2_GSLT starten Match-Container mit CS2_LAN=1. Neue Container warten, bis der vorige seinen SteamCMD-Update-Check abgeschlossen hat, damit nie zwei gleichzeitig das gemeinsame Volume aktualisieren. Anleitung: Docker per SSH (DOCKER_HOST), eindeutige Passwort-Beispiele mit Warnung vor Sonderzeichen (Startskript nutzt eval). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8996294d7e
commit
59d6dc7177
5 files changed
+178
-22
No files matched your search
+1
-1
@@ -39,7 +39,7 @@ DEMO_START_SECONDS=20
|
|||||||
DEMO_FAIL_FIRST_CONFIGURE=false
|
DEMO_FAIL_FIRST_CONFIGURE=false
|
||||||
|
|
||||||
# --- Docker-Adapter (SERVER_ADAPTER=docker) ---
|
# --- Docker-Adapter (SERVER_ADAPTER=docker) ---
|
||||||
# Pflicht: Game Server Login Token für App-ID 730, https://steamcommunity.com/dev/managegameservers
|
# Game Server Login Token für App-ID 730 (https://steamcommunity.com/dev/managegameservers); leer = LAN-Modus (sv_lan 1)
|
||||||
CS2_GSLT=
|
CS2_GSLT=
|
||||||
# Pflicht: Adresse, unter der Spieler den Docker-Host erreichen (IP oder DNS-Name)
|
# Pflicht: Adresse, unter der Spieler den Docker-Host erreichen (IP oder DNS-Name)
|
||||||
CS2_PUBLIC_HOST=
|
CS2_PUBLIC_HOST=
|
||||||
|
|||||||
@@ -331,7 +331,7 @@ Alle Variablen mit Standardwerten stehen in [`.env.example`](.env.example). Die
|
|||||||
| `SERVER_IDLE_TIMEOUT_MINUTES` | `30` | Frist bis zum automatischen Herunterfahren |
|
| `SERVER_IDLE_TIMEOUT_MINUTES` | `30` | Frist bis zum automatischen Herunterfahren |
|
||||||
| `SERVER_MAX_LIFETIME_HOURS` | `8` | harte Obergrenze pro Server |
|
| `SERVER_MAX_LIFETIME_HOURS` | `8` | harte Obergrenze pro Server |
|
||||||
| `CS2_TV_DELAY` | `90` | CSTV-Verzögerung in Sekunden |
|
| `CS2_TV_DELAY` | `90` | CSTV-Verzögerung in Sekunden |
|
||||||
| `CS2_GSLT` | – | **Pflicht bei Docker:** Game Server Login Token |
|
| `CS2_GSLT` | leer | Docker: Game Server Login Token; leer = LAN-Modus (`sv_lan 1`) |
|
||||||
| `CS2_PUBLIC_HOST` | – | **Pflicht bei Docker:** öffentliche Adresse |
|
| `CS2_PUBLIC_HOST` | – | **Pflicht bei Docker:** öffentliche Adresse |
|
||||||
| `CS2_STATIC_SERVERS` | – | **Pflicht bei Static:** Serverliste |
|
| `CS2_STATIC_SERVERS` | – | **Pflicht bei Static:** Serverliste |
|
||||||
| `DEMO_FAIL_FIRST_CONFIGURE` | `false` | Demo: Konfigurationsfehler simulieren |
|
| `DEMO_FAIL_FIRST_CONFIGURE` | `false` | Demo: Konfigurationsfehler simulieren |
|
||||||
@@ -343,7 +343,7 @@ Alle Variablen mit Standardwerten stehen in [`.env.example`](.env.example). Die
|
|||||||
|
|
||||||
Benötigte Zugangsdaten für den Echtbetrieb:
|
Benötigte Zugangsdaten für den Echtbetrieb:
|
||||||
|
|
||||||
- **Docker:** GSLT, Zugriff auf den Docker-Daemon, öffentliche IP bzw. Portfreigaben
|
- **Docker:** Zugriff auf den Docker-Daemon (lokal oder per `DOCKER_HOST=ssh://…`), Portfreigaben, für Internet-Betrieb ein GSLT (im LAN nicht nötig)
|
||||||
- **Static:** RCON-Passwörter der Server
|
- **Static:** RCON-Passwörter der Server
|
||||||
|
|
||||||
RCON-Passwörter für Docker-Container erzeugt das Backend selbst.
|
RCON-Passwörter für Docker-Container erzeugt das Backend selbst.
|
||||||
|
|||||||
+48
-12
@@ -83,12 +83,16 @@ volumes:
|
|||||||
|
|
||||||
5. Darunter bei **Environment variables** auf *+ Add an environment variable* klicken und anlegen:
|
5. Darunter bei **Environment variables** auf *+ Add an environment variable* klicken und anlegen:
|
||||||
|
|
||||||
| Name | Wert |
|
| Name | Beispielwert | Bedeutung |
|
||||||
|---|---|
|
|---|---|---|
|
||||||
| `SRCDS_TOKEN` | dein GSLT |
|
| `SRCDS_TOKEN` | `A1B2C3D4E5F6…` | dein GSLT; leer lassen für LAN-Modus |
|
||||||
| `CS2_RCONPW` | langes, zufälliges Passwort (nur Buchstaben und Ziffern) |
|
| `CS2_RCONPW` | `Kx7fQ2mPz9Lw4tNb` | RCON-Passwort, lang und zufällig |
|
||||||
| `CS2_PW` | beliebiges Serverpasswort |
|
| `CS2_PW` | `Lan2026` | Serverpasswort |
|
||||||
| `TV_PW` | Passwort für CSTV |
|
| `TV_PW` | `Cstv2026` | Passwort für CSTV |
|
||||||
|
|
||||||
|
> **Nur Buchstaben und Ziffern verwenden.** Das Startskript des Images setzt die Werte ungeschützt in einen
|
||||||
|
> Shell-Befehl ein. Leerzeichen, Klammern, Kommas, Anführungszeichen oder `$` führen dazu, dass der Server
|
||||||
|
> nicht startet (`syntax error near unexpected token`) und Docker ihn endlos neu startet.
|
||||||
|
|
||||||
So stehen die Zugangsdaten nicht im Stack-Text selbst.
|
So stehen die Zugangsdaten nicht im Stack-Text selbst.
|
||||||
|
|
||||||
@@ -208,19 +212,51 @@ CS2_STATIC_SERVERS=192.168.178.50:27015:PASSWORT:27020,192.168.178.50:27025:PASS
|
|||||||
|
|
||||||
Statt dauerhaft laufender Server kann CS2 Mapveto pro Match einen eigenen Container starten und danach wieder entfernen (`SERVER_ADAPTER=docker`). Damit auch dabei nichts neu geladen wird, nutzt der Adapter das Volume **`cs2-mapveto-gamefiles`**. Das ist genau das Volume, das der Stack oben angelegt und befüllt hat.
|
Statt dauerhaft laufender Server kann CS2 Mapveto pro Match einen eigenen Container starten und danach wieder entfernen (`SERVER_ADAPTER=docker`). Damit auch dabei nichts neu geladen wird, nutzt der Adapter das Volume **`cs2-mapveto-gamefiles`**. Das ist genau das Volume, das der Stack oben angelegt und befüllt hat.
|
||||||
|
|
||||||
|
### 1. Docker-Zugriff von CS2 Mapveto auf den Docker-Host
|
||||||
|
|
||||||
|
Läuft CS2 Mapveto auf einem anderen Rechner als Docker (z. B. Windows-PC → Docker-Host im LAN), steuert es Docker per SSH:
|
||||||
|
|
||||||
|
1. Auf dem Docker-Host den Benutzer in die Docker-Gruppe aufnehmen (einmalig, danach neu anmelden):
|
||||||
|
```bash
|
||||||
|
sudo usermod -aG docker server
|
||||||
|
```
|
||||||
|
2. Auf dem Rechner mit CS2 Mapveto einen SSH-Eintrag anlegen (Windows: `C:\Users\<Name>\.ssh\config`) und die Anmeldung per Schlüssel einrichten:
|
||||||
|
```
|
||||||
|
Host cs2docker
|
||||||
|
HostName 192.168.178.111
|
||||||
|
User server
|
||||||
|
```
|
||||||
|
3. Testen, **auf dem Rechner mit CS2 Mapveto**, nicht auf dem Docker-Host:
|
||||||
|
```powershell
|
||||||
|
docker -H ssh://cs2docker ps
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `.env` von CS2 Mapveto
|
||||||
|
|
||||||
```env
|
```env
|
||||||
SERVER_ADAPTER=docker
|
SERVER_ADAPTER=docker
|
||||||
CS2_GSLT=dein_gslt
|
DOCKER_HOST=ssh://cs2docker
|
||||||
CS2_PUBLIC_HOST=192.168.178.50
|
# leer = LAN-Modus (sv_lan 1), sonst ein Game Server Login Token
|
||||||
|
CS2_GSLT=
|
||||||
|
# Adresse, unter der Spieler den Docker-Host erreichen
|
||||||
|
CS2_PUBLIC_HOST=192.168.178.111
|
||||||
|
# Adresse, unter der CS2 Mapveto RCON erreicht – bei getrennten Rechnern ebenfalls der Docker-Host
|
||||||
|
CS2_RCON_HOST=192.168.178.111
|
||||||
CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles
|
CS2_DOCKER_VOLUME=cs2-mapveto-gamefiles
|
||||||
CS2_DOCKER_IMAGE=joedwards32/cs2:latest
|
CS2_DOCKER_IMAGE=joedwards32/cs2:latest
|
||||||
|
# zwei Ports pro Match (Spiel TCP+UDP, CSTV UDP), in der Firewall freigeben
|
||||||
|
CS2_PORT_RANGE_START=27100
|
||||||
|
CS2_PORT_RANGE_END=27139
|
||||||
|
# Container müssen CS2 Mapveto für die Live-Ergebnisse erreichen
|
||||||
|
LOG_RECEIVER_URL=http://192.168.178.31:3000
|
||||||
```
|
```
|
||||||
|
|
||||||
Wichtig dabei:
|
Wichtig dabei:
|
||||||
|
|
||||||
- **Den Stack-Server vorher stoppen**, damit nicht zwei Container gleichzeitig dasselbe Volume aktualisieren. Der Stack dient dann nur zum einmaligen Befüllen und für Updates.
|
- **Den Stack-Server stoppen**, sobald die Spieldateien geladen sind. Der Stack dient nur zum einmaligen Befüllen und für Updates. Das Volume bleibt beim Stoppen und Löschen des Stacks erhalten.
|
||||||
- Für gleichzeitige Matches gilt dasselbe Problem: Alle Match-Container teilen sich das Volume und prüfen beim Start auf Updates. Das ist unkritisch, solange kein Valve-Update ansteht. Kurz nach einem CS2-Patch nur **ein** Match auf einmal starten.
|
- **Starts laufen nacheinander:** Jeder Match-Container prüft beim Start per SteamCMD auf Updates. Damit nie zwei Container gleichzeitig das Volume aktualisieren, startet CS2 Mapveto den nächsten erst, wenn der vorige seinen Update-Check abgeschlossen hat. Nach einem CS2-Patch dauert der erste Start dadurch länger, die übrigen warten kurz.
|
||||||
- Das Backend braucht Zugriff auf den Docker-Daemon (es läuft auf demselben Host bzw. hat `/var/run/docker.sock`).
|
- **Ein GSLT gilt nur für einen laufenden Server.** Ohne Token (LAN-Modus) gibt es diese Grenze nicht.
|
||||||
|
- **Hardware:** Ein CS2-Server braucht etwa 1–2 CPU-Kerne und 2–3 GB RAM. 6 Kerne und 16 GB reichen für etwa 2–3 gleichzeitige Matches.
|
||||||
|
|
||||||
Für eine LAN-Party mit festen Servern ist der **Static-Adapter mit Stack aus Schritt 1** die einfachere und robustere Wahl.
|
Für eine LAN-Party mit festen Servern ist der **Static-Adapter mit Stack aus Schritt 1** die einfachere und robustere Wahl.
|
||||||
|
|
||||||
@@ -228,7 +264,7 @@ Für eine LAN-Party mit festen Servern ist der **Static-Adapter mit Stack aus Sc
|
|||||||
|
|
||||||
## LAN-Betrieb ohne GSLT
|
## LAN-Betrieb ohne GSLT
|
||||||
|
|
||||||
Für reine LAN-Veranstaltungen kann `CS2_LAN=1` gesetzt werden (`sv_lan 1`). Der Server ist dann nur im lokalen Netz erreichbar und braucht kein Token. Spieler müssen im selben Netz sein. Für Zuschauer über das Internet oder externe Teams ist ein GSLT nötig.
|
Für reine LAN-Veranstaltungen kann `CS2_LAN=1` gesetzt werden (`sv_lan 1`). Beim Docker-Adapter genügt es, `CS2_GSLT` leer zu lassen. Der Server ist dann nur im lokalen Netz erreichbar und braucht kein Token. Spieler müssen im selben Netz sein. Für Zuschauer über das Internet oder externe Teams ist ein GSLT nötig.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -4,26 +4,39 @@ import { configureViaRcon, rconStatus } from './rconConfigure.js';
|
|||||||
import { runRcon } from './rcon.js';
|
import { runRcon } from './rcon.js';
|
||||||
|
|
||||||
const exec = promisify(execFile);
|
const exec = promisify(execFile);
|
||||||
|
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
||||||
|
|
||||||
|
// Diese Zeile schreibt das Image, sobald SteamCMD fertig ist und der Spielserver startet.
|
||||||
|
const UPDATE_DONE_MARKER = 'Starting CS2 Dedicated Server';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Startet pro Match einen eigenen CS2-Container (Standard-Image: joedwards32/cs2, Nachfolger von cm2network/cs2).
|
* Startet pro Match einen eigenen CS2-Container (Standard-Image: joedwards32/cs2, Nachfolger von cm2network/cs2).
|
||||||
* Benötigt Docker auf dem Backend-Host, ein Game Server Login Token (CS2_GSLT)
|
* Benötigt Docker (lokal oder per DOCKER_HOST, z. B. ssh://…) und die Adresse, unter der Spieler
|
||||||
* und eine öffentlich erreichbare Adresse (CS2_PUBLIC_HOST).
|
* den Docker-Host erreichen (CS2_PUBLIC_HOST).
|
||||||
|
*
|
||||||
|
* Ohne CS2_GSLT laufen die Server im LAN-Modus (sv_lan 1): nur aus dem lokalen Netz erreichbar.
|
||||||
*
|
*
|
||||||
* Pro Match werden zwei aufeinanderfolgende Ports belegt: Spielport (TCP+UDP, auch RCON)
|
* Pro Match werden zwei aufeinanderfolgende Ports belegt: Spielport (TCP+UDP, auch RCON)
|
||||||
* und CSTV-Port (UDP).
|
* und CSTV-Port (UDP).
|
||||||
|
*
|
||||||
|
* Alle Container teilen sich das Volume mit den Spieldateien. Jeder prüft beim Start per SteamCMD
|
||||||
|
* auf Updates; damit nie zwei gleichzeitig schreiben, starten Container nacheinander: der nächste
|
||||||
|
* erst, wenn der vorige seinen Update-Check abgeschlossen hat.
|
||||||
*/
|
*/
|
||||||
export class DockerServerAdapter {
|
export class DockerServerAdapter {
|
||||||
constructor(options, { getUsedPorts }) {
|
constructor(options, { getUsedPorts, runDocker } = {}) {
|
||||||
this.name = 'docker';
|
this.name = 'docker';
|
||||||
this.simulated = false;
|
this.simulated = false;
|
||||||
this.options = options;
|
this.options = options;
|
||||||
this.getUsedPorts = getUsedPorts;
|
this.getUsedPorts = getUsedPorts;
|
||||||
|
this.runDocker = runDocker; // nur für Tests
|
||||||
|
this.lanMode = !options.gslt;
|
||||||
|
this.startQueue = Promise.resolve();
|
||||||
if (!options.host) throw new Error('SERVER_ADAPTER=docker benötigt CS2_PUBLIC_HOST');
|
if (!options.host) throw new Error('SERVER_ADAPTER=docker benötigt CS2_PUBLIC_HOST');
|
||||||
if (!options.gslt) throw new Error('SERVER_ADAPTER=docker benötigt CS2_GSLT (Game Server Login Token)');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async docker(args, timeout = 60_000) {
|
async docker(args, timeout = 60_000) {
|
||||||
|
if (this.runDocker) return this.runDocker(args);
|
||||||
const { stdout } = await exec(this.options.bin, args, { timeout, maxBuffer: 4 * 1024 * 1024 });
|
const { stdout } = await exec(this.options.bin, args, { timeout, maxBuffer: 4 * 1024 * 1024 });
|
||||||
return stdout.trim();
|
return stdout.trim();
|
||||||
}
|
}
|
||||||
@@ -36,12 +49,45 @@ export class DockerServerAdapter {
|
|||||||
throw new Error('Kein freier Port im Bereich CS2_PORT_RANGE_START–CS2_PORT_RANGE_END');
|
throw new Error('Kein freier Port im Bereich CS2_PORT_RANGE_START–CS2_PORT_RANGE_END');
|
||||||
}
|
}
|
||||||
|
|
||||||
async start({ matchId, mode, secrets }) {
|
async start(args) {
|
||||||
|
// In die Warteschlange: erst starten, wenn der vorige Container seinen Update-Check hinter sich hat.
|
||||||
|
const previous = this.startQueue;
|
||||||
|
let release;
|
||||||
|
this.startQueue = new Promise((resolve) => { release = resolve; });
|
||||||
|
await previous;
|
||||||
|
let result;
|
||||||
|
try {
|
||||||
|
result = await this.launch(args);
|
||||||
|
} catch (err) {
|
||||||
|
release();
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
this.waitForUpdateCheck(result.handle.container).finally(release);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Wartet, bis der Container SteamCMD abgeschlossen hat, beendet ist oder die Startzeit abläuft. */
|
||||||
|
async waitForUpdateCheck(container, { pollMs = 3000 } = {}) {
|
||||||
|
const deadline = Date.now() + this.options.startTimeoutMinutes * 60_000;
|
||||||
|
while (Date.now() < deadline) {
|
||||||
|
try {
|
||||||
|
const state = await this.docker(['inspect', '-f', '{{.State.Status}}', container], 15_000);
|
||||||
|
if (state !== 'running' && state !== 'created') return;
|
||||||
|
const logs = await this.docker(['logs', '--tail', '200', container], 15_000);
|
||||||
|
if (logs.includes(UPDATE_DONE_MARKER)) return;
|
||||||
|
} catch {
|
||||||
|
return; // Container weg – nicht ewig blockieren
|
||||||
|
}
|
||||||
|
await sleep(pollMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async launch({ matchId, mode, secrets }) {
|
||||||
const port = this.allocatePort();
|
const port = this.allocatePort();
|
||||||
const tvPort = port + 1;
|
const tvPort = port + 1;
|
||||||
const container = `cs2mv-${matchId.replace(/[^a-zA-Z0-9_-]/g, '')}`.toLowerCase();
|
const container = `cs2mv-${matchId.replace(/[^a-zA-Z0-9_-]/g, '')}`.toLowerCase();
|
||||||
const env = {
|
const env = {
|
||||||
SRCDS_TOKEN: this.options.gslt,
|
...(this.lanMode ? { CS2_LAN: '1' } : { SRCDS_TOKEN: this.options.gslt, CS2_LAN: '0' }),
|
||||||
CS2_SERVERNAME: `CS2 Mapveto ${matchId}`,
|
CS2_SERVERNAME: `CS2 Mapveto ${matchId}`,
|
||||||
CS2_PORT: String(port),
|
CS2_PORT: String(port),
|
||||||
CS2_RCONPW: secrets.rconPassword,
|
CS2_RCONPW: secrets.rconPassword,
|
||||||
@@ -104,7 +150,7 @@ export class DockerServerAdapter {
|
|||||||
if (minutes >= this.options.startTimeoutMinutes) {
|
if (minutes >= this.options.startTimeoutMinutes) {
|
||||||
return { state: 'failed', message: `Server nach ${minutes} Minuten nicht per RCON erreichbar` };
|
return { state: 'failed', message: `Server nach ${minutes} Minuten nicht per RCON erreichbar` };
|
||||||
}
|
}
|
||||||
return { state: 'starting', message: 'Container läuft, Spielserver lädt (Download/Update möglich)' };
|
return { state: 'starting', message: 'Container läuft, Spielserver lädt (Update-Prüfung oder Start)' };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { test } from 'node:test';
|
||||||
|
import assert from 'node:assert/strict';
|
||||||
|
import { DockerServerAdapter } from '../src/servers/dockerAdapter.js';
|
||||||
|
|
||||||
|
const options = {
|
||||||
|
bin: 'docker', image: 'joedwards32/cs2:latest', host: '192.168.178.111', rconHost: '192.168.178.111',
|
||||||
|
portRangeStart: 27100, portRangeEnd: 27139, gslt: '', volume: 'cs2-mapveto-gamefiles', extraArgs: '', startTimeoutMinutes: 1,
|
||||||
|
};
|
||||||
|
const secrets = { rconPassword: 'Rcon123', serverPassword: 'Pw123', tvPassword: 'Tv123' };
|
||||||
|
|
||||||
|
/** Simuliertes Docker: merkt sich Aufrufe; Container gelten als "Update fertig", sobald freigegeben. */
|
||||||
|
function fakeDocker() {
|
||||||
|
const calls = [];
|
||||||
|
const updated = new Set();
|
||||||
|
const run = async (args) => {
|
||||||
|
calls.push(args);
|
||||||
|
const [cmd] = args;
|
||||||
|
const name = args.at(-1);
|
||||||
|
if (cmd === 'inspect') return 'running';
|
||||||
|
if (cmd === 'logs') return updated.has(name) ? 'Success! App \'730\' already up to date.\nStarting CS2 Dedicated Server' : 'Checking for available updates...';
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
return { run, calls, finishUpdate: (name) => updated.add(name) };
|
||||||
|
}
|
||||||
|
|
||||||
|
const envOf = (args) => Object.fromEntries(args.flatMap((a, i) => (args[i - 1] === '-e' ? [a.split(/=(.*)/s).slice(0, 2)] : [])));
|
||||||
|
|
||||||
|
test('Ohne GSLT startet der Container im LAN-Modus, mit GSLT mit Token', async () => {
|
||||||
|
const lan = fakeDocker();
|
||||||
|
const a = new DockerServerAdapter(options, { getUsedPorts: () => new Set(), runDocker: lan.run });
|
||||||
|
const r = await a.start({ matchId: 'abc_DEF-1', mode: 'wingman', secrets });
|
||||||
|
lan.finishUpdate(r.handle.container);
|
||||||
|
const runArgs = lan.calls.find((c) => c[0] === 'run');
|
||||||
|
const env = envOf(runArgs);
|
||||||
|
assert.equal(env.CS2_LAN, '1');
|
||||||
|
assert.equal(env.SRCDS_TOKEN, undefined);
|
||||||
|
assert.equal(env.CS2_GAMEMODE, '2');
|
||||||
|
assert.equal(env.CS2_PORT, '27100');
|
||||||
|
assert.equal(env.TV_PORT, '27101');
|
||||||
|
assert.ok(runArgs.includes('cs2-mapveto-gamefiles:/home/steam/cs2-dedicated'));
|
||||||
|
assert.equal(r.host, '192.168.178.111');
|
||||||
|
|
||||||
|
const tok = fakeDocker();
|
||||||
|
const b = new DockerServerAdapter({ ...options, gslt: 'TOKEN1' }, { getUsedPorts: () => new Set(), runDocker: tok.run });
|
||||||
|
const r2 = await b.start({ matchId: 'x', mode: 'competitive', secrets });
|
||||||
|
tok.finishUpdate(r2.handle.container);
|
||||||
|
const env2 = envOf(tok.calls.find((c) => c[0] === 'run'));
|
||||||
|
assert.equal(env2.SRCDS_TOKEN, 'TOKEN1');
|
||||||
|
assert.equal(env2.CS2_LAN, '0');
|
||||||
|
});
|
||||||
|
|
||||||
|
test('Zweiter Container startet erst, wenn der erste seinen Update-Check abgeschlossen hat', async () => {
|
||||||
|
const d = fakeDocker();
|
||||||
|
const used = new Set();
|
||||||
|
const a = new DockerServerAdapter(options, { getUsedPorts: () => used, runDocker: d.run });
|
||||||
|
// Wartezeit zwischen Prüfungen für den Test verkürzen
|
||||||
|
const original = a.waitForUpdateCheck.bind(a);
|
||||||
|
a.waitForUpdateCheck = (name) => original(name, { pollMs: 10 });
|
||||||
|
|
||||||
|
const first = await a.start({ matchId: 'm1', mode: 'competitive', secrets });
|
||||||
|
used.add(first.port).add(first.tvPort);
|
||||||
|
let secondDone = false;
|
||||||
|
const second = a.start({ matchId: 'm2', mode: 'competitive', secrets }).then((r) => { secondDone = true; return r; });
|
||||||
|
|
||||||
|
await new Promise((r) => setTimeout(r, 60));
|
||||||
|
assert.equal(secondDone, false, 'zweiter Start wartet noch');
|
||||||
|
assert.equal(d.calls.filter((c) => c[0] === 'run').length, 1);
|
||||||
|
|
||||||
|
d.finishUpdate(first.handle.container);
|
||||||
|
const r2 = await second;
|
||||||
|
assert.equal(d.calls.filter((c) => c[0] === 'run').length, 2);
|
||||||
|
assert.equal(r2.port, 27102, 'zweiter Container bekommt die nächsten Ports');
|
||||||
|
d.finishUpdate(r2.handle.container);
|
||||||
|
});
|
||||||
Reference in new issue
Block a user