Documentation Index
Fetch the complete documentation index at: https://mintlify.com/ohemilyy/universe/llms.txt
Use this file to discover all available pages before exploring further.
Universe runs as a single fat JAR that becomes either a Master or a Wrapper based on the isMasterNode flag in config.json. The Master holds cluster state in Hazelcast distributed maps and exposes a Ktor REST API. Wrappers join the same Hazelcast cluster, consume tasks dispatched by the Master, resolve templates, allocate ports, and start processes through pluggable RuntimeProvider implementations. Because both roles share the same binary, a Master node is always also a Wrapper — it hosts instances locally while coordinating the rest of the cluster.
Cluster topology
┌─────────────────────────────────────────────────────────────┐
│ Master Node │
│ ┌─────────────┐ ┌─────────────┐ ┌─────────────────────┐ │
│ │ Ktor REST │ │ Hazelcast │ │ Console Commands │ │
│ │ API │ │ IMap/Exec │ │ (Cloud v2) │ │
│ └─────────────┘ └─────────────┘ └─────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ POST /api/instances DeployInstanceTask instance create │
│ PUT /api/instances StopInstanceTask instance stop │
│ POST /api/commands TemplateSyncTask template sync │
└─────────────────────────────────────────────────────────────┘
│
│ Hazelcast Cluster
▼
┌─────────────────────────────────────────────────────────────┐
│ Wrapper Node(s) │
│ ┌─────────────┐ ┌─────────────┐ ┌──────────────────────┐ │
│ │ TaskRouter │ │ Template │ │ RuntimeProvider │ │
│ │ (IExecutor) │ │ Manager │ │ (screen/tmux/docker) │ │
│ └─────────────┘ └─────────────┘ └──────────────────────┘ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Receive Tasks Copy Templates Start Processes │
│ Route Actions Replace Variables Pipe Stdin │
└─────────────────────────────────────────────────────────────┘
How the Master works
The Master node starts three subsystems on launch:
Hazelcast — HazelcastService forms the cluster. ClusterStateService maintains two distributed maps:
IMap<String, Configuration> ("configurations") — loaded from ./configuration/*.json on the Master
IMap<String, InstanceInfo> ("instances") — live state for every instance in the cluster
Ktor REST API — KtorServerService starts only when isMasterNode is true. It exposes four endpoints:
| Method | Endpoint | Description |
|---|
GET | /api/instances | List all instances from the distributed map |
POST | /api/instances | Create an instance — selects a target Wrapper and dispatches DeployInstanceTask |
PUT | /api/instances/{id}/state | Update instance state and heartbeat timestamp |
POST | /api/commands/execute | Execute any console command and return captured output |
Console command system — Built on Cloud v2 (org.incendo.cloud). CommandBootstrap starts a daemon thread reading from System.in; ManagementCommands registers all available commands. Every command dispatched through the console is also reachable via POST /api/commands/execute, enabling identical behavior from the CLI and the REST API.
Resilience — ResilienceMembershipListener watches for Hazelcast memberRemoved events. When a Wrapper disconnects, the Master sets the affected InstanceInfo entries to state = OFFLINE but does not remove them. This preserves instance metadata for external services (such as a Minecraft plugin) to reconcile on reconnect.
How Wrappers work
Every node (including the Master) consumes IExecutorService tasks. TaskRouter deserializes each inbound task and routes it to the correct handler:
DeployInstanceTask — triggers TemplateManager to resolve and copy templates, then calls the appropriate RuntimeProvider to start the instance process.
StopInstanceTask — calls RuntimeProvider.stop() on the target instance.
ExecuteCommandTask — calls RuntimeProvider.executeCommand() to pipe a string into the instance process stdin.
TemplateSyncTask — receives a zipped template archive from another node and extracts it into the local ./templates/ directory.
Tasks are serialized as Gson JSON strings (plain String payloads) for Hazelcast IExecutorService dispatch — not Hazelcast-native serialization.
Template management
TemplateManager handles the full template lifecycle for each instance:
- Reads
TemplateInstallationConfig from the instance Configuration
- Resolves templates from
./templates/<group>/<name>/ in priority order
- Copies resolved templates to
./running/<instance-id>/
- Scans files listed in
Configuration.fileModifications
- Replaces variables with actual instance values
Built-in template variables include %PORT%, %INSTANCE_ID%, and %MASTER_IP%. Extensions can contribute additional variables by implementing TemplateVariableProvider and registering with TemplateVariableRegistry.
Port allocation
Before starting an instance, PortAllocator scans Configuration.availablePorts from min to max, testing each port for bind availability. The lowest free port is assigned to the instance.
Runtime providers
At startup, three runtime providers are registered in RuntimeRegistry:
| Key | Provider | Description |
|---|
screen | ScreenRuntimeProvider | Starts a GNU screen session; pipes commands to session stdin |
tmux | TmuxRuntimeProvider | Starts a tmux session; pipes commands to session stdin |
process | ProcessRuntimeProvider | Starts a raw OS process |
The docker runtime is available via the runtime-docker extension. Custom runtimes are added by implementing RuntimeProvider in an extension and calling registry.register("key", provider) in onLoad().
Module breakdown
| Module | Purpose |
|---|
api | Shared data classes (InstanceInfo, Configuration, TemplateInstallationConfig), task DTOs, RuntimeProvider and RuntimeRegistry interfaces, command abstractions |
app | Core orchestrator: HazelcastService, KtorServerService, TemplateManager, TaskRouter, CommandBootstrap, ManagementCommands, built-in runtime providers |
loader | Bootstrap classloader that downloads runtime dependencies and launches app — produces the distributable fat JAR |
extensions/extension-api | Extension-facing interfaces: Extension, TemplateStorageProvider, TemplateVariableProvider, TemplateStorageRegistry, TemplateVariableRegistry, TemplateResolver |
extensions/runtime-docker | Docker container runtime provider |
extensions/storage-s3 | AWS S3 template storage backend |
extensions/example | Reference extension implementation |
Extension system
Extensions are self-registering JARs placed in ./extensions/. ExtensionService discovers and loads them at startup, then calls onLoad() on each. Extensions receive Guice-injected registries and use them to contribute runtimes, storage backends, or variable providers.
Extensions must depend only on :api and :extensions:extension-api. Depending on :app is not allowed and will cause classloader issues.
A minimal runtime extension:
class MyExtension : Extension {
override fun id() = "my-extension"
override fun version() = "1.0.0"
@Inject lateinit var registry: RuntimeRegistry
override fun onLoad() {
registry.register("my-runtime", MyRuntimeProvider())
}
}
See Extensions overview for the full extension API, and Building extensions for a step-by-step guide.