Version: 0.1.63 | Versioning policy

DurisMUD is a long-running dark-fantasy MUD built around a global race war between good and evil. Its text world combines full player-versus-player conflict with exploration, quests, ships, crafting, guilds, and powerful artifacts.
This repository contains the game server, world data, area-building toolchain, database schema, regression tests, and operational scripts.
Architecture
flowchart LR
Player["MUD client"]
subgraph Server["DurisMUD server process"]
Network["Telnet / TLS / WebSocket"]
Loop["Single event loop<br/>commands, combat, world ticks"]
Snapshots["Revisioned snapshots<br/>player and world"]
Commands["Critical commands<br/>economy, ownership, outcomes"]
Legacy["Bounded compatibility queues<br/>item, scalar, large payload"]
Network <--> Loop
Loop -->|immutable jobs| Snapshots
Loop -->|operation IDs| Commands
Loop -->|remaining events| Legacy
end
Content["World + runtime data<br/>areas/ and lib/"]
Content -->|boot and reset data| Loop
Loop -->|boot, bounded reads, legacy routes| Database[("MySQL / MariaDB<br/>durable authority")]
Snapshots -->|revision guarded| Database
Commands -->|inbox, ledger, outbox| Database
Legacy -->|deduplicated events| Database
Snapshots -.-|optional immutable world recovery| Redis[("Redis cache / recovery")]
Player <-->|game protocol| Network
classDef focal fill:#f4ecd9,stroke:#9e3b25,color:#2e2418,stroke-width:2px;
class Loop focal;
The C-style sources under src/ are compiled as C++20. Network I/O and mutable game
state remain on one select()-driven pulse loop. Immutable revisioned snapshots and
non-coalescing operation-ID commands cross typed worker boundaries; the older item,
scalar, and large-payload queues retain only bounded compatibility roles. MySQL or
MariaDB is the durable authority for snapshots, ledgers, current rows, inbox/results,
outbox state, migration history, and lifecycle evidence. Redis is optional and limited
to reconstructible caches plus validated world-recovery generations. See the full
architecture guide and database guide.
Quick start
The maintained setup path is Debian/Ubuntu, matching the CI workflow and the repository's build-dependency manifest.
Docker alternative
Docker Compose provides a complete local deployment without installing the compiler or MariaDB on the host. It builds Duris, creates a private MariaDB service, loads the fresh schema, applies immutable migrations, imports the tracked help content, generates a persistent self-signed local TLS certificate, and starts the full world:
./scripts/init_docker_env.sh
docker compose --env-file .env.docker up --build --detach --wait
curl http://127.0.0.1:4050/health
nc 127.0.0.1 4000
The generated .env.docker is ignored, mode 0600, and contains random
database credentials. The database, filesystem-backed player state, recovery
journals, backups, certificate, and logs live in named Docker volumes and
survive ordinary container rebuilds.
This stack is a local/development alternative to the native setup below; it is
not the production deployment model. See the Docker deployment guide
for lifecycle, configuration, upgrades, logs, and data-reset commands.
After setup, one command builds every maintained target and runs the complete non-database regression gate:
make test-all
Run the isolated Docker/MySQL migration and schema suites separately with
make test-db; Docker is an optional prerequisite for that database gate.
1. Install dependencies
sudo apt update
sudo apt install equivs
make build-deps-package
sudo apt install ./bin/packages/duris-build-deps_1.0_all.deb
equivs is only the bootstrap tool used to build the metapackage. The manifest
then installs Git, Python, dos2unix, the compiler, GNU Make, GDB, Valgrind,
clang-format, MariaDB-compatible client and server packages, and the XML,
compression, TLS, JSON, Redis, BSD, libcurl (SMTP), and MySQL/MariaDB
development libraries required by the repository. The compiler supplies the
ASan/UBSan runtimes used by the sanitizer build. Redis itself is optional, and so
is the SMTP relay: account password recovery by email stays off unless
MAIL_ENABLED=TRUE is set in .env.
On another Linux distribution, use
packaging/duris-build-deps.equivs as the
authoritative dependency list.
2. Configure the server
cp .env.example .env
chmod 600 .env
Edit .env and set the explicit environment role, listener address, database
host/port, user, password, name, and exact DB_ALLOWED_TARGETS entry. There are
no database credential defaults. The server rejects a .env that is not an
owner-controlled regular file with mode 0600 or stricter. It loads this file
at boot, and the migration scripts use the same values. .env is ignored by
Git and must never be committed. See the
configuration reference for precedence, Redis
recovery, proxy handling, and diagnostic switches.
Set REDIS=TRUE with REDIS_HOST, REDIS_PORT, and an explicit REDIS_DB to enable
caches and recovery integration. Local development may use shared
REDIS_USERNAME/REDIS_PASSWORD ACL authentication. Production requires distinct world,
presence, cache, maintenance, and, when enabled, donation ACL identities. Verified
REDIS_TLS=TRUE transport applies to every runtime connection; non-loopback production
endpoints require TLS. REDIS_WORLD_STATE=TRUE additionally enables immutable world
recovery.
Player saves do not depend on Redis. If a DurisWeb backend will authenticate
through WebSocket or GMCP, give it a private DURISWEB_SECRET and follow the
challenge-response contract in the
DurisWeb API reference. Production browser
traffic must terminate TLS at a local reverse proxy. The remaining switches in
.env.example are documented inline and are intended primarily for local gameplay testing.
The local-development example enables external donation notices with
REDIS_DONATION_SUBSCRIBER=TRUE. Replace its local-only placeholder with an
independent HMAC key before connecting a publisher; see the
donation event envelope.
3. Create a development database
The following uses duris_dev as an example. Set that database name, the new
user password, and DB_ALLOWED_TARGETS=127.0.0.1/duris_dev explicitly in
.env.
CREATE DATABASE IF NOT EXISTS duris_dev
CHARACTER SET utf8mb4
COLLATE utf8mb4_unicode_ci;
CREATE USER IF NOT EXISTS 'duris'@'127.0.0.1'
IDENTIFIED BY 'CHOOSE_A_PASSWORD';
ALTER USER 'duris'@'127.0.0.1'
IDENTIFIED BY 'CHOOSE_A_PASSWORD';
GRANT ALL PRIVILEGES ON duris_dev.* TO 'duris'@'127.0.0.1';
Load the authoritative fresh-database baseline:
set -a
source .env
set +a
MYSQL_PWD="$DB_PASSWD" mysql \
--host="$DB_HOST" \
--port="${DB_PORT:-3306}" \
--user="$DB_USER" \
"$DB_NAME" < migrations/bootstrap_multithread_safe.sql
# Record the exact sealed baseline, then apply immutable post-baseline steps.
python3 scripts/migration_runner.py adopt --kind fresh_bootstrap
python3 scripts/migration_runner.py run
# Confirm the exact runtime contract before first boot.
./migrations/verify_runtime_compatibility.sh
# Epic-zone gameplay data is separate from the sealed schema contract.
# Before first boot, inspect and apply the source-derived seed as documented in
# docs/operations/EPIC_ZONE_SEED.md (it handles the initially empty zones table).
python3 scripts/epic_zone_seed.py check
# Seed the tracked help and login content. Inspect the counts first, then
# confirm the live import when prompted.
./scripts/import_help_to_prod.sh --local --dry-run
./scripts/import_help_to_prod.sh --local
[!IMPORTANT]
bootstrap_multithread_safe.sqlis for an empty database. To upgrade an existing populated database, back it up, restore a disposable development clone, and use a separate owner-readable configuration file that targets only that clone. Never test schema changes on a live database.
For a cloned legacy database, copy the local configuration into a temporary
mode-0600 file, change DB_NAME and DB_ALLOWED_TARGETS to the clone, and run
the guarded legacy upgrade through MIGRATION_ENV_FILE:
migration_env=$(mktemp)
chmod 600 "$migration_env"
cp .env "$migration_env"
${EDITOR:-vi} "$migration_env"
MIGRATION_ENV_FILE="$migration_env" ./migrations/run_migration.sh
rm -f "$migration_env"
unset migration_env
To replace an allow-listed local/development database directly from a private
MySQL dump, use the guarded importer. It refuses active database connections,
creates an owner-only backup before mutation, translates MySQL 8's 0900
collation and stale view definers for MariaDB, runs both migration layers, verifies
the runtime contract, and restores the backup automatically if any stage fails:
chmod 600 /path/to/legacy.sql
python3 scripts/import_legacy_dump.py \
--env-file .env \
--replace \
/path/to/legacy.sql
The canonical game schema is still checked exactly. Non-runtime tables from a
combined game/website dump may coexist and are preserved, but they are not treated
as part of the game server's schema contract. When a launcher-era server_reboots
table contains values the canonical lifecycle schema cannot represent, its complete
raw rows remain in legacy_import_server_reboots before the runtime projection is
normalized. Likewise, when required uniqueness cleanup collapses duplicate legacy
item metadata, the complete source rows remain in corresponding
legacy_import_player_* archive tables.
For the complete source-to-destination map, safety model, audited reference-import results, and recovery runbook, see Legacy Dump Import Guide.
The legacy runner is re-runnable, records verified baseline adoption, and checks runtime schema compatibility before it succeeds. If any step fails, treat the clone as partially migrated and restore it from the known backup before retrying.
4. Build and start
make
./scripts/start_mud.sh --dev
The root build places every compiled artifact below bin/: the staged server
is bin/server/dms_new, the editor is bin/areas/editor/de, and the
area-generation tools are under bin/areas/tools/. The startup supervisor
promotes the server to bin/server/dms, rotates prior executables under
bin/server/history/, regenerates combined areas/world.* files, applies
pending immutable migrations for an allow-listed local database, verifies the
exact runtime schema on every restart, and only then starts it. Production
startup performs the verification read-only; use the migration runbook to
upgrade production. Without a configured user service it runs in the
background and writes console output to logs/duris-console.log. The --dev
quick-start listener is port 4000 and cannot select the production runtime role.
Production deployments use the checked-in systemd service rather than the local user service. Its installer requires an explicit production configuration check, enables boot startup, and supervises every exit with an unlimited restart policy. See Production systemd service for installation and cutover instructions.
For a foreground development session on port 4000, use this instead of
start_mud.sh:
./scripts/cycle_mud.sh --dev
For a fast development boot using only the tracked world data in areas_mini/,
run:
./scripts/cycle_mud.sh --minimal
--minimal implies development mode and port 4000. It validates the minimal
dataset, skips full areas/world.* generation and full-world runtime systems,
but keeps the player load/save and critical-command pipelines available so a
configured test character can log in, play, and disconnect cleanly. Use
./scripts/start_mud.sh --minimal for the corresponding background launcher.
Troubleshooting
If the server stops during boot, inspect logs/log/status and
logs/duris-console.log. The most common checks are:
- MySQL initialization failed: confirm
.envvalues, that the selected database exists, and that the account can connect onDB_HOST:DB_PORT. The server logs the effective database target during boot and aborts when the required schema is missing. - Redis connection failed: Redis is optional; set
REDIS=FALSEor remove the setting to run without Redis caches and world recovery. Player checkpoints remain available through their local coordinator and journal. If Redis is required, checkREDIS_HOST,REDIS_PORT, and that the service is reachable. - Missing world files or tools: run
make build-area-toolsfollowed bymake world, then restart. Combinedareas/world.*files are generated outputs and should not be edited by hand. - Port already in use: choose a different development port, or stop the
existing local instance. Keep development on a non-
7777port.
Operational log locations and restart behavior are documented in the runbook.
Connect
nc localhost 4000
| Listener | Standard start | --dev start |
|---|---|---|
| Plain telnet | 7777 | 4000 |
| TLS telnet | 7778 | 4001 |
| WebSocket and HTTP health | 4050 | 4050 |
The WebSocket port can be overridden with DURIS_WEBSOCKET_PORT. Once the game is
running, scripts/healthcheck.sh verifies both process and database-pool readiness.
For local TLS, run ./scripts/generate_localhost_cert.sh. It creates an ignored,
machine-local self-signed keypair under certs/; that fallback is accepted only
when ENVIRONMENT=local and LISTEN_ADDRESS is exactly 127.0.0.1 or ::1.
For a networked deployment, provide the ignored root files duris.crt and
duris.key; every private key must be owned by the server user and mode 0600
or stricter. Startup fails if that deployment certificate boundary is not met.
Development workflow
Run the complete developer/CI gate before handing off a change:
make test-all
make test-db
make test-all covers maintained builds, generated world data, Python
regressions, and native tests. make test-db additionally creates disposable
MySQL containers for schema contracts, immutable migration checks, and the full
historical 145-step legacy upgrade, replay, fresh-bootstrap equivalence, and
runtime-compatibility test. It never targets the database configured in .env.
During development, run the smallest relevant regression directly:
make -C src
python3 tests/async/test_wear_all_regression.py
Tests under tests/async/ are focused Python regression or source-contract
checks. The root harness generates required world data and runs these tests in
bounded parallel workers. Docker/MySQL suites remain an explicit make test-db
step, while externally provisioned migration checks must target a development
clone. See Testing for target details and focused-test
controls.
Format only touched C/C++ lines so legacy diffs stay reviewable:
./scripts/format.sh
./scripts/format.sh --check
Install the repository's pre-commit hook with ./scripts/install-hooks.sh.
Sanitizer and Valgrind workflows are covered in
Building and Valgrind.
Repository map
| Path | Purpose |
|---|---|
Makefile |
Root build, world-generation, and test entry points. |
bin/ |
Ignored compiled artifacts: executables, objects, packages, tests, and runtime history. |
src/ |
Server sources and Makefile; builds bin/server/dms_new with g++. |
areas/ |
World sources, compilers, and generated world.* boot files. |
lib/ |
Runtime configuration, help, boards, descriptions, and game data. |
migrations/ |
Fresh schema, upgrade runner, and schema-contract tools. |
migrations/tools/ |
Standalone legacy player/account conversion tools. |
scripts/ |
Launch, backup, formatting, debugging, and maintenance helpers. |
tests/async/ |
Focused regression, source-contract, and DB-backed tests. |
docs/ |
Architecture, operations, database, test, and builder guides. |
Runtime state belongs under Players/, Accounts/, Ships/, and logs/.
Do not commit player data, credentials, logs, generated binaries, or backup
archives.
Project releases use Semantic Versioning. The canonical version is stored in
the root VERSION file.
Documentation
Browse the project website and documentation library for searchable guides with source links, code highlighting, and diagrams.
| Guide | Covers |
|---|---|
| Architecture | Process model, boot gate, game loop, typed persistence, recovery. |
| Codebase | Module-by-module map of the server sources. |
| Building | Build flags, areas, sanitizers, verification. |
| Database | Connections, reads, typed writes, reconciliation, schema, migrations. |
| Configuration | Environment variables, Redis, networking, and diagnostics. |
| Docker deployment | End-to-end local Compose setup, lifecycle, data, and troubleshooting. |
| Runbook | Restarts, logs, backups, recovery, operations. |
| Testing | Test layout, commands, and conventions. |
| Immutable migrations | Baseline adoption, ordered checksums, exact resume. |
| Runtime compatibility | Pre-mutation boot verification and lookup publication. |
| Data lifecycle | Store inventory, pending policy, archive/export/erasure boundaries. |
| Critical commands | Operation identity, journal, inbox/results, outbox, replay, fences. |
| Formatting | Style, changed-line formatting, and editors. |
| Help system | Help sources, database import, and rendering. |
The complete index, including builder references and standalone diagrams, is
in docs/README_docs.md.