Initial commit: Minecraft Java & Bedrock crossplay server setup
This commit is contained in:
@@ -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 <Gamertag>" 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
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
.env
|
||||||
|
*.log
|
||||||
|
.DS_Store
|
||||||
|
Thumbs.db
|
||||||
|
|
||||||
@@ -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 → <deine VCN> → 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.
|
||||||
@@ -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 `<Server-IP>`, Port `25565`
|
||||||
|
* **Bedrock Edition:** Server hinzufuegen, Adresse `<Server-IP>`, 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/<Plugin>/` 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
|
||||||
|
```
|
||||||
@@ -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
|
||||||
Binary file not shown.
@@ -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 <Gamertag>"` 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:
|
||||||
@@ -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()
|
||||||
Reference in New Issue
Block a user