diff --git a/MSCO.Server.csproj b/MSCO.Server.csproj
index 3f2f464..3e586e3 100644
--- a/MSCO.Server.csproj
+++ b/MSCO.Server.csproj
@@ -7,6 +7,9 @@
enable
MSCO.Server
MSCO.Server
+ 2.6.0
+ MBM
+ MSCO Dedicated Server
true
logo.ico
diff --git a/Program.cs b/Program.cs
index 78f7f55..31d7df8 100644
--- a/Program.cs
+++ b/Program.cs
@@ -65,7 +65,7 @@ if (Array.IndexOf(args, "--import-save") >= 0)
if (File.Exists(world)) File.Delete(world);
}
-// Lizenzschlüssel prüfen (wie beim FiveM-Keymaster)
+// Lizenzschlüssel beim Keymaster prüfen
if (string.IsNullOrWhiteSpace(config.LicenseKey))
{
GameServer.Log("No license key set. Get one at " + MasterServer.Url + "/keys and put it into server.json.");
diff --git a/Protocol.cs b/Protocol.cs
index c787696..7035735 100644
--- a/Protocol.cs
+++ b/Protocol.cs
@@ -74,7 +74,8 @@ public static class Wire
/// TCP-Verbindung zu einem Spieler, gleiches Format wie in der Mod: [int Länge][byte Typ][Payload].
public sealed class Connection
{
- const int MaxFrame = 32 * 1024 * 1024;
+ // Spieler schicken nur kleine Pakete (Positionen, Aktionen); der Spielstand geht nur vom Server zum Spieler
+ const int MaxFrame = 1024 * 1024;
public bool IsClosed { get; private set; }
public string CloseReason { get; private set; }
diff --git a/README.md b/README.md
index 1060599..b0c68d6 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ und Windows.
## Voraussetzungen
-- Ein **Lizenzschlüssel** von https://msco.mbm.wtf/keys (Login mit Steam, wie der FiveM-Keymaster).
+- Ein **Lizenzschlüssel** von https://msco.mbm.wtf/keys (Login mit Steam).
Der Server prüft ihn beim Start und meldet sich danach regelmäßig beim Master für die Serverliste.
- Ein My-Summer-Car-Spielstand als Startwelt.
- Port **7777/TCP** im Router/Firewall freigegeben (sonst wird der Server nicht gelistet).
@@ -122,11 +122,50 @@ Der Chat landet auch im Server-Log.
Beim Beitreten: Spielstand laden → Objekte erfassen → Weltzustand anwenden → an der letzten eigenen Position weiterspielen.
+## Betrieb in Produktion
+
+**Checkliste**
+- [ ] `LicenseKey` gesetzt, Server startet ohne Fehler (`docker logs msco-server`)
+- [ ] Nur Port **7777/TCP** nach außen offen; der Server braucht sonst nur ausgehend HTTPS zu `msco.mbm.wtf`
+- [ ] Log meldet keine Warnung „NOT reachable from the internet“ (sonst erscheint der Server nicht in der Liste)
+- [ ] Steam-Server (`RequireSteam: true`): `SteamWebApiKey` eintragen, damit Anmeldetickets wirklich bei Steam geprüft werden
+- [ ] Mindestens eine SteamID in `Admins`
+- [ ] Backups von `data/` eingerichtet (siehe unten)
+
+**Neustarts:** Docker startet den Server nach einem Absturz neu (`restart: on-failure`). Bei Konfigurationsfehlern
+(kein/ungültiger Schlüssel, kein Spielstand) beendet er sich mit Code 1 und wird nach 3 Versuchen nicht mehr
+gestartet – dann ins Log schauen. `docker compose stop` / `systemctl stop` speichern vorher den Weltzustand.
+
+**Logs:** werden von Docker auf 3 × 10 MB begrenzt. Chat, Beitritte, Kicks/Bans und Admin-Aktionen stehen darin.
+
+**Backups:** Alles Wichtige liegt in `data/` (`server.json`, `save/`, `world.json`). Den Server dafür nicht
+anhalten – der Weltzustand wird atomar geschrieben. Beispiel (täglich per cron):
+
+```bash
+tar czf /backup/msco-$(date +%F).tar.gz -C /pfad/zu/MSCO-Dedicated-Server data
+```
+
+**Updates:** Server und Mod müssen dieselbe Version haben. Nach einem neuen Release:
+
+```bash
+git pull
+docker compose up -d --build
+```
+
+Spieler mit einer anderen Mod-Version bekommen beim Verbinden eine Warnung.
+
+**Weltzustand zurücksetzen:** Wird `world.json` mit der Zeit sehr groß oder macht Probleme beim Beitreten,
+einen aktuellen Spielstand nach `data/save/` legen und `msco reset-world` ausführen.
+
## Sicherheit
- Die Adresse des Masters (`https://msco.mbm.wtf`) ist fest eingebaut, damit niemand die Schlüsselprüfung mit
einem eigenen Master umgehen kann. Nur Debug-Builds akzeptieren `MSCO_MASTER_URL`.
- `data/` enthält Lizenzschlüssel und API-Keys – nicht veröffentlichen (steht in `.gitignore`).
+- Der Container läuft nicht als root. Spielerdaten (Nachrichten, Objektzustände) werden in der Größe begrenzt,
+ unbekannte oder zu große Nachrichten verworfen.
+- Gebannte Spieler werden über ihre (bei Steam geprüfte) SteamID erkannt. Auf Non-Steam-Servern gibt es keine
+ verlässliche Identität – dort sind Bans nur über den Namen möglich.
## Lizenz
diff --git a/WorldState.cs b/WorldState.cs
index 8d6cdc8..e278418 100644
--- a/WorldState.cs
+++ b/WorldState.cs
@@ -79,7 +79,11 @@ public sealed class WorldState
void ReadPose(BinaryReader r)
{
int id = r.ReadInt32();
- if (!Objects.TryGetValue(id, out var pose)) Objects[id] = pose = new ObjectPose();
+ if (!Objects.TryGetValue(id, out var pose))
+ {
+ pose = new ObjectPose(); // bei voller Liste nur mitlesen, nicht speichern
+ if (Objects.Count < MaxObjects) Objects[id] = pose;
+ }
for (int k = 0; k < 3; k++) pose.P[k] = r.ReadSingle();
for (int k = 0; k < 4; k++) pose.R[k] = r.ReadSingle();
r.ReadSingle(); r.ReadSingle(); r.ReadSingle(); // Geschwindigkeit wird nicht gespeichert
@@ -113,13 +117,20 @@ public sealed class WorldState
var all = new byte[value.Length + 1];
all[0] = type;
value.CopyTo(all, 1);
+ if (Vars.Count >= MaxVars && !Vars.ContainsKey(key)) continue;
Vars[key] = Convert.ToBase64String(all);
}
Dirty = true;
}
+ // Obergrenzen, damit ein fehlerhafter oder manipulierter Client world.json nicht endlos aufbläht
+ public const int MaxEvents = 50_000;
+ public const int MaxVars = 50_000;
+ public const int MaxObjects = 20_000;
+
public void AddFsmEvent(byte[] payload)
{
+ if (Events.Count >= MaxEvents) return;
Events.Add(Convert.ToBase64String(payload));
Dirty = true;
}
diff --git a/docker-compose.yml b/docker-compose.yml
index 5ed4a64..b6f0779 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -21,6 +21,11 @@ services:
stdin_open: true # Server-Konsole per docker attach
tty: true
stop_grace_period: 30s
+ logging: # Logs begrenzen, damit sie im Dauerbetrieb nicht die Platte füllen
+ driver: json-file
+ options:
+ max-size: "10m"
+ max-file: "3"
ports:
- "7777:7777/tcp"
volumes: