286 lines
16 KiB
Markdown
286 lines
16 KiB
Markdown
# MSCO Dedicated Server
|
||
|
||
Dedizierter Server für **MSCO – My Summer Car Online**. Er hält einen gemeinsamen Spielstand, speichert alles,
|
||
was die Spieler verändern, und gibt es an später Beitretende weiter. .NET 10, läuft unter Linux (Docker oder systemd)
|
||
und Windows.
|
||
|
||
- Mod: [MSCO](https://git.mbm.wtf/MrMaho/MSCO)
|
||
- Keymaster & Serverliste: [MSCO-Keymaster](https://git.mbm.wtf/MrMaho/MSCO-Keymaster) – https://msco.mbm.wtf
|
||
|
||

|
||
|
||
## Voraussetzungen
|
||
|
||
- 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).
|
||
- Server und Mod müssen dieselbe Version haben (sonst bekommen Spieler eine Warnung).
|
||
|
||
## Mit Docker (empfohlen)
|
||
|
||
```bash
|
||
git clone https://git.mbm.wtf/MrMaho/MSCO-Dedicated-Server.git
|
||
cd MSCO-Dedicated-Server
|
||
docker compose up -d --build # erster Start legt data/server.json an und beendet sich
|
||
```
|
||
|
||
1. In `data/server.json` mindestens `LicenseKey` eintragen.
|
||
2. Die Spielstand-Dateien nach `data/save/` kopieren (siehe unten).
|
||
3. `docker compose up -d`
|
||
|
||
| Aktion | Befehl |
|
||
|---|---|
|
||
| Konsole | `docker attach msco-server` – verlassen mit **Strg+P, dann Strg+Q** (Strg+C beendet den Server) |
|
||
| Einzelner Befehl | `docker exec msco-server msco <befehl>`, z.B. `msco players` |
|
||
| Logs | `docker logs -f msco-server` |
|
||
| Stoppen | `docker compose stop` – speichert vorher den Weltzustand und meldet den Server ab |
|
||
| Update | `git pull && docker compose up -d --build` |
|
||
|
||
Anderer Port: in `data/server.json` **und** in `docker-compose.yml` ändern.
|
||
|
||
### Linux: „Permission denied“ in `data/`
|
||
|
||
Der Container läuft als Benutzer-ID **1000** (der erste Linux-Benutzer), damit dir die Dateien in `data/` gehören.
|
||
Hat dein Benutzer eine andere ID (`id -u` / `id -g`), trag sie in eine `.env` neben der `docker-compose.yml` ein:
|
||
|
||
```bash
|
||
echo "MSCO_UID=$(id -u)" > .env
|
||
echo "MSCO_GID=$(id -g)" >> .env
|
||
```
|
||
|
||
Wurden Dateien schon von einer älteren Version als root angelegt (typisch: „Permission denied“ beim Hochladen
|
||
per SFTP oder bei `mkdir` in `data/`), einmal zurückholen:
|
||
|
||
```bash
|
||
docker compose down
|
||
sudo chown -R $(id -u):$(id -g) data
|
||
docker compose up -d --build
|
||
```
|
||
|
||
## Ohne Docker
|
||
|
||
**Linux (systemd):** .NET 10 Runtime installieren (`sudo apt install dotnet-runtime-10.0`), dann
|
||
`dotnet publish -c Release -o /opt/msco-server` und [`deploy/msco-server.service`](deploy/msco-server.service)
|
||
einrichten (Anleitung oben in der Datei). Daten liegen in `/var/lib/msco-server`.
|
||
Für eine Konsole den Server stattdessen in `screen`/`tmux` starten.
|
||
|
||
**Windows:** `dotnet publish -c Release -o publish`, dann `publish\MSCO.Server.exe` starten.
|
||
|
||
Startparameter:
|
||
- `--data <ordner>` – Datenordner (Standard: Programmordner; Docker: `/data`)
|
||
- `--import-save [pfad]` – Spielstand übernehmen (ohne Pfad: der eigene Spielstand dieses PCs, auch Proton)
|
||
- `--cmd <befehl>` – Befehl an einen laufenden Server im selben Datenordner schicken
|
||
|
||
## Spielstand
|
||
|
||
In `data/save/` gehören diese Dateien aus dem My-Summer-Car-Spielstand:
|
||
`defaultES2File.txt`, `items.txt`, `meshsave.txt`, `graveyard.txt` (und `notepad.txt`, falls vorhanden).
|
||
|
||
- Windows: `%USERPROFILE%\AppData\LocalLow\Amistech\My Summer Car`
|
||
- Linux (Proton): `~/.steam/steam/steamapps/compatdata/516750/pfx/drive_c/users/steamuser/AppData/LocalLow/Amistech/My Summer Car`
|
||
|
||
### Spielstand hochladen (SFTP, z.B. WinSCP / FileZilla)
|
||
|
||
Die Dateien einfach nach `data/save/` im Server-Ordner hochladen, z.B. `/home/<benutzer>/MSCO-Dedicated-Server/data/save/`.
|
||
Das geht auch, während der Server läuft. Kommt dabei **„Permission denied“**, gehört der Ordner noch root
|
||
(siehe Abschnitt „Linux: Permission denied“ oben) – einmal `sudo chown -R $(id -u):$(id -g) data` ausführen.
|
||
Meldet der **Server** nach dem Hochladen `Cannot read the save game … Permission denied`, gehören die neuen
|
||
Dateien einem anderen Benutzer (z.B. mit einem anderen SFTP-Konto oder als root hochgeladen) oder deine
|
||
Benutzer-ID ist nicht 1000. Der Server nennt im Log seine Benutzer-ID und den passenden `chown`-Befehl.
|
||
|
||
### Neuen Spielstand übernehmen
|
||
|
||
Nach dem Hochladen eines **anderen** Spielstands die alten Änderungen verwerfen und die Dateien neu einlesen –
|
||
sonst passt der gespeicherte Weltzustand nicht zum neuen Spielstand:
|
||
|
||
```bash
|
||
docker exec msco-server msco reset-world
|
||
docker exec msco-server msco reload-save
|
||
```
|
||
|
||
Ohne Docker: die beiden Befehle in der Server-Konsole eingeben, oder den Server stoppen, `data/world.json` löschen
|
||
und neu starten.
|
||
|
||
## Einstellungen (`data/server.json`)
|
||
|
||
| Einstellung | Bedeutung |
|
||
|---|---|
|
||
| `LicenseKey` | Schlüssel von der Keymaster-Seite (Pflicht) |
|
||
| `Name`, `Description` | Anzeige im Server-Browser |
|
||
| `MaxPlayers`, `Port` | Spieler-Limit (max. 3 sinnvoll), Port |
|
||
| `Public` | `false` = nicht in der Liste, nur Direct Connect |
|
||
| `RequireSteam` | `true` = nur Spieler mit Steam; `false` = Non-Steam-Server (Spieler werden über den Namen erkannt) |
|
||
| `PublicAddress` | öffentliche IP/Domain für die Liste (nur hinter NAT/Proxy nötig) |
|
||
| `AutosaveSeconds` | wie oft der Weltzustand gespeichert wird |
|
||
| `SaveSyncMinutes` | wie oft (Minuten) sich der Server von einem Spieler im Spiel den **aktuellen Spielstand** hochladen lässt (Standard 5, zusätzlich bei jedem Beitritt; 0 = nur beim Beitritt) |
|
||
| `SteamWebApiKey` | Steam-Anmeldetickets der Spieler bei Steam prüfen – **nötig für Admins** (kostenlos: https://steamcommunity.com/dev/apikey) |
|
||
| `BannedSteamIds` | gesperrte Spieler |
|
||
| `Admins` | SteamID64s, die im Spiel mit `/admin` kicken/bannen dürfen (nur mit `SteamWebApiKey`, siehe Sicherheit) |
|
||
|
||
Fehlende Einstellungen ergänzt der Server beim Start automatisch. Umgebungsvariablen (`MSCO_LICENSE_KEY`,
|
||
`MSCO_NAME`, `MSCO_PORT`, `MSCO_MAX_PLAYERS`, `MSCO_PUBLIC`, `MSCO_REQUIRE_STEAM`, `MSCO_PUBLIC_ADDRESS`,
|
||
`MSCO_STEAM_WEB_API_KEY`) überschreiben `server.json`.
|
||
|
||
## Web-Panel
|
||
|
||
Der Server bringt ein Web-Panel mit – **auf demselben Port wie das Spiel** (Standard `7777`). Einfach im Browser
|
||
`http://<server-ip>:7777/` öffnen; der Server erkennt selbst, ob ein Browser oder das Spiel verbindet. Es muss also
|
||
kein weiterer Port freigegeben werden.
|
||
|
||
| Spieler | Live-Konsole |
|
||
|---|---|
|
||
|  |  |
|
||
| **Welt & Backups** | **Einstellungen** |
|
||
|  |  |
|
||
|
||
**Erstes Anmelden:** Beim Start ohne Panel-Konto steht im Log ein Einrichtungs-Code:
|
||
|
||
```bash
|
||
docker logs msco-server | grep -A2 "WEB PANEL SETUP"
|
||
```
|
||
|
||
Mit diesem Code legst du im Browser das **Owner**-Konto an (erneut anzeigen: `msco panel`).
|
||
|
||
| Bereich | Was geht |
|
||
|---|---|
|
||
| Dashboard | Spieler, Laufzeit, Weltzustand, Backups – **Start / Stop / Restart** des Gameservers, Grund wenn er nicht läuft |
|
||
| Players | Liste mit ID, Name, SteamID, IP · Kick, Ban, Admin geben/nehmen · Nachricht an alle |
|
||
| Console | Live-Log (Chat, Beitritte, Admin-Aktionen) und Befehle eingeben (mit Verlauf ↑/↓) |
|
||
| Admins | Ingame-Admins per SteamID hinzufügen/entfernen (braucht einen [Steam Web API Key](https://steamcommunity.com/dev/apikey)) |
|
||
| Bans | Gebannte ansehen, entbannen, SteamID auch offline bannen |
|
||
| World & Backups | Welt zurücksetzen, Backup, Reset-Punkt, Backups wiederherstellen, **Spielstand als ZIP, RAR oder 7z hochladen**, als ZIP herunterladen |
|
||
| Settings | `server.json` bearbeiten (Name, Spielerzahl, Sichtbarkeit, Ports, Panel, Schlüssel …) |
|
||
| Panel Users | weitere Benutzer anlegen: **Moderator** (Spieler/Chat), **Admin** (alles am Server), **Owner** (+ Benutzer) |
|
||
|
||
- **Das Panel ist unabhängig vom Gameserver:** Es läuft auch, wenn der Server gestoppt ist, abgestürzt ist oder gar
|
||
nicht starten kann (z.B. kein Lizenzschlüssel, kein Spielstand). Das Dashboard zeigt dann den Grund – Schlüssel
|
||
unter **Settings** eintragen bzw. Spielstand als ZIP unter **World & Backups** hochladen und **Start** drücken.
|
||
- **Stop** fährt den Gameserver herunter (Spieler bekommen den Grund, niemand kann beitreten), **Start** startet ihn
|
||
wieder, **Restart** lädt Einstellungen, Lizenz und Spielstand neu. Nach einem Absturz startet er automatisch neu.
|
||
- Erster Start ganz ohne Einrichtung: `docker compose up -d`, Panel öffnen, Einrichtungs-Code aus dem Log eingeben,
|
||
Lizenzschlüssel und Spielstand im Panel hinterlegen, Start.
|
||
- Oberfläche auf **Deutsch oder Englisch** (Umschalter unten in der Seitenleiste).
|
||
- **Port oder Panel-Einstellungen geändert?** In den Einstellungen unten **„Panel neu starten“** – übernimmt sie sofort,
|
||
man bleibt angemeldet und wird auf die neue Adresse weitergeleitet.
|
||
- **Keymaster nicht erreichbar oder lehnt den Schlüssel ab?** Der Server läuft weiter und warnt im Log/Panel; erst nach
|
||
`LicenseGraceHours` (Standard 72 h) ohne Bestätigung stoppt der Gameserver und startet von selbst wieder, sobald der
|
||
Master den Schlüssel bestätigt. Auch ein Neustart klappt in dieser Zeit (letzte Bestätigung in `data/license.json`).
|
||
- Passwörter werden nur als PBKDF2-Hash in `data/panel_users.json` gespeichert, Login mit Sperre nach
|
||
Fehlversuchen, Sitzungs-Cookie (HttpOnly, SameSite=Strict), Schutz gegen CSRF.
|
||
|
||
### Hinter einem Reverse-Proxy
|
||
|
||
Den Proxy einfach auf `http://<server>:7777` zeigen lassen und in `server.json` setzen:
|
||
|
||
```json
|
||
"PanelTrustProxy": true,
|
||
"PanelBasePath": ""
|
||
```
|
||
|
||
`PanelTrustProxy` übernimmt die echte Besucher-IP und HTTPS (`X-Forwarded-For` / `-Proto`), `PanelBasePath` (z.B.
|
||
`"/panel"`) bzw. `X-Forwarded-Prefix` für einen Unterpfad. Lieber einen eigenen Port? `"PanelPort": 40120` (und im
|
||
`docker-compose.yml` freigeben); mit `"PanelBindAddress": "127.0.0.1"` ist das Panel dann nur über den lokalen Proxy
|
||
erreichbar. Ganz aus: `"PanelEnabled": false`.
|
||
|
||
> **Eigener Panel-Port – nicht aussperren:** Der Port gilt erst nach einem Neustart des ganzen Programms
|
||
> (`docker compose restart`; der Neustart-Knopf im Panel startet nur den Gameserver neu). Danach ist das Panel **nur
|
||
> noch** unter dem neuen Port erreichbar, in Docker nur, wenn er in `docker-compose.yml` unter `ports` steht
|
||
> (`- "40120:40120/tcp"`). Schon ausgesperrt? `"PanelPort": 0` in `data/server.json` setzen und
|
||
> `docker compose restart` – dann ist das Panel wieder auf dem Spiel-Port.
|
||
|
||
## Konsolenbefehle
|
||
|
||
| Befehl | Beschreibung |
|
||
|---|---|
|
||
| `status`, `players` | Übersicht, Spielerliste mit IDs |
|
||
| `kick <id> [grund]`, `ban <id> [grund]` | Spieler entfernen / sperren |
|
||
| `bans`, `unban <SteamID64 oder name:spieler>` | Gesperrte anzeigen / Sperre aufheben |
|
||
| `say <text>` | Chatnachricht an alle |
|
||
| `admins`, `op <id oder SteamID64>`, `deop <...>` | Admins anzeigen / geben / nehmen |
|
||
| `save` | Weltzustand jetzt speichern |
|
||
| `reload-save` | Spielstand-Dateien neu einlesen – verbundene Spieler verbinden sich automatisch neu |
|
||
| `backups`, `restore-save <nr>` | gesicherte Spielstände anzeigen / einen davon zurückholen (z.B. wenn der aktuelle kaputt ist) – alle verbinden sich neu |
|
||
| `backup` | aktuellen Spielstand jetzt sichern |
|
||
| `reset-world` | ganze Welt zurück auf den Reset-Punkt (den Spielstand, wie er dem Server gegeben wurde: Start, `--import-save`, `reload-save`) – alle verbinden neu |
|
||
| `set-reset-point` | aktuellen Spielstand als neuen Reset-Punkt nehmen |
|
||
| `stop` / `start` | Gameserver stoppen (das Web-Panel läuft weiter) / wieder starten |
|
||
| `restart` | Server neu starten (Einstellungen, Lizenz, Spielstand neu laden) |
|
||
| `exit` | ganzes Programm beenden (auch das Panel) – mit Docker startet es danach wieder, dauerhaft aus: `docker compose stop` |
|
||
| `panel` | Adresse des Web-Panels und ggf. den Einrichtungs-Code anzeigen |
|
||
| `stop` | speichern und beenden |
|
||
|
||
Im Spiel öffnen Admins mit `/admin` (Chat, Taste T) oder `admin` (F7-Konsole) das Admin-Menü: Kick/Ban,
|
||
GOTO/BRING (zu einem Spieler / Spieler zu sich teleportieren) und Cheats (Geld, Bedürfnisse auffüllen, Satsuma zu sich holen).
|
||
Cheats werden allen im Chat angezeigt, Teleports stehen im Server-Log.
|
||
Der Chat landet auch im Server-Log.
|
||
|
||
## Was der Server speichert
|
||
|
||
**Aktueller Spielstand statt Nachspielen:** Tritt jemand bei (und alle `SaveSyncMinutes`), bittet der Server einen
|
||
Spieler, der schon im Spiel ist, zu speichern und den Spielstand hochzuladen – genau wie das Spiel selbst speichert
|
||
(Schrauben, angebaute Teile, alles). Neue Spieler bekommen diesen Stand; alte Aktionen werden dann nicht mehr
|
||
nachgespielt. Der vorherige Spielstand liegt jeweils in `data/save_backups/` (die letzten 5).
|
||
|
||
- `save/` – der Ausgangs-Spielstand, den jeder neue Spieler bekommt.
|
||
- `world.json` – alles, was seitdem passiert ist: Positionen bewegter Objekte, Schraub-/Montage-Aktionen,
|
||
Geld, Türen/Tore, Fahrzeugzustand (Tank, Öl, Schrauben, Verschleiß …) und die letzte Position jedes Spielers.
|
||
|
||
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:** `msco reset-world` (oder RESET WORLD im Admin-Menü) setzt die ganze Welt auf den
|
||
Reset-Punkt zurück – den Spielstand, wie er dem Server gegeben wurde (liegt in `data/save_original/`). Einen **neuen**
|
||
Spielstand übernehmen: Dateien nach `data/save/` legen und `msco reload-save` ausführen – der wird dann auch der neue
|
||
Reset-Punkt.
|
||
|
||
## Sicherheit
|
||
|
||
- Die Adresse des Masters (`https://msco.mbm.wtf`) ist fest eingebaut, damit niemand die Schlüsselprüfung mit
|
||
einem eigenen Master umgehen kann. Sie lässt sich weder in `server.json` noch per Umgebungsvariable ändern.
|
||
- `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.
|
||
- **Admins nur mit `SteamWebApiKey`:** Ohne Key prüft der Server die Steam-Tickets nicht – jeder könnte dann eine
|
||
fremde SteamID (z.B. die eines Admins) angeben. Deshalb gibt es ohne Key keine Admin-Rechte (das Log warnt beim
|
||
Start), und eine unbestätigte SteamID kann keinen verbundenen Spieler verdrängen.
|
||
- 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
|
||
|
||
GNU AGPL-3.0 – siehe [LICENSE](LICENSE).
|
||
|
||
Made by **MBM** – [mbm.wtf](https://mbm.wtf)
|