commit 9f50df534cf77da0c57279600280ed2104872d29 Author: nicolas Date: Sun Sep 13 22:06:00 2026 +0200 Initial commit: Minecraft Java & Bedrock crossplay server setup diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..01caa5f --- /dev/null +++ b/.env.example @@ -0,0 +1,37 @@ +# Stellschrauben fuer den Crossplay-Server. +# Nach jeder Aenderung: docker compose up -d + +# Minecraft-/Paper-Version. LATEST = jeweils neueste stabile Version. +# Fest pinnen (z.B. 26.2) macht Updates planbar - empfohlen im Dauerbetrieb. +MC_VERSION=LATEST + +# Arbeitsspeicher fuer die JVM (Xms=Xmx). Faustregel: 4-8G fuer eine +# Handvoll Spieler, nie mehr als ~50-60% des Host-RAM. +MC_MEMORY=4G + +# Ports auf dem Host. Container-intern bleibt es immer 25565/19132. +JAVA_PORT=25565 +BEDROCK_PORT=19132 + +# server.properties +MC_MOTD=Crossplay Server - Java & Bedrock +MC_DIFFICULTY=normal +MC_MAX_PLAYERS=20 +MC_VIEW_DISTANCE=10 +MC_SIMULATION_DISTANCE=8 + +# Operatoren: Java-Namen kommagetrennt. Bedrock-Spieler kommen mit ihrem +# Floodgate-Namen inkl. Praefix, also z.B. .MeinGamertag +MC_OPS=PlayerName + +# Whitelist. Erst auf true stellen, wenn unten der richtige Name drin steht - +# sonst sperrst du dich vom eigenen Server aus. +# MC_WHITELIST nimmt nur Java-Profilnamen (kommagetrennt). Bedrock-Spieler +# kommen per con-cli "fwhitelist add " dazu. +MC_WHITELIST_ENABLED=true +MC_WHITELIST=PlayerName + +# Passwort fuer RCON (nur containerintern erreichbar, kein Host-Port). +RCON_PASSWORD=change_this_to_a_secure_password + +TZ=Europe/Berlin diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9f3b8ce --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.env +*.log +.DS_Store +Thumbs.db + diff --git a/DEPLOY-ORACLE.md b/DEPLOY-ORACLE.md new file mode 100644 index 0000000..6d8ad30 --- /dev/null +++ b/DEPLOY-ORACLE.md @@ -0,0 +1,265 @@ +# Deployment: Oracle Cloud (Ampere A1) auf minecraft.djnizz.at + +Anleitung fuer den Umzug des lokal getesteten Stacks auf die bestehende +OCI-Instanz. Alles per Hand, kein Automatismus. + +## Ausgangslage + +| | | +|---|---| +| Ziel-Host | `130.61.230.183` | +| DNS | `minecraft.djnizz.at` → `130.61.230.183` (A-Record bei helloly, **existiert bereits**) | +| Shape | Ampere A1, ARM64 | +| SSH-Key | `C:\Users\nicol\Documents\oracle free private key.ppk` | +| Paket | `deploy/minecraft-crossplay.tar.gz` | + +Geprueft: DNS loest korrekt auf, die Ports 25565/TCP und 19132/UDP sind von +aussen aktuell noch dicht — erwartbar, es laeuft ja noch nichts. + +**ARM ist kein Problem.** `itzg/minecraft-server` ist multiarch und bringt +`linux/arm64` mit; Paper, Geyser und Floodgate sind reines Java. + +**Caddy wird nicht gebraucht.** Minecraft spricht ein eigenes Protokoll ueber +rohes TCP, Bedrock zusaetzlich UDP — beides kann ein HTTP-Reverse-Proxy nicht +sinnvoll weiterreichen. Die Container-Ports werden direkt veroeffentlicht. +80/443 bleiben unberuehrt, es gibt keine Kollision. + +## 1. SSH-Key nutzbar machen + +Die `.ppk` ist PuTTY-Format, `scp`/`ssh` von Windows koennen damit nichts +anfangen. Entweder mit `pscp`/WinSCP arbeiten oder einmalig konvertieren: + +```powershell +puttygen "$HOME\Documents\oracle free private key.ppk" -O private-openssh -o "$HOME\.ssh\oracle_mc.key" +``` + +Danach Rechte einschraenken, sonst verweigert OpenSSH den Key: + +```powershell +icacls "$HOME\.ssh\oracle_mc.key" /inheritance:r /grant:r "$($env:USERNAME):(R)" +``` + +Der Login-User haengt am Image: Ubuntu → `ubuntu`, Oracle Linux → `opc`. + +## 2. Paket hochladen und entpacken + +```powershell +scp -i "$HOME\.ssh\oracle_mc.key" "deploy\minecraft-crossplay.tar.gz" ubuntu@130.61.230.183:~/ +``` + +```powershell +ssh -i "$HOME\.ssh\oracle_mc.key" ubuntu@130.61.230.183 +``` + +Auf dem Server: + +```bash +mkdir -p ~/docker && tar xzf ~/minecraft-crossplay.tar.gz -C ~/docker/ && cd ~/docker/minecraft-crossplay && ls -la +``` + +Docker liegt durch den Caddy-Stack schon vor. Kurz gegenpruefen: + +```bash +docker version --format '{{.Server.Version}}' && docker compose version +``` + +## 3. Ports in OCI oeffnen (der eigentliche Knackpunkt) + +Das ist in der Praxis die Ursache fuer die meisten „Server nicht erreichbar"- +Faelle. In der OCI-Konsole: + +**Networking → Virtual Cloud Networks → → Security Lists → +Default Security List → Add Ingress Rules** + +Zwei Regeln, beide *stateful* (Haken bei „Stateless" NICHT setzen): + +| Source CIDR | IP Protocol | Destination Port Range | +|---|---|---| +| `0.0.0.0/0` | TCP | `25565` | +| `0.0.0.0/0` | UDP | `19132` | + +Haengt an der VNIC deiner Instanz eine **Network Security Group**, muessen die +beiden Regeln dort hinein statt in die Security List — sonst greifen sie nicht. + +Die UDP-Regel wird gerne vergessen. Ohne sie funktioniert Java einwandfrei und +Bedrock gar nicht. + +## 4. Firewall auf der Instanz + +Von Docker veroeffentlichte Ports gehen per DNAT an der `INPUT`-Kette vorbei, +deshalb laeuft das meist ohne Zutun. Erst testen (Schritt 6), und nur wenn es +scheitert, hier nachziehen. + +Welche Firewall aktiv ist: + +```bash +systemctl is-active firewalld; sudo iptables -S INPUT | head +``` + +**Oracle Linux (firewalld):** + +```bash +sudo firewall-cmd --permanent --add-port=25565/tcp --add-port=19132/udp && sudo firewall-cmd --reload +``` + +**Ubuntu (iptables + netfilter-persistent):** + +```bash +sudo iptables -I INPUT 5 -p tcp --dport 25565 -j ACCEPT && sudo iptables -I INPUT 6 -p udp --dport 19132 -j ACCEPT && sudo netfilter-persistent save +``` + +## 5. Konfiguration anpassen + +In `~/docker/minecraft-crossplay/.env`: + +* `MC_MEMORY` — im Paket auf `8G` gesetzt. Die Instanz hat 24 GB, davon gehen + Caddy und der uebrige Kram ab; 8 GB sind fuer eine private Runde reichlich. +* `MC_OPS` — dein Java-Name, sonst hast du auf dem Server keine Rechte. +* `MC_MOTD` — steht auf `djnizz.at - Java & Bedrock`. +* `RCON_PASSWORD` — im Paket bereits neu erzeugt, nicht identisch mit lokal. + +## 6. Starten und pruefen + +```bash +cd ~/docker/minecraft-crossplay && docker compose up -d && docker compose logs -f +``` + +Fertig, sobald `Started Geyser on UDP port 19132` und `Done (…)! For help, type +"help"` erscheinen. Erster Start dauert einige Minuten (Paper, Plugins, Welt). + +Danach **von deinem Windows-Rechner aus** — das ist der ehrliche Test, weil er +den kompletten Weg inklusive Security List prueft: + +```bash +python "scripts/check-crossplay.py" minecraft.djnizz.at +``` + +Beide Abschnitte muessen `OK` melden. Kommt bei Java ein Timeout, fehlt die +TCP-Regel; kommt er nur bei Bedrock, fehlt die UDP-Regel. + +## 7. Verbinden + +* **Java Edition:** `minecraft.djnizz.at` (Port 25565 ist Standard, muss nicht + mitgegeben werden) +* **Bedrock Edition:** Adresse `minecraft.djnizz.at`, Port `19132` +* **Konsolen** (PS/Xbox/Switch) koennen keine freie Adresse eintragen — dort + fuehrt der Weg ueber BedrockConnect oder eine DNS-Umleitung im Heimnetz. + +### Zum SRV-Record + +Ein SRV-Record (`_minecraft._tcp.minecraft.djnizz.at`) lohnt nur, wenn Java auf +einem anderen Port als 25565 laufen soll — dann koennen Java-Spieler die +Portangabe weglassen. **Bedrock wertet keine SRV-Records aus**, dort muss der +Port immer 19132 sein oder manuell eingetippt werden. Bei der jetzigen +Konfiguration brauchst du also keinen. + +## 8. Betrieb + +**Wichtig — reservierte IP:** Ist die Public IP der Instanz *ephemeral*, aendert +sie sich beim Stoppen/Starten der VM und der A-Record bei helloly zeigt ins +Leere. In der OCI-Konsole unter *Instance → Attached VNICs → IPv4 Addresses* auf +**Reserved** umstellen, falls noch nicht geschehen. + +Autostart nach Reboot deckt `restart: unless-stopped` ab, solange der +Docker-Dienst selbst aktiviert ist: + +```bash +sudo systemctl is-enabled docker +``` + +Konsole und Backup laufen wie lokal (siehe [README.md](README.md)): + +```bash +docker compose exec minecraft rcon-cli +``` + +```bash +docker compose exec minecraft rcon-cli save-all && docker run --rm -v mc-crossplay_mc-data:/data -v "$PWD":/backup alpine tar czf /backup/mc-backup-$(date +%F).tar.gz -C /data . +``` + +Ein Cronjob dafuer ist auf einem oeffentlich erreichbaren Server keine schlechte +Idee. + +## 9. Oeffentlicher Server: Whitelist + +Sobald die Ports offen sind, findet dich jeder Scanner. Die Whitelist ist im +Stack bereits verkabelt und wird ueber die `.env` gesteuert: + +``` +MC_WHITELIST_ENABLED=true +MC_WHITELIST=Nizz_05 +``` + +`MC_WHITELIST` nimmt **nur Java-Profilnamen** - itzg loest sie beim Start ueber +PlayerDB/Mojang in UUIDs auf. Ein Xbox-Gamertag scheitert an diesem Lookup und +landet still nicht in der Liste. Trag hier also nie einen Bedrock-Namen ein. + +### Bedrock-Spieler auf die Whitelist + +Erster Versuch ueber den Gamertag - Floodgate strippt einen fuehrenden Punkt +selbst: + +```bash +docker compose exec minecraft rcon-cli "fwhitelist add Nizz05" +``` + +Das schlaegt oft fehl, weil Floodgate den Gamertag ueber die oeffentliche +GeyserMC-API in eine XUID aufloest und die nur Accounts kennt, die dort schon +mal abgefragt wurden. **Achtung:** ueber RCON kommt in beiden Faellen *keine* +Ausgabe zurueck - Floodgate schickt seine Antworten ueber ein eigenes +Message-System, das den RCON-Aufrufer nicht erreicht. Leerer Output heisst also +nicht „hat geklappt". Maszgeblich ist immer: + +```bash +docker compose exec minecraft rcon-cli "whitelist list" +``` + +Der zuverlaessige Weg fuehrt ueber die Floodgate-UUID. Der Spieler verbindet +sich einmal bei abgeschalteter Whitelist (`whitelist off`), danach steht er im +User-Cache: + +```bash +docker compose exec minecraft cat /data/usercache.json +``` + +Die UUID hat die Form `00000000-0000-0000-XXXX-XXXXXXXXXXXX`: die oberen 64 Bit +sind null (daran erkennt Floodgate seine eigenen IDs), die hinteren 16 +Hex-Stellen sind die Xbox-XUID. Damit: + +```bash +docker compose exec minecraft rcon-cli "fwhitelist add 00000000-0000-0000-XXXX-XXXXXXXXXXXX" +``` + +In `whitelist list` taucht der Eintrag danach als **`unknown`** auf, nicht unter +dem Gamertag - Floodgate traegt beim Hinzufuegen per UUID fest `"unknown"` als +Namen ein. Rein kosmetisch, geprueft wird gegen die UUID. + +`EXISTING_WHITELIST_FILE: MERGE` in der `docker-compose.yml` sorgt dafuer, dass +diese Eintraege einen Neustart ueberleben. Ohne das wuerde die `whitelist.json` +bei jedem Start auf den Inhalt von `MC_WHITELIST` zusammengestrichen - und die +Bedrock-Spieler waeren wieder draussen. + +**Reihenfolge beachten:** erst eintragen, dann scharf schalten. Operatoren +umgehen die Whitelist zwar, aber darauf sollte man sich nicht verlassen. + +## 10. Troubleshooting + +**Von aussen keine Verbindung, lokal auf dem Server aber schon** — dann liegt es +an OCI. Auf dem Server gegenpruefen: + +```bash +docker compose exec minecraft rcon-cli list +``` + +Antwortet das, laeuft der Server und es fehlt die Security-List-Regel oder die +Instanz-Firewall blockt. + +**Bedrock timeoutet, Java geht** — die UDP-Regel fehlt. Sicher der haeufigste +Fall. + +**IP hat sich geaendert** — A-Record bei helloly nachziehen und die Public IP +auf reserved umstellen. + +**Container startet in Schleife, Log sagt „requires Java 25 or above"** — das +Image-Tag wurde auf eine aeltere JVM zurueckgesetzt. Es muss +`stable-java25` oder neuer bleiben. diff --git a/README.md b/README.md new file mode 100644 index 0000000..c1210a3 --- /dev/null +++ b/README.md @@ -0,0 +1,202 @@ +# Minecraft Crossplay-Server (Java + Bedrock) + +Ein PaperMC-Server im Container, der per **GeyserMC** zusaetzlich Bedrock-Clients +(Konsole, Handy, Windows-10-Edition) annimmt. **Floodgate** authentifiziert diese +Spieler ueber Xbox Live, ein gekaufter Java-Account ist fuer sie nicht noetig. + +Es bleibt ein **Java-Server**: Redstone, Kampfsystem und Mob-Verhalten folgen den +Java-Mechaniken. Geyser sitzt als Uebersetzungsschicht dazwischen. + +``` +Java-Client ──TCP 25565──┐ + ├──► PaperMC (Java-Server) +Bedrock-Client ─UDP 19132─┴──► Geyser ──► Floodgate ──► Xbox-Live-Auth +``` + +## Dateien + +| Datei | Zweck | +|---|---| +| `docker-compose.yml` | Der Stack: Image, Ports, Plugins, server.properties | +| `.env` | Alle Stellschrauben (Version, RAM, Ports, MOTD, Ops, RCON-Passwort) | +| `config/plugins/Geyser-Spigot/config.yml` | Geyser-Vorgabe, u.a. `auth-type: floodgate` | + +Die Welt und alle Plugin-Daten liegen im Docker-Volume `mc-crossplay_mc-data`, +nicht im Projektordner (deutlich schnellere I/O unter Docker Desktop). + +## Schnellstart + +```bash +docker compose up -d +``` + +Der erste Start dauert ein paar Minuten: Paper-Jar, Geyser und Floodgate werden +geladen und die Welt wird generiert. + +Logs mitlesen: + +```bash +docker compose logs -f +``` + +Fertig ist der Server, sobald `Started Geyser on UDP port 19132` und danach +`Done (…)! For help, type "help"` in den Logs stehen. + +Getestet mit Paper 26.2, Geyser 2.11.1 und Floodgate 2.2.5: Java-Ping auf +25565/TCP und RakNet-Ping auf 19132/UDP antworten beide, MOTD und Spielerzahl +werden vom Java-Server an die Bedrock-Serverliste durchgereicht. + +## Verbinden + +* **Java Edition:** Adresse ``, Port `25565` +* **Bedrock Edition:** Server hinzufuegen, Adresse ``, Port `19132` +* **Vom selben Rechner:** `localhost` bzw. `127.0.0.1` + +Auf Konsolen (Switch, PS, Xbox) laesst sich oft keine freie Serveradresse +eintragen. Ueblicher Weg dort: DNS-Umleitung im Heimnetz oder ein Dienst wie +BedrockConnect. + +Bedrock-Spieler erscheinen mit einem Punkt vor dem Namen, z.B. `.MeinGamertag`. +Das ist der Floodgate-Praefix; er verhindert Namenskollisionen mit Java-Accounts. +Aendern laesst er sich in `plugins/floodgate/config.yml` (`username-prefix`). + +## Firewall (Windows) + +Docker Desktop oeffnet die Ports nur lokal. Fuer Zugriff aus dem LAN muessen +beide Ports durch die Windows-Firewall — in einer **Admin-PowerShell**: + +```powershell +New-NetFirewallRule -DisplayName "Minecraft Java 25565" -Direction Inbound -Protocol TCP -LocalPort 25565 -Action Allow +``` + +```powershell +New-NetFirewallRule -DisplayName "Minecraft Bedrock 19132" -Direction Inbound -Protocol UDP -LocalPort 19132 -Action Allow +``` + +Fuer Zugriff aus dem Internet zusaetzlich im Router portforwarden: +**25565/TCP** und **19132/UDP**. Bedrock laeuft ueber UDP — ein reines +TCP-Forwarding reicht nicht. + +## Erreichbarkeit pruefen + +Sobald der Router-Port offen ist, testet Geyser die Erreichbarkeit von aussen +selbst — das fragt einen oeffentlichen GeyserMC-Testdienst an, uebermittelt also +deine oeffentliche IP und den Port dorthin: + +```bash +docker compose exec minecraft rcon-cli "geyser connectiontest" +``` + +## Betrieb + +```bash +docker compose ps +``` + +Serverkonsole (Befehle wie `op`, `whitelist`, `save-all`): + +```bash +docker compose exec minecraft rcon-cli +``` + +Einzelner Befehl: + +```bash +docker compose exec minecraft rcon-cli op DeinName +``` + +Sauber stoppen (die Welt wird gespeichert, bis zu 2 Minuten Zeit): + +```bash +docker compose down +``` + +## Updates + +* **Geyser/Floodgate:** werden bei jedem Containerstart in der aktuellsten + Version geladen. Nach einem Bedrock-Update also einfach + `docker compose restart` — Geyser zieht dann den passenden Build nach. +* **Paper/Minecraft:** `MC_VERSION` in der `.env` steht auf `LATEST`. Fuer einen + planbaren Betrieb besser fest pinnen, z.B. `MC_VERSION=26.2`. +* **Image:** `docker compose pull && docker compose up -d` + +## Weitere Plugins + +URL in `docker-compose.yml` unter `PLUGINS` als weitere Zeile ergaenzen, dann +`docker compose up -d`. Aus der Liste entfernte Plugins werden beim naechsten +Start auch aus dem `plugins`-Ordner geloescht. + +Fuer Bedrock-Spieler gilt: Plugins moeglichst simpel halten. Komplexe +Chest-GUI-Menues (Auktionshaeuser, Shop-Menues) funktionieren ueber Geyser oft +nur fehlerhaft. Plugins mit Formular-Unterstuetzung fuer Bedrock sind die +bessere Wahl. + +## Konfiguration aendern + +* **server.properties:** ueber die `.env` bzw. die Environment-Variablen in + `docker-compose.yml`. Direkte Aenderungen an der `server.properties` im Volume + werden bei diesen Werten beim naechsten Start ueberschrieben. +* **Geyser:** `config/plugins/Geyser-Spigot/config.yml` bearbeiten, dann + `docker compose restart`. Die Datei aus dem Projektordner gewinnt, sobald sie + neuer ist als die im Volume. +* **Alles andere (Floodgate, weitere Plugins):** liegt im Volume. Anschauen mit + +```bash +docker compose exec minecraft cat /data/plugins/floodgate/config.yml +``` + + Dauerhaft vorgeben laesst es sich, indem man die Datei unter + `config/plugins//` im gleichen Pfadaufbau ablegt. + +## Backup + +Welt und Plugin-Daten aus dem Volume in eine Datei sichern: + +```bash +docker run --rm -v mc-crossplay_mc-data:/data -v "$PWD":/backup alpine tar czf /backup/mc-backup.tar.gz -C /data . +``` + +Vorher `docker compose exec minecraft rcon-cli save-all` ausfuehren oder den +Server stoppen, damit die Welt konsistent auf der Platte liegt. + +## Deployment in die Oracle Cloud + +Der Umzug auf die OCI-Instanz hinter `minecraft.djnizz.at` ist in +[DEPLOY-ORACLE.md](DEPLOY-ORACLE.md) beschrieben. + +## Bekannte Einschraenkungen + +* Bedrock unterstuetzt nicht alle Java-Features grafisch 1:1 (bestimmte + Bannermuster, manche Offhand-Interaktionen) — kleine optische Abweichungen + sind normal. +* Geyser erzeugt einen leichten CPU-Overhead pro Bedrock-Spieler. Auf moderner + Hardware faellt das nicht ins Gewicht. +* Bedrock-Spieler haben keine Java-Chatsignatur, deshalb ist + `enforce-secure-profile=false` gesetzt. Ohne das fliegen sie beim Chatten raus. + +## Troubleshooting + +**Bedrock-Client findet den Server nicht** — meist die Firewall oder ein +fehlendes UDP-Forwarding. Test vom Host aus: + +```bash +docker compose exec minecraft sh -c "netstat -uln | grep 19132" +``` + +**„Unable to connect to world"** — pruefen, ob Geyser sauber gestartet ist: + +```bash +docker compose logs | grep -i geyser +``` + +**Container startet in einer Schleife neu, Log sagt „requires Java 25 or +above"** — dann zeigt das Image-Tag auf eine zu alte JVM. Minecraft 26.x braucht +Java 25, das Tag in `docker-compose.yml` muss `stable-java25` (oder neuer) +bleiben. + +**Bedrock-Spieler wird nach Java-Account gefragt** — dann greift `auth-type` +nicht. Kontrolle: + +```bash +docker compose exec minecraft grep -A2 "^java:" /data/plugins/Geyser-Spigot/config.yml +``` diff --git a/config/plugins/Geyser-Spigot/config.yml b/config/plugins/Geyser-Spigot/config.yml new file mode 100644 index 0000000..d623629 --- /dev/null +++ b/config/plugins/Geyser-Spigot/config.yml @@ -0,0 +1,45 @@ +# Geyser-Konfiguration (Vorgabe aus dem Projektordner) +# ---------------------------------------------------- +# Diese Datei wird beim Containerstart nach +# /data/plugins/Geyser-Spigot/config.yml kopiert. Geyser ergaenzt beim ersten +# Start alle hier nicht gesetzten Optionen mit ihren Standardwerten (samt +# Kommentaren) in der Datei im Volume. +# +# Aenderungen hier + `docker compose restart` uebersteuern die Datei im Volume, +# weil diese Datei dann neuer ist. + +bedrock: + # 0.0.0.0 = im Container an allen Interfaces lauschen; die Absicherung + # macht das Port-Mapping in docker-compose.yml bzw. die Firewall. + address: 0.0.0.0 + # Container-interner Bedrock-Port. Der nach aussen sichtbare Port wird in + # der .env ueber BEDROCK_PORT gesetzt - diesen Wert hier nicht anfassen. + port: 19132 + clone-remote-port: false + +java: + # Der Kern des Ganzen: Bedrock-Spieler werden ueber Floodgate/Xbox Live + # authentifiziert und brauchen keinen gekauften Java-Account. + # Moegliche Werte: floodgate | online | offline + auth-type: floodgate + +motd: + # MOTD und Spielerzahl vom Java-Server uebernehmen, damit beide Editionen + # in der Serverliste dasselbe anzeigen. + passthrough-motd: true + passthrough-player-counts: true + # Zeile 1 wird bei passthrough-motd: true vom Java-Server ueberschrieben, + # Zeile 2 nicht - die kommt immer von hier. + primary-motd: "Crossplay Server" + secondary-motd: "Java & Bedrock" + +gameplay: + # Name, den Bedrock-Clients im Pause-/Einstellungsmenue sehen. + server-name: "Crossplay Server" + # Bedrock nutzt weiterhin 1.8-Kampfmechanik; blendet einen simulierten + # Cooldown-Indikator ein, damit das Java-Kampfsystem bedienbar bleibt. + show-cooldown: title + show-coordinates: true + +# Wird von Geyser beim Start ggf. hochgezaehlt/migriert - nicht manuell aendern. +config-version: 5 diff --git a/deploy/minecraft-crossplay.tar.gz b/deploy/minecraft-crossplay.tar.gz new file mode 100644 index 0000000..d1b658a Binary files /dev/null and b/deploy/minecraft-crossplay.tar.gz differ diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..852d5db --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,97 @@ +# Minecraft Crossplay-Server: Java Edition + Bedrock Edition +# ---------------------------------------------------------- +# Basis : PaperMC (Java-Server, Plugin-faehig) +# Bruecke: GeyserMC uebersetzt Bedrock-Pakete -> Java-Protokoll +# Auth : Floodgate authentifiziert Bedrock-Spieler ueber Xbox Live, +# d.h. Konsolen-/Mobile-Spieler brauchen KEINEN Java-Account. +# +# Alle Stellschrauben liegen in .env + +name: mc-crossplay + +services: + minecraft: + # Minecraft 26.x setzt Java 25 voraus - Tag nicht auf java21 zuruecksetzen. + image: itzg/minecraft-server:stable-java25 + container_name: mc-crossplay + restart: unless-stopped + # noetig, damit die Server-Konsole via `docker attach` bedienbar bleibt + stdin_open: true + tty: true + + ports: + - "${JAVA_PORT:-25565}:25565/tcp" # Java Edition (TCP) + - "${BEDROCK_PORT:-19132}:19132/udp" # Bedrock Edition via Geyser (UDP!) + + environment: + # --- Mojang EULA ------------------------------------------------- + EULA: "TRUE" + + # --- Server-Software --------------------------------------------- + TYPE: "PAPER" + VERSION: "${MC_VERSION:-LATEST}" + + # --- Speicher / JVM ---------------------------------------------- + INIT_MEMORY: "${MC_MEMORY:-4G}" + MAX_MEMORY: "${MC_MEMORY:-4G}" + USE_AIKAR_FLAGS: "true" + + # --- Plugins ------------------------------------------------------ + # Immer der aktuellste Build. Wichtig, weil Bedrock-Clients (Konsole, + # Handy) sich selbst updaten und Geyser hinterherziehen muss. + # Eigene Plugins hier einfach als weitere Zeile (URL) ergaenzen. + PLUGINS: | + https://download.geysermc.org/v2/projects/geyser/versions/latest/builds/latest/downloads/spigot + https://download.geysermc.org/v2/projects/floodgate/versions/latest/builds/latest/downloads/spigot + + # --- Konfigurations-Seeding --------------------------------------- + # Inhalt von ./config wird nach /data kopiert, also z.B. + # ./config/plugins/Geyser-Spigot/config.yml -> /data/plugins/Geyser-Spigot/config.yml + COPY_CONFIG_DEST: "/data" + + # --- server.properties -------------------------------------------- + MOTD: "${MC_MOTD:-Crossplay Server - Java & Bedrock}" + DIFFICULTY: "${MC_DIFFICULTY:-normal}" + MAX_PLAYERS: "${MC_MAX_PLAYERS:-20}" + VIEW_DISTANCE: "${MC_VIEW_DISTANCE:-10}" + SIMULATION_DISTANCE: "${MC_SIMULATION_DISTANCE:-8}" + SPAWN_PROTECTION: "0" + OPS: "${MC_OPS:-}" + + # --- Whitelist ---------------------------------------------------- + # MC_WHITELIST nimmt ausschliesslich *Java*-Profilnamen auf - itzg loest + # sie beim Start via PlayerDB/Mojang in UUIDs auf. Xbox-Gamertags von + # Bedrock-Spielern lassen sich hier NICHT eintragen, die kommen ueber + # `rcon-cli "fwhitelist add "` direkt von Floodgate dazu. + ENABLE_WHITELIST: "${MC_WHITELIST_ENABLED:-false}" + ENFORCE_WHITELIST: "${MC_WHITELIST_ENABLED:-false}" + WHITELIST: "${MC_WHITELIST:-}" + # MERGE ist hier entscheidend: ohne das wuerde die whitelist.json bei + # jedem Start auf MC_WHITELIST reduziert - und alle per fwhitelist + # eingetragenen Bedrock-Spieler waeren wieder raus. + EXISTING_WHITELIST_FILE: "MERGE" + + # Muss TRUE bleiben: Floodgate haengt sich in die Online-Auth ein. + ONLINE_MODE: "TRUE" + # Muss false sein: Bedrock-Spieler haben keine Java-Chat-Signatur und + # wuerden sonst beim Chatten rausfliegen. + ENFORCE_SECURE_PROFILE: "false" + + # --- Fernsteuerung / Betrieb --------------------------------------- + ENABLE_RCON: "true" + RCON_PASSWORD: "${RCON_PASSWORD:?RCON_PASSWORD fehlt - bitte in .env setzen}" + TZ: "${TZ:-Europe/Berlin}" + # Zeit fuers saubere Speichern der Welt beim Stoppen + STOP_DURATION: "120" + + volumes: + # Welt + Plugin-Daten in einem Named Volume (schnelle I/O, auch unter + # Docker Desktop/WSL2). Zugriff via `docker compose cp` oder Backup s. README. + - mc-data:/data + # Vorgegebene Konfigurationsdateien, versionierbar im Projektordner + - ./config:/config:ro + + stop_grace_period: 2m + +volumes: + mc-data: diff --git a/scripts/check-crossplay.py b/scripts/check-crossplay.py new file mode 100644 index 0000000..154c28c --- /dev/null +++ b/scripts/check-crossplay.py @@ -0,0 +1,130 @@ +#!/usr/bin/env python3 +"""Prueft, ob Java- und Bedrock-Endpunkt eines Crossplay-Servers antworten. + + python scripts/check-crossplay.py # localhost + python scripts/check-crossplay.py minecraft.djnizz.at # Live-Server + +Der Java-Test spricht das Server-List-Ping-Protokoll, der Bedrock-Test einen +RakNet Unconnected Ping - also genau das, was die echten Clients tun. +""" +import json +import os +import socket +import struct +import sys +import time + +HOST = sys.argv[1] if len(sys.argv) > 1 else "127.0.0.1" +JAVA_PORT = int(sys.argv[2]) if len(sys.argv) > 2 else 25565 +BEDROCK_PORT = int(sys.argv[3]) if len(sys.argv) > 3 else 19132 +TIMEOUT = 10 + + +def _varint(n): + out = b"" + while True: + b = n & 0x7F + n >>= 7 + out += bytes([b | (0x80 if n else 0)]) + if not n: + return out + + +def _read_varint(sock): + n = 0 + for i in range(5): + b = sock.recv(1) + if not b: + raise EOFError("Verbindung vorzeitig geschlossen") + n |= (b[0] & 0x7F) << (7 * i) + if not b[0] & 0x80: + return n + raise ValueError("VarInt zu lang") + + +def java_ping(host, port): + s = socket.create_connection((host, port), timeout=TIMEOUT) + try: + host_b = host.encode() + # Handshake: Protokollversion ist beim Status-Ping beliebig + payload = (b"\x00" + _varint(772) + _varint(len(host_b)) + host_b + + struct.pack(">H", port) + _varint(1)) + s.sendall(_varint(len(payload)) + payload) + s.sendall(_varint(1) + b"\x00") # Status Request + _read_varint(s) # Paketlaenge + _read_varint(s) # Paket-ID + n = _read_varint(s) # JSON-Laenge + data = b"" + while len(data) < n: + chunk = s.recv(n - len(data)) + if not chunk: + break + data += chunk + return json.loads(data.decode("utf-8")) + finally: + s.close() + + +RAKNET_MAGIC = bytes([0x00, 0xFF, 0xFF, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, + 0xFD, 0xFD, 0xFD, 0xFD, 0x12, 0x34, 0x56, 0x78]) + + +def bedrock_ping(host, port): + s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) + s.settimeout(TIMEOUT) + try: + pkt = (b"\x01" + struct.pack(">Q", int(time.time() * 1000)) + + RAKNET_MAGIC + os.urandom(8)) + s.sendto(pkt, (host, port)) + data, _ = s.recvfrom(4096) + if data[0] != 0x1C: + raise ValueError(f"unerwartete Antwort-ID 0x{data[0]:02x}") + strlen = struct.unpack(">H", data[33:35])[0] + return data[35:35 + strlen].decode("utf-8", "replace") + finally: + s.close() + + +def main(): + print(f"Ziel: {HOST} (Java {JAVA_PORT}/TCP, Bedrock {BEDROCK_PORT}/UDP)\n") + failed = False + + print("=== Java Edition (TCP) ===") + try: + j = java_ping(HOST, JAVA_PORT) + version = j.get("version", {}) + desc = j.get("description") + if isinstance(desc, dict): + desc = desc.get("text", "") or "".join( + e.get("text", "") for e in desc.get("extra", [])) + players = j.get("players", {}) + print(f" OK {version.get('name')} (Protokoll {version.get('protocol')})") + print(f" MOTD {desc!r}") + print(f" Spieler {players.get('online')}/{players.get('max')}") + except Exception as e: + failed = True + print(f" FEHLER {type(e).__name__}: {e}") + print(" -> Security List (TCP 25565), Instanz-Firewall, Container laeuft?") + + print("\n=== Bedrock Edition (UDP, RakNet) ===") + try: + fields = bedrock_ping(HOST, BEDROCK_PORT).split(";") + labels = ["Edition", "MOTD Zeile 1", "Protokoll", "Version", "Spieler", + "Max. Spieler", "Server-GUID", "MOTD Zeile 2", "Gamemode"] + print(" OK") + for label, value in zip(labels, fields): + print(f" {label:<13} {value}") + except Exception as e: + failed = True + print(f" FEHLER {type(e).__name__}: {e}") + print(" -> Security List (UDP 19132!), Geyser gestartet?") + + print() + if failed: + print("Mindestens ein Endpunkt antwortet nicht.") + sys.exit(1) + print("Beide Editionen erreichbar.") + + +if __name__ == "__main__": + main()