Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/universeclouddev/Universe/llms.txt

Use this file to discover all available pages before exploring further.

Universe ships as a single fat JAR produced by the loader module. The loader bootstraps a custom classloader, extracts the embedded app.jarinjar, resolves and downloads runtime dependencies listed in dependencies.txt, and then invokes the orchestrator. Everything — Master API, Wrapper runtime, and console — lives in that one file, so getting a node running takes only a few minutes once the JAR is built.

Prerequisites

  • JDK 25+ and Gradle 9.5+ (required to build from source)
  • Linux or macOS recommended for the built-in screen and tmux runtimes
  • Docker (optional) if you prefer the container quick-start

1

Build the JAR

Clone the repository and run the Gradle build. Compiled artifacts are automatically copied to .built/ by the build script.
./gradlew build
After a successful build you will find:
.built/
  universe-loader-0.0.1.jar   ← the only file you need to run Universe
  minecraft-modern-*.jar
  minecraft-velocity-*.jar
  ...
You can distribute or copy universe-loader-0.0.1.jar to any remote node. It is entirely self-contained.
2

Run the JAR for the first time

Navigate to the directory where you want Universe to store its data, then launch the loader:
java -jar .built/universe-loader-0.0.1.jar
On first run, Universe detects missing configuration and creates the following directory structure:
./config.json          ← node identity and cluster settings
./database.json        ← database provider settings
./configuration/       ← instance configuration files (.json)
./templates/           ← template file trees (<group>/<name>/)
./running/             ← active instance working directories
./extensions/          ← extension JARs and their configs
./logs/universe.log    ← SLF4J/Logback log file
The node will start in Master mode by default (isMasterNode: true in the generated config.json). Stop the process (Ctrl+C or type stop) and edit the generated files before running again.
3

Edit ./config.json

The main node configuration controls the node’s identity, cluster membership, and which role it assumes. Open ./config.json and review every field:
{
  "address": "127.0.0.1",
  "port": 6000,
  "apiPort": 7000,
  "nodeId": "node-1",
  "clusterName": "universe-cluster",
  "isMasterNode": true,
  "masterAddress": "127.0.0.1",
  "masterPort": 6000,
  "masterApiPort": 7000,
  "debug": false
}
FieldDescription
addressIP address this node advertises to the Hazelcast cluster
portHazelcast cluster port (default 6000)
apiPortKtor REST API port — only used when isMasterNode: true (default 7000)
nodeIdUnique string identifier for this node, used in instance assignment and template sync
clusterNameHazelcast cluster group name — all nodes in the same cluster must share this value
isMasterNodetrue → start REST API and run InstanceCountEnforcer; false → join as Wrapper only
masterAddressIP or hostname of the Master node (used by Wrapper nodes to join the Hazelcast cluster)
masterPortHazelcast port on the Master
masterApiPortREST API port on the Master (used by Minecraft plugins and tooling)
debugtrue → enables DEBUG-level console and INFO-level framework logging; falseWARN+ only (default false)
A Master node can also run instances locally. You do not need a separate Wrapper node for a single-machine deployment.
4

Create your first template directory

Templates are file trees stored under ./templates/<group>/<name>/. Universe copies the resolved tree into ./running/<instance-id>/ before launching the process, replacing any variables it finds in files listed under fileModifications.Create a minimal template for a server:
mkdir -p ./templates/server/base
# Add any files your process needs, for example:
cp /path/to/server.jar ./templates/server/base/server.jar
A minimal server.properties that uses built-in variables might look like:
server-port=%PORT%
server-ip=%HOST_ADDRESS%
Built-in template variables replaced at deploy time:
VariableValue
%PORT%Allocated instance port
%INSTANCE_ID%6-character alphanumeric instance ID
%MASTER_IP% / %MASTER_ADDRESS%Master node address
%MASTER_PORT%Master Hazelcast port
%MASTER_API_PORT%Master REST API port
%NODE_ID%Local node ID
%HOST_ADDRESS%Local host address (or runtime-specific override)
%CONFIGURATION_NAME%Configuration name
5

Create your first instance configuration

