Skip to main content

Getting started

This page gets you from a fresh clone to a running server you can join from a vanilla Minecraft client.

Toolchain​

ToolVersionNotes
JDK25 (Eclipse Temurin)The only thing you install. Minestom's current release builds with Java 25.
Gradle9.8, via the wrapper./gradlew downloads it; never install Gradle separately.
Minestom2026.09.12-26.2 (pinned)For Minecraft 26.2 clients. Minestom's master branch changes often, so the exact version is pinned in gradle/libs.versions.toml.
Dockerany recentOnly for the local ClickHouse database (optional, see below).

If java isn't on your PATH, point JAVA_HOME at a JDK 25 and Gradle's toolchain support does the rest.

Clone, build, run​

git clone [email protected]:Preckster/mcrpg.git
cd mcrpg
git checkout develop

./gradlew build # compile, Javadoc (doclint on), tests, ArchUnit rules
./gradlew :server:run # start the server on 0.0.0.0:25565

To run without Gradle in the loop (for example on a test machine):

./gradlew :server:installDist
server/build/install/server/bin/server

Then join localhost:25565 from a Minecraft 26.2 client. With the welcome module enabled (the default) you'll see the Welcome to Shatterveil title and the emblem in the server list.

:::tip Always build before you finish ./gradlew build is the gate. It runs Javadoc with doclint (a missing comment on a public member is an error), the whole test suite, and the ArchUnit architecture tests that enforce the ground rules. A change that doesn't build clean isn't done. :::

Configuration​

Everything is configured through environment variables; nothing secret is ever in git.

VariableDefaultMeaning
SHATTERVEIL_HOST0.0.0.0Bind address
SHATTERVEIL_PORT25565Port
SHATTERVEIL_ONLINE_MODEtrueMojang authentication. false only for offline LAN tests.
SHATTERVEIL_DATA_DIRdataRuntime state (git-ignored), e.g. data/modules.json and the audit spill file
SHATTERVEIL_DB_URL / _NAME / _USER / _PASSWORDunsetClickHouse connection (see data). Falls back to CLICKHOUSE_USER / CLICKHOUSE_PASSWORD, the names in deploy/clickhouse/.env.
SHATTERVEIL_ADMIN_TOKENS / _PORT / _HOSTunset / 8765 / 127.0.0.1The admin API. No tokens → the API doesn't listen.
SHATTERVEIL_DISCORD_WEBHOOK_AUDIT / _GAME / _SYSTEM / _ALERTSunsetDiscord delivery for the log pipeline

Without a database configured, the server still runs. Module state falls back to data/modules.json, roles and player records live in memory for the session, and audit events are spilled to a file so they aren't lost. Each component says so once in the log.

Local ClickHouse​

Every module that persists anything uses one ClickHouse database, running on the same machine as the server and bound to 127.0.0.1 only. For local development it's optional; for anything resembling a real server it's required.

cd deploy/clickhouse
cp .env.example .env && chmod 600 .env # set a long random password
docker compose up -d
docker compose ps # wait for "healthy"
ItemValue
Imageclickhouse/clickhouse-server:26.8.10.6 (26.8 LTS, pinned)
HTTP127.0.0.1:8123
Native127.0.0.1:9000
Databaseshatterveil
Userfrom .env (CLICKHOUSE_USER, CLICKHOUSE_PASSWORD)
DataDocker volume shatterveil-clickhouse-data

initdb/*.sql runs once, on the first start with an empty volume, and creates the schema_migrations table. After that every schema change ships as a migration inside the module that owns the table, applied by the data library at startup.

To check it's alive:

set -a; . ./.env; set +a
curl -s -u "$CLICKHOUSE_USER:$CLICKHOUSE_PASSWORD" \
http://127.0.0.1:8123/ --data-binary 'SELECT version()'

:::danger Never expose the database The ports are bound to localhost on purpose. Don't publish them on a public interface, a tunnel or the LAN. Only the data and log libraries connect to ClickHouse; modules, Hermes, the Discord bot and the web panel all go through those or through the admin API. :::

Memory is capped at 25% of RAM (config.d/shatterveil.xml) so the game server always has room. Backups (BACKUP DATABASE shatterveil TO File(...)) need a backup disk configured — that's on the roadmap before real player data exists.

Repository layout​

mcrpg/
├── settings.gradle.kts subprojects: server, libs/*, modules/*
├── build-logic/ Gradle convention plugins (toolchain 25, doclint,
│ JUnit 5, dependency rules)
├── gradle/libs.versions.toml every third-party version in one place
├── server/ the launcher: boots Minestom, the ModuleManager,
│ /module, and the kernel modules
├── libs/ shared libraries (never depend on modules)
│ ├── core-api/ commands/ permissions/ data/ log/
│ ├── security/ text/ palette/ testing/
├── modules/ one game system each (depend only on libs)
│ ├── admin-api/ logging/ permissions/ player-data/ welcome/
├── deploy/clickhouse/ the local database (Docker Compose)
├── docs/commands.md generated command reference
├── tools/ small scripts (server-icon rasteriser)
└── design/ design documents; 04-architecture-rules.md binds all code

A module and a library may share a directory name (libs/permissions and modules/permissions both exist). Gradle project identity is disambiguated by the convention plugin, and module jars are named module-<name>.jar.

Branch workflow​

  • All work happens on develop. Commit there, push there.
  • main is production. Nothing is merged to main without Preck's explicit go-ahead for that specific change. This applies to every repo in the project, including documentation-only changes.
  • Every update pushed to develop bumps the version — see Versioning & releases.

Check git branch --show-current before editing, not just before committing.

Commit messages​

Every commit explains itself:

  1. A short summary line.
  2. A blank line.
  3. A bullet body covering what changed and in which files, why, and anything to watch out for (follow-up work, breaking changes, restarts).

Changes to a CRITICAL module also include a security note: what input is trusted, and how it's validated. See Architecture › Security for what CRITICAL means.

Adding a module​

Copy modules/welcome/ — it's the reference module and is kept deliberately small. A module needs:

  1. One entry class implementing ShatterveilModule with a manifest.
  2. A META-INF/services/com.shatterveil.api.ShatterveilModule file naming that class (modules are discovered with ServiceLoader).
  3. A README.md (what it does, manifest, settings, commands, events, what happens when disabled, tests).
  4. A toggle test using ModuleTestSupport.assertToggleLeavesNothingBehind.
  5. An include("modules:<name>") line in settings.gradle.kts, and the module added as a runtimeOnly + testImplementation dependency in server/build.gradle.kts.

Then add a page for it under Modules & libraries on this site. The full checklist for a "done" module is in Architecture › When a module is done.