Browser and native WebSocket clients can use the optional WebSocket gateway, which preserves player IPs across login and game handoffs while retaining the existing TCP listeners.
Everything below builds the server from the sources in this repository - no pre-built image is downloaded.
cd docker
docker compose up -d --buildThat command:
- builds
../Dockerfile, which compilesloginserver,sharedserverandgameserveras C++20 with pinned Zig 0.16.0/Clang 21.1.0, then packages the binaries in an Ubuntu 20.04 runtime together withdata/anddocker/conf/; - starts MySQL 5.7 and imports
initdb/*.sqlon first run; - applies
docker/initdb-docker.sql, which pointsDARKEDEN.WorldDBInfoandDARKEDEN.GameServerInfoat this stack (the dumps ship with the original developers' LAN addresses); - starts the three servers in order once the database is ready
(see
docker/start.sh); - builds
src/server/websocketproxyserverand starts the WebSocket gateway (odk-websocket) in the game servers' network namespace, published on127.0.0.1:8080for browser and native WebSocket clients (docker/websocket.json, seedocs/websocket.md).
Follow the logs:
docker compose logs -f odk-serverStop everything (the database keeps its data in the odk-mysql-data volume):
docker compose downAdd -v to docker compose down to wipe the database as well.
macOS: the same commands work under OrbStack (what this was verified
with) and should under Docker Desktop. On Apple Silicon the server images
build natively for arm64, but
mysql/mysql-server:5.7 is published for amd64 only, so Docker runs it under
emulation and warns that The requested image's platform (linux/amd64) does not match the detected host platform. That warning is expected, and the
stack comes up as it does on Linux.
NOTE: the compose setup assumes server and client run on the same machine.
To run the client on another machine, set the server IP in the
DARKEDEN.GameServerInfo table (or in docker/initdb-docker.sql before the
first start) and restart the server container.
cd docker
docker compose up -d --buildThe image uses C++20 and defaults to CMAKE_BUILD_TYPE=Release. The compose
build arguments can select Debug:
BUILD_TYPE=Debug docker compose up -d --builddocker compose down requests gameserver shutdown first and keeps the
login/shared processes alive until its workers finish. Gameserver has a
30-second shutdown deadline; Compose allows 45 seconds before killing the
container. A deadline expiry is a failed, forced exit, not a completed drain.
This joins workers but does not introduce a full world-save operation.
Set command: ["sleep","infinity"] on the odk-server service, then:
docker exec -w /home/darkeden/vs/bin -it odk-server /bin/bash
./start.shDockerfile.dev provides the same pinned Zig/Clang compiler as the production
builder. Build it once from the repository root:
docker build -f Dockerfile.dev -t darkeden-dev .The development helper copies only build inputs into a Docker volume, avoiding
the cost of compiling directly from a Windows bind mount. Artifacts remain in
that volume rather than updating the checkout's bin/ and lib/ directories.
make dev-test
make dev-build
make dev-shellThe same commands work on macOS under OrbStack (verified) and should under Docker Desktop; on Apple Silicon the image is arm64 and the build is native to it.
The servers also build as native macOS executables with Apple Clang. This is verified on Apple Silicon (macOS 27.0, Apple Clang 21, CMake 4.4). The dependencies come from Homebrew:
xcode-select --install
brew install cmake mysql-client@8.4 luajitmysql-client@8.4 is the MySQL client library the build was verified
with; the unversioned mysql-client formula is a newer series nobody has
built against. Both are keg-only. CMake looks for them under Homebrew's
opt/ directories, on Apple Silicon (/opt/homebrew) and Intel
(/usr/local) alike, preferring mysql-client@8.4, then
mysql-client@8.0, then mysql-client, so no extra flags are needed.
From the repository root:
make debug # or: make releaseThat writes bin/loginserver, bin/sharedserver, bin/gameserver and
bin/hashpw. bin/hashpw works as it does in the container:
./bin/hashpw <<< 'new-password'The Docker stack is still the way to run the servers. Running the native
binaries needs a MySQL they can reach (the compose file publishes no MySQL
port) and a copy of conf/ with HomePath, DB_HOST, UI_DB_HOST and
LoginServerIP set for this machine. That has not been tried on macOS yet.
make dev-test is the reference run. The suite also builds natively, with
one workaround: the macOS file system ignores letter case, so
#include <assert.h> finds the project's own src/Core/Assert.h and
googletest does not compile. Forcing the system header in first works. The
tree gets its own output root so that it leaves make debug's bin/ and
lib/ alone:
cmake -B build-tests -DCMAKE_BUILD_TYPE=Debug -DDARKEDEN_BUILD_TESTS=ON \
-DDARKEDEN_OUTPUT_ROOT="$PWD/build-tests" \
-DCMAKE_CXX_FLAGS="-include $(xcrun --show-sdk-path)/usr/include/assert.h"
cmake --build build-tests --target wire_tests -j"$(sysctl -n hw.ncpu)"
(cd build-tests && ctest --output-on-failure)50 of the 53 tests pass, the wire goldens among them. ratchets,
proxy_acceptor_tests and shutdown_supervisor fail because of the
platform rather than the code; docs/FIXES.md records each one.
docker exec -it odk-mysql mysql -u elcastle -pelca110use DARKEDEN;
update GameServerInfo set IP = '192.168.0.16';initdb/DARKEDEN.sql ships two development accounts, each with characters:
| Account | Password |
|---|---|
111111 |
111111 |
222222 |
222222 |
Passwords are stored as argon2id hashes in Player.Password (the
loginserver's PasswordHash module, over the vendored third_party/argon2),
never in plain text. Registering from the client creates a hashed account.
To set or reset a password by hand, hash it with bin/hashpw (the password
is read from stdin so it stays out of shell history) and store the result:
docker exec -i odk-server ./hashpw <<< 'new-password'UPDATE DARKEDEN.Player SET Password = '$argon2id$v=19$...' WHERE PlayerID = 'someone';An existing database needs the column widened once
(initdb/migrations/001-argon2-password-column.sql). Its rows can keep
their old plaintext value: the loginserver still accepts it and rewrites the
row as a hash on that account's next successful login, so nobody is locked
out. bin/hashpw --verify '<stored value>' checks a password against a
stored value.
The content tables of initdb/DARKEDEN.sql (NPC names and dialogue, zone,
monster and item names, system messages, nicknames and the rest) and the
quest lists in data/ are English; the translations and the scripts that
write them are in tools/i18n/ (its README explains the tables). A fresh
install gets the English rows from the seed; a database created before them
takes them once, without touching accounts, characters or items:
docker exec -i odk-mysql mysql -u elcastle -pelca110 DARKEDEN < initdb/migrations/004-english-content.sqlThe client repository ships the matching English for the client-side data
(tools/i18n there); the NPC and place spellings are shared between the two.
Dockerfile.pub packages an already-compiled bin/ directory instead of
compiling from source, which is useful when publishing a release image. It
installs the same runtime libraries and applies the same start.sh/CRLF
handling as the source build's runtime stage. The checkout's bin/ must hold
Linux binaries built for the Ubuntu 20.04 runtime (e.g. copied out of the
darkeden-dev volume; make dev-build does not update bin/). BuildKit is
required so that Dockerfile.pub.dockerignore (which keeps bin/ in the
context) is used instead of .dockerignore:
DOCKER_BUILDKIT=1 docker build . -t darkeden:latest -f Dockerfile.pub