Instance configurations live in ./configuration/ as JSON files. Create ./configuration/default.json:
{
  "name": "default",
  "runtime": "screen",
  "command": "java -jar server.jar",
  "static": false,
  "instanceGroups": [],
  "nodes": ["node-1"],
  "hostAddress": "127.0.0.1",
  "availablePorts": { "min": 25565, "max": 25570 },
  "minimumServiceCount": 1,
  "environmentVariables": {},
  "templateInstallationConfig": {
    "allOf": [{ "name": "base", "group": "server", "storage": "local", "priority": 0 }],
    "allInGroups": [],
    "oneOf": [],
    "oneInGroups": [],
    "onTemplatePasteOverridePresentFiles": false
  },
  "fileModifications": ["server.properties"],
  "properties": {}
}
FieldDescription
runtimeRuntime provider key: screen, tmux, process, docker, k8s
commandShell command executed inside the instance working directory
statictrue → working directory is preserved between restarts
nodesList of nodeId values eligible to host this configuration
availablePortsPort range Universe scans when allocating the instance port
minimumServiceCountInstanceCountEnforcer ensures at least this many instances are always running
templateInstallationConfig.allOfTemplates always copied, in priority order (ascending)
fileModificationsFiles scanned for %VARIABLE% replacement after template copy
propertiesCustom key→value pairs exposed as %KEY% template variables
6

Deploy an instance

Start the node:
java -jar .built/universe-loader-0.0.1.jar
You can create an instance either through the REST API or the console.
# Create a new instance from the "default" configuration
curl -X POST http://localhost:7000/api/instances \
  -H "Content-Type: application/json" \
  -d '{"configurationName": "default"}'
The response contains the new InstanceInfo object including the assigned id, allocatedPort, and initial state of CREATING.
# Stop an instance by ID
curl -X DELETE http://localhost:7000/api/instances/<id>
7

Verify the running instance

List all instances to confirm the new instance is ONLINE:
curl http://localhost:7000/api/instances
Example response:
[
  {
    "id": "a1b2c3",
    "configurationName": "default",
    "wrapperNodeId": "node-1",
    "hostAddress": "127.0.0.1",
    "allocatedPort": 25565,
    "state": "ONLINE",
    "lastHeartbeat": 1718000000000,
    "processPid": 12345,
    "runtime": "screen"
  }
]
Other useful endpoints:
# Health check
curl http://localhost:7000/api/ping

# Node info (version, uptime, resources)
curl http://localhost:7000/api/node

# Cluster node list
curl http://localhost:7000/api/cluster/nodes

# Tail last 100 log lines for an instance
curl "http://localhost:7000/api/instances/a1b2c3/logs?lines=100"

Docker Compose Quick-Start

Docker Compose is the fastest way to get a Master node running without building from source. The image is available at git.lunarlabs.dev/scala/universe:latest.
Create a docker-compose.yml in your working directory:
services:
  universe-master:
    image: git.lunarlabs.dev/scala/universe:latest
    container_name: universe-master
    stdin_open: true
    tty: true
    ports:
      - "7000:7000"   # REST API
      - "6000:6000"   # Hazelcast
    volumes:
      - ./data:/data

  # Optional: add a dedicated Wrapper node
  universe-wrapper:
    image: git.lunarlabs.dev/scala/universe:latest
    depends_on:
      - universe-master
    volumes:
      - ./wrapper-data:/data
For the Wrapper’s ./wrapper-data/config.json, set isMasterNode: false and point to the Master:
{
  "isMasterNode": false,
  "masterAddress": "universe-master",
  "masterPort": 6000,
  "masterApiPort": 7000
}
Start both services:
docker compose up -d
Interact with the Master console via attach, or use the REST API:
# Interactive console attach
docker attach universe-master

# Or via REST API (recommended)
curl -X POST http://localhost:7000/api/commands/execute \
  -H "Content-Type: application/json" \
  -d '{"command": "cluster status"}'
When running instances inside Docker containers, the spawned processes inherit the container environment. Use a runtime extension such as runtime-docker if you need isolated per-instance containers rather than bare processes inside the orchestrator container.

Build docs developers (and LLMs) love