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: