Skip to main content

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: HazelcastHazelcastService 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 APIKtorServerService starts only when isMasterNode is true. It exposes four endpoints:
MethodEndpointDescription
GET/api/instancesList all instances from the distributed map
POST/api/instancesCreate an instance — selects a target Wrapper and dispatches DeployInstanceTask
PUT/api/instances/{id}/stateUpdate instance state and heartbeat timestamp
POST/api/commands/executeExecute 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. ResilienceResilienceMembershipListener 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:
  1. Reads TemplateInstallationConfig from the instance Configuration
  2. Resolves templates from ./templates/<group>/<name>/ in priority order
  3. Copies resolved templates to ./running/<instance-id>/
  4. Scans files listed in Configuration.fileModifications
  5. 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:
KeyProviderDescription
screenScreenRuntimeProviderStarts a GNU screen session; pipes commands to session stdin
tmuxTmuxRuntimeProviderStarts a tmux session; pipes commands to session stdin
processProcessRuntimeProviderStarts 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

ModulePurpose
apiShared data classes (InstanceInfo, Configuration, TemplateInstallationConfig), task DTOs, RuntimeProvider and RuntimeRegistry interfaces, command abstractions
appCore orchestrator: HazelcastService, KtorServerService, TemplateManager, TaskRouter, CommandBootstrap, ManagementCommands, built-in runtime providers
loaderBootstrap classloader that downloads runtime dependencies and launches app — produces the distributable fat JAR
extensions/extension-apiExtension-facing interfaces: Extension, TemplateStorageProvider, TemplateVariableProvider, TemplateStorageRegistry, TemplateVariableRegistry, TemplateResolver
extensions/runtime-dockerDocker container runtime provider
extensions/storage-s3AWS S3 template storage backend
extensions/exampleReference 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.

Build docs developers (and LLMs) love