better defaults + update readme

This commit is contained in:
2026-08-08 12:31:04 +02:00
parent a9153ed2bc
commit 03e34655ba
5 changed files with 291 additions and 312 deletions

148
README.md
View File

@@ -1,70 +1,98 @@
# Zomboid client-hosted → dedicated server
# AI Disclosure
Convert a locally **hosted** Project Zomboid multiplayer world into a **dedicated** server layout, then run it with Docker.
yeah, 100%. I read the code, should be fine.
Zomboid savefiles are a mess IMHO, this was the quickest way to get to the goal.
If you don't like it, don't use it, idk.
----
# Zomboid Converter
## client-hosted → dedicated server
Rust CLI that converts a locally **hosted** Project Zomboid multiplayer world into a **dedicated** server layout.
The world save format is the same (B42 `Saves/Multiplayer/…`). Conversion is packaging + naming + `players.db` world rename + mod recovery — not a binary rewrite.
## Layout
This repository is **only** the converter. Sibling folders next to a checkout (e.g. `original/`, `server/`) are optional local testing aids and are not part of the git tree.
Paths below are relative to the **repo root** (parent of this `converter/` folder):
## Build
| Path | Role |
|------|------|
| `original/` | Source saves / zips (**do not edit the zips**) |
| `converter/` | This crate — Rust CLI (`zomboid-converter`) |
| `server/` | Docker dedicated server (`docker-compose.yml`, `.env`) |
| `server/server-data/` | Zomboid userdir (Saves, Server, db, Logs) |
| `server/server-files/` | Game install (Steam download target) |
```bash
cargo build --release
# binary: target/release/zomboid-converter
```
Copy the binary wherever you run the dedicated server (example below: `~/docker/zomboid/bin/`).
## Inputs / outputs
Typical source pair from a host:
- `Saves/Multiplayer/<WorldName>/` — authoritative world (**use this**)
- `Saves/Multiplayer/<WorldName>_player/` — client cache (**ignore**)
Server settings live separately under `Zomboid/Server/<WorldName>.*` on the host machine. They are **not** inside the world folder.
## Prerequisites
- Rust toolchain (for the converter)
- Docker + Docker Compose
- Optional: local Steam workshop cache for app `108600` (to resolve `WorkshopItems`)
## Converter
```bash
cd converter
cargo build --release
cargo run --release -- \
--input ../original/serverb42stable \
--output ../server/server-data \
--server-name pzserver \
--workshop-dir /path/to/steamapps/workshop/content/108600 \
--force
```
Server settings live separately under `Zomboid/Server/<WorldName>.*` on the host. They are **not** inside the world folder. Pass the directory that contains those files with `--server-config`.
`--input` may be a world **directory** or a **`.zip`** (opened read-only; extracted to a temp dir). Paths ending in `_player` are rejected.
### Arguments
`--output` is the dedicated **server-data** root (writes `Saves/Multiplayer/<server-name>/` and patches `Server/<server-name>.ini`).
## Example (Hetzner / Docker layout)
Assumes:
| Path | Role |
|------|------|
| `~/docker/zomboid/` | Dedicated server (`docker-compose.yml`, `.env`) |
| `~/docker/zomboid/server-data/` | Zomboid userdir (Saves, Server, db, …) |
| `~/docker/zomboid/server-files/` | Game install (Steam download target) |
| `~/docker/zomboid/bin/zomboid-converter` | This binary |
| `~/docker/copyparty/data/zomboid/…` | Uploaded host world + configs |
```bash
# stop the server first if it is running
cd ~/docker/zomboid
docker compose stop
./bin/zomboid-converter \
--input ~/docker/copyparty/data/zomboid/alt/serverb42stable \
--output ~/docker/zomboid/server-data \
--server-name pzserver \
--server-config ~/docker/copyparty/data/zomboid/alt \
--force
docker compose up -d
docker compose logs -f
```
Or edit and run `~/docker/zomboid/convert-and-start.sh` (config block at the top).
`--server-name` (default `pzserver`) must match `SERVER_NAME` in the server `.env`. It is only a basename for the dedicated world and `Server/*.ini` files.
`--workshop-dir` defaults to `<output>/../server-files/steamapps/workshop/content/108600` when that directory exists (fills `WorkshopItems=`). Until Steam has downloaded mods once, re-run after the first install if needed.
## Arguments
| Flag | Description |
|------|-------------|
| `-i`, `--input` | Hosted world dir or zip |
| `-o`, `--output` | Dedicated `server-data` root |
| `-n`, `--server-name` | World + config basename (default: `pzserver`) |
| `--workshop-dir` | `…/workshop/content/108600` — fills `WorkshopItems=` |
| `--server-config` | Optional host `Server/` dir to copy/rename configs |
| `--server-config` | Dir with host `<WorldName>.ini`, `_SandboxVars.lua`, spawn files |
| `--workshop-dir` | `…/workshop/content/108600` — fills `WorkshopItems=` (auto if present next to `server-data`) |
| `--password` | Server join password (`Password=` in ini); overrides `.env` |
| `--env-file` | `.env` for password (default: `<output>/../.env`) |
| `--force` | Replace existing `Saves/Multiplayer/<server-name>/` |
### What it does
## What it does
1. Copies the world into `output/Saves/Multiplayer/<server-name>/`
2. Skips syncthing conflict sidecars (`*-ZwS1g3qi*`), `*.db-journal`, and player-cache junk
3. Sets `networkPlayers.world` in `players.db` to `<server-name>`
4. Recovers mod IDs from `WorldDictionaryReadable.lua` into `Server/<name>.ini` (`Mods=`)
5. If `--workshop-dir` is set, resolves Steam workshop IDs (`WorkshopItems=`)
5. Resolves Steam workshop IDs into `WorkshopItems=` when a workshop dir is available
6. Optionally sets `Password=` from `--password` or `SERVER_PASSWORD` / `PASSWORD` in `.env`
It does **not** overwrite `db/<server-name>.db` (accounts / whitelist stay managed by the server).
@@ -73,51 +101,41 @@ It does **not** overwrite `db/<server-name>.db` (accounts / whitelist stay manag
Characters bind to the **account username** in `players.db` (`networkPlayers.username`), not Steam display name. Players must log in with that exact username (or you rename the column before they join).
## Dedicated server (Docker)
## Dedicated server notes
```bash
cd ../server
cp .env.example .env # if needed; set passwords / SERVER_NAME
docker compose up -d
docker compose logs -f
```
Use any Project Zomboid dedicated image/layout you like. With Docker next to `server-data`:
Important `.env` keys:
- `SERVER_NAME` — must match converter `--server-name` (default `pzserver`)
- `ADMIN_USERNAME` / `ADMIN_PASSWORD` — admin account
- `RCON_PASSWORD` — RCON
- `SERVER_PASSWORD` — optional join password for the converter (`Password=` in ini)
Ports: `16261/udp`, `16262/udp`, `27015/tcp`.
Stop the container before re-running the converter with `--force`.
- `SERVER_NAME` must match converter `--server-name`
- Optional: `SERVER_PASSWORD` in `.env` for the converters `Password=` patch
- Also typical: `ADMIN_USERNAME` / `ADMIN_PASSWORD`, `RCON_PASSWORD`, memory, ports
- Ports: `16261/udp`, `16262/udp`, `27015/tcp` (RCON)
- Stop the container before re-running the converter with `--force`
- After first start, wait until workshop items finish installing (`Workshop: … Installed` in logs)
## Recommended migration flow
1. Stop the dedicated server.
2. Copy the host world into `original/` (keep zips untouched as backups).
3. Ideally also copy the hosts `Zomboid/Server/<WorldName>.ini` (+ `_SandboxVars.lua`, spawn files) and pass `--server-config`.
4. Run the converter with `--workshop-dir` and `--force`.
5. Start Docker; wait until workshop items finish installing (`Workshop: … Installed` in logs).
6. Join with the **same username** as in the hosted `players.db`.
2. Upload/copy the host world (not the `_player` folder) and ideally the host `Server/<WorldName>.*` configs.
3. Run the converter with `--server-config` pointing at the config directory.
4. Start the dedicated server; wait for workshop installs if needed.
5. Join with the **same username** as in the hosted `players.db`.
## Known gaps / caveats
| Item | Notes |
|------|--------|
| Host `SandboxVars` / spawn / `Map=` | Not in the world save. Without `--server-config`, the dedicated defaults remain. |
| Host `SandboxVars` / spawn / `Map=` | Not in the world save. Without `--server-config`, dedicated defaults remain. |
| Mods with no WorldDictionary entries | Pure Lua/QoL mods may not appear in recovered `Mods=`. |
| Workshop **versions** | IDs are resolved from your local workshop cache; newer mod builds than the host used can cause chunk `LENGTH mismatch` / `blam` errors and odd world state (e.g. zombies in cleared bases). |
| Workshop **versions** | IDs come from the workshop cache on disk; newer mod builds than the host used can cause chunk `LENGTH mismatch` / `blam` errors. |
| `ZombieMigrate` | Dedicated sandbox may allow migration even when respawn is off; align sandbox with the host when possible. |
| Running without mods first | Can rewrite `WorldDictionary` and map chunks. Re-import from `original/` if that happened. |
| Running without mods first | Can rewrite `WorldDictionary` and map chunks. Re-import from the original host world if that happened. |
## Example passwords
```bash
# ../server/.env (next to docker-compose)
# next to docker-compose (.env)
SERVER_PASSWORD=hunter2
# or CLI override (from this directory)
cargo run --release -- … --password 'hunter2'
# or CLI override
zomboid-converter … --password 'hunter2'
```