Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ jobs:
- name: Check Palworld frame rate against an engine response
run: python3 test-palworld-performance.py

- name: Check Minecraft JVM settings in the pinned image
run: python3 test-minecraft-jvm.py

- name: Check ARK world migration in the pinned vendor image
run: python3 test-ark-volume-migration.py

Expand Down
12 changes: 11 additions & 1 deletion .schema/gametemplate.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -943,6 +943,10 @@
"items": {
"description": "ConfigField is a single user-tunable setting surfaced in the wizard.",
"properties": {
"applyOnRestart": {
"description": "ApplyOnRestart stages this environment value for the next container start.\nEdits and clears update the config Secret without rolling the game Pod.\nConfig files that reference the field are refreshed by their init container\non the next Pod start. Other fields keep their usual rollout behavior.",
"type": "boolean"
},
"autoFromMemoryLimit": {
"description": "AutoFromMemoryLimit, when set, computes this field's value from the\ngame container's effective memory limit whenever neither\nGameServer.spec.config nor Default provides one: floor(limit \u00d7\npercent / 100), rendered as whole mebibytes with an \"M\" suffix\n(e.g. \"6144M\"). Lets templates size game memory settings (like a\nJVM heap) to whatever resources the server was given instead of a\nstatic default \u2014 an explicit user value always wins, and a template\nwithout a memory limit leaves the field unset.",
"properties": {
Expand Down Expand Up @@ -1033,7 +1037,13 @@
"name",
"type"
],
"type": "object"
"type": "object",
"x-kubernetes-validations": [
{
"message": "applyOnRestart requires an environment field",
"rule": "!has(self.applyOnRestart) || !self.applyOnRestart || !has(self.target) || self.target == 'env'"
}
]
},
"type": "array"
},
Expand Down
36 changes: 19 additions & 17 deletions minecraft-java/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,25 @@ one-click install a mod or plugin by name without hunting for a URL. The
chosen file downloads through the same allowlist (Modrinth's CDN is listed).
**From URL** install remains available for anything not on Modrinth.

## Memory / JVM heap

Left empty (the default), the wizard's **JVM max heap** / **JVM initial
heap** fields (configSchema `MAX_MEMORY` / `INIT_MEMORY`) are sized
automatically to **75% of the container memory limit** — pick 8Gi of
memory and the heap becomes 6144M, and it re-tracks the limit whenever
you resize the server. The remaining 25% is headroom for non-heap JVM
memory: the limit only caps the cgroup — it does not size the heap — so
a heap at or above the limit gets the server OOM-killed, and a limit far
above the heap wastes RAM the JVM will never touch. Automatic sizing
needs gameplane ≥ 0.2.0-beta.6 (`gameplaneMinVersion` gates this bundle
accordingly).

Setting either field explicitly (e.g. `2G`, `3072M`) turns the automatic
sizing off for that field and feeds the value straight to the itzg
image's `-Xmx`/`-Xms` plumbing; keep an explicit max heap roughly 25%
below the container limit for the same reason.
## JVM startup settings (2.12.0)

Install the Gameplane CRDs and operator with support for `applyOnRestart`
before updating the catalog module to 2.12.0 (minimum Gameplane 0.4.1).
Adopting this template on a running server can restart it once. A server
using the OnRequest template policy can schedule that adoption. Once adopted,
saving or clearing heap sizes, extra JVM options or Aikar's flags leaves the
running game in place. Restart it when ready to use the new values.

Each empty heap field independently defaults to 75% of the container memory
limit, rounded down to whole MiB. A 4Gi limit gives 3072M for each automatic
field. To set the maximum to 2G, also set an initial heap of 2G or less.
Leave space for the JVM's non-heap allocations.

Fixed sizes such as `512M` and `2G` become `-Xms` and `-Xmx`. A value such
as `37.5%` selects the JVM's InitialRAMPercentage or MaxRAMPercentage mode.
`JVM_OPTS` and `JVM_XX_OPTS` pass space-separated arguments through to the
image. Set heap sizes in their dedicated fields so a later argument does
not override the preview. `USE_AIKAR_FLAGS` enables the image's own preset.

## Logs (including install)

Expand Down
10 changes: 8 additions & 2 deletions minecraft-java/locales/.review.json
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,15 @@
"config.DIFFICULTY.options.normal": "317b32c14369",
"config.DIFFICULTY.options.peaceful": "2a4baa647f52",
"config.HARDCORE.displayName": "38956488bd78",
"config.INIT_MEMORY.description": "b4a5e6eacd0a",
"config.INIT_MEMORY.description": "ff410f20c96d",
"config.INIT_MEMORY.displayName": "cfd0b63a804d",
"config.JVM_OPTS.description": "0bb24aebd8fb",
"config.JVM_OPTS.displayName": "64dc680da4d7",
"config.JVM_XX_OPTS.description": "29fc716839c6",
"config.JVM_XX_OPTS.displayName": "df73f9d53612",
"config.LEVEL_SEED.description": "cb50f3d0826b",
"config.LEVEL_SEED.displayName": "2af8986090a5",
"config.MAX_MEMORY.description": "f1fbbbcce1d6",
"config.MAX_MEMORY.description": "59c3fc2af7ce",
"config.MAX_MEMORY.displayName": "5a53a9f3d0ad",
"config.MAX_PLAYERS.displayName": "5a6ba90743a6",
"config.MODE.displayName": "ede76ceab6f1",
Expand All @@ -81,6 +85,8 @@
"config.ONLINE_MODE.description": "e0d4a3c8f231",
"config.ONLINE_MODE.displayName": "6443cff7f789",
"config.PVP.displayName": "82f91ef7cf31",
"config.USE_AIKAR_FLAGS.description": "6a12bb75a5f6",
"config.USE_AIKAR_FLAGS.displayName": "d4a0c9b99aab",
"config.VIEW_DISTANCE.displayName": "e308828f4f7c",
"config.WHITELIST_ENABLED.displayName": "adda297584e7",
"metrics.difficulty.displayName": "be44133ed57f",
Expand Down
28 changes: 26 additions & 2 deletions minecraft-java/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,29 @@
"description": "Label of configuration field HARDCORE (template.yaml spec.configSchema)"
},
"config.INIT_MEMORY.description": {
"defaultMessage": "Starting heap size (-Xms), at most JVM max heap. Leave empty to match the automatic max heap (a fixed heap avoids GC resizes).",
"defaultMessage": "Starting heap, for example 512M or 37.5%. Leave empty for 75% of the container memory limit. Keep the initial heap at or below the maximum.",
"description": "Help text of configuration field INIT_MEMORY (template.yaml spec.configSchema)"
},
"config.INIT_MEMORY.displayName": {
"defaultMessage": "JVM initial heap",
"description": "Label of configuration field INIT_MEMORY (template.yaml spec.configSchema)"
},
"config.JVM_OPTS.description": {
"defaultMessage": "Space-separated Java options, for example -Dfile.encoding=UTF-8.",
"description": "Help text of configuration field JVM_OPTS (template.yaml spec.configSchema)"
},
"config.JVM_OPTS.displayName": {
"defaultMessage": "Additional JVM options",
"description": "Label of configuration field JVM_OPTS (template.yaml spec.configSchema)"
},
"config.JVM_XX_OPTS.description": {
"defaultMessage": "Space-separated JVM tuning options, for example -XX:+PrintCommandLineFlags.",
"description": "Help text of configuration field JVM_XX_OPTS (template.yaml spec.configSchema)"
},
"config.JVM_XX_OPTS.displayName": {
"defaultMessage": "Additional JVM tuning options",
"description": "Label of configuration field JVM_XX_OPTS (template.yaml spec.configSchema)"
},
"config.LEVEL_SEED.description": {
"defaultMessage": "Leave blank for a random seed.",
"description": "Help text of configuration field LEVEL_SEED (template.yaml spec.configSchema)"
Expand All @@ -272,7 +288,7 @@
"description": "Label of configuration field LEVEL_SEED (template.yaml spec.configSchema)"
},
"config.MAX_MEMORY.description": {
"defaultMessage": "Java heap ceiling (-Xmx, e.g. 2G, 3072M). Leave empty to size it automatically to 75% of the container memory limit — it then tracks the limit whenever you resize the server. If you set it yourself, keep roughly 25% below the limit: the JVM needs headroom for non-heap memory, and exceeding the limit gets the server OOM-killed.",
"defaultMessage": "Heap ceiling, for example 2G or 65%. Leave empty for 75% of the container memory limit. Allow room for memory outside the Java heap.",
"description": "Help text of configuration field MAX_MEMORY (template.yaml spec.configSchema)"
},
"config.MAX_MEMORY.displayName": {
Expand Down Expand Up @@ -323,6 +339,14 @@
"defaultMessage": "PvP enabled",
"description": "Label of configuration field PVP (template.yaml spec.configSchema)"
},
"config.USE_AIKAR_FLAGS.description": {
"defaultMessage": "Let the image select its Aikar garbage collection flags for this JVM and heap.",
"description": "Help text of configuration field USE_AIKAR_FLAGS (template.yaml spec.configSchema)"
},
"config.USE_AIKAR_FLAGS.displayName": {
"defaultMessage": "Use Aikar''s flags",
"description": "Label of configuration field USE_AIKAR_FLAGS (template.yaml spec.configSchema)"
},
"config.VIEW_DISTANCE.displayName": {
"defaultMessage": "View distance (chunks)",
"description": "Label of configuration field VIEW_DISTANCE (template.yaml spec.configSchema)"
Expand Down
28 changes: 26 additions & 2 deletions minecraft-java/locales/fr.json
Original file line number Diff line number Diff line change
Expand Up @@ -256,13 +256,29 @@
"description": "Label of configuration field HARDCORE (template.yaml spec.configSchema)"
},
"config.INIT_MEMORY.description": {
"defaultMessage": "Taille initiale du tas (-Xms), au maximum égale au maximum de la JVM. Laisser vide pour correspondre au tas maximum automatique (un tas fixe évite les redimensionnements du GC).",
"defaultMessage": "Taille initiale du tas, par exemple 512M ou 37.5%. Laisser vide pour utiliser 75 % de la limite mémoire du conteneur. La taille initiale doit être inférieure ou égale au maximum.",
"description": "Help text of configuration field INIT_MEMORY (template.yaml spec.configSchema)"
},
"config.INIT_MEMORY.displayName": {
"defaultMessage": "Tas initial de la JVM",
"description": "Label of configuration field INIT_MEMORY (template.yaml spec.configSchema)"
},
"config.JVM_OPTS.description": {
"defaultMessage": "Options Java séparées par des espaces, par exemple -Dfile.encoding=UTF-8.",
"description": "Help text of configuration field JVM_OPTS (template.yaml spec.configSchema)"
},
"config.JVM_OPTS.displayName": {
"defaultMessage": "Options JVM supplémentaires",
"description": "Label of configuration field JVM_OPTS (template.yaml spec.configSchema)"
},
"config.JVM_XX_OPTS.description": {
"defaultMessage": "Options de réglage de la JVM séparées par des espaces, par exemple -XX:+PrintCommandLineFlags.",
"description": "Help text of configuration field JVM_XX_OPTS (template.yaml spec.configSchema)"
},
"config.JVM_XX_OPTS.displayName": {
"defaultMessage": "Options de réglage supplémentaires de la JVM",
"description": "Label of configuration field JVM_XX_OPTS (template.yaml spec.configSchema)"
},
"config.LEVEL_SEED.description": {
"defaultMessage": "Laisser vide pour une graine aléatoire.",
"description": "Help text of configuration field LEVEL_SEED (template.yaml spec.configSchema)"
Expand All @@ -272,7 +288,7 @@
"description": "Label of configuration field LEVEL_SEED (template.yaml spec.configSchema)"
},
"config.MAX_MEMORY.description": {
"defaultMessage": "Plafond du tas Java (-Xmx, ex. 2G, 3072M). Laisser vide pour le dimensionner automatiquement à 75 % de la limite de mémoire du conteneur : il suit alors la limite à chaque redimensionnement du serveur. Si vous le définissez vous-même, gardez environ 25 % de marge sous la limite : la JVM a besoin de place pour la mémoire hors tas, et dépasser la limite entraîne l’arrêt du serveur par OOM-kill.",
"defaultMessage": "Plafond du tas, par exemple 2G ou 65%. Laisser vide pour utiliser 75 % de la limite mémoire du conteneur. Prévoir de la mémoire en dehors du tas Java.",
"description": "Help text of configuration field MAX_MEMORY (template.yaml spec.configSchema)"
},
"config.MAX_MEMORY.displayName": {
Expand Down Expand Up @@ -323,6 +339,14 @@
"defaultMessage": "PvP activé",
"description": "Label of configuration field PVP (template.yaml spec.configSchema)"
},
"config.USE_AIKAR_FLAGS.description": {
"defaultMessage": "Laisser l’image choisir les options de ramasse-miettes d’Aikar adaptées à la JVM et au tas.",
"description": "Help text of configuration field USE_AIKAR_FLAGS (template.yaml spec.configSchema)"
},
"config.USE_AIKAR_FLAGS.displayName": {
"defaultMessage": "Utiliser les options d’Aikar",
"description": "Label of configuration field USE_AIKAR_FLAGS (template.yaml spec.configSchema)"
},
"config.VIEW_DISTANCE.displayName": {
"defaultMessage": "Distance d’affichage (chunks)",
"description": "Label of configuration field VIEW_DISTANCE (template.yaml spec.configSchema)"
Expand Down
4 changes: 2 additions & 2 deletions minecraft-java/module.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@
apiVersion: gameplane.local/module/v1
name: minecraft-java
displayName: Minecraft (Java Edition)
version: 2.11.0
version: 2.12.0
game: minecraft-java
categories: [Sandbox, Survival, Building, Modded, Creative]
summary: Vanilla / Paper / Spigot / Bukkit / Purpur / Forge / NeoForge / Fabric / Quilt servers (itzg/minecraft-server) with a version+loader picker spanning 1.16.5–latest, per-loader mod volumes, and in-app Modrinth browse.
homepage: https://www.minecraft.net/
license: MIT
gameplaneMinVersion: 0.2.0-beta.7
gameplaneMinVersion: 0.4.1
42 changes: 26 additions & 16 deletions minecraft-java/template.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ spec:
displayName: Minecraft (Java Edition)
game: minecraft-java
# Kept in lockstep with module.yaml's bundle version from 2.5.0 onward.
version: 2.11.0
version: 2.12.0
categories: [Sandbox, Survival, Building, Modded, Creative]
# Brand color the dashboard tints this game's icon/accents with.
accentColor: "#5b9a3e"
Expand Down Expand Up @@ -828,31 +828,41 @@ spec:
type: int
default: "10"

# The itzg image passes these to the JVM as -Xms/-Xmx. Left empty,
# the operator sizes both to 75% of the container memory limit
# (autoFromMemoryLimit — needs gameplane ≥ 0.2.0-beta.6; the
# remaining 25% is headroom for non-heap JVM memory, which is what
# keeps the cgroup from OOM-killing the server). Equal -Xms/-Xmx
# gives a fixed heap, which game servers prefer over GC-driven
# resizes.
- name: MAX_MEMORY
displayName: JVM max heap
description: >-
Java heap ceiling (-Xmx, e.g. 2G, 3072M). Leave empty to size it
automatically to 75% of the container memory limit — it then
tracks the limit whenever you resize the server. If you set it
yourself, keep roughly 25% below the limit: the JVM needs
headroom for non-heap memory, and exceeding the limit gets the
server OOM-killed.
Heap ceiling, for example 2G or 65%. Leave empty for 75% of the
container memory limit. Allow room for memory outside the Java heap.
type: string
applyOnRestart: true
autoFromMemoryLimit:
percent: 75

- name: INIT_MEMORY
displayName: JVM initial heap
description: >-
Starting heap size (-Xms), at most JVM max heap. Leave empty to
match the automatic max heap (a fixed heap avoids GC resizes).
Starting heap, for example 512M or 37.5%. Leave empty for 75% of the
container memory limit. Keep the initial heap at or below the maximum.
type: string
applyOnRestart: true
autoFromMemoryLimit:
percent: 75

- name: JVM_OPTS
displayName: Additional JVM options
description: Space-separated Java options, for example -Dfile.encoding=UTF-8.
type: string
applyOnRestart: true

- name: JVM_XX_OPTS
displayName: Additional JVM tuning options
description: Space-separated JVM tuning options, for example -XX:+PrintCommandLineFlags.
type: string
applyOnRestart: true

- name: USE_AIKAR_FLAGS
displayName: Use Aikar's flags
description: Let the image select its Aikar garbage collection flags for this JVM and heap.
type: bool
default: "false"
applyOnRestart: true
54 changes: 54 additions & 0 deletions test-minecraft-jvm.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Verify the Minecraft Java startup settings with their pinned image launcher."""

from pathlib import Path
import shlex
import subprocess
import unittest

import yaml


class MinecraftStartup(unittest.TestCase):
@classmethod
def setUpClass(cls):
path = Path(__file__).parent / "minecraft-java/template.yaml"
template = yaml.safe_load(path.read_text())["spec"]
cls.image = template["image"]
cls.fields = {f["name"]: f for f in template["configSchema"]}

def launch(self, **env):
args = ["docker", "run", "--rm", "--platform", "linux/amd64", "--network", "none",
"--tmpfs", "/data", "--entrypoint", "/image/scripts/start-finalExec"]
for name, value in {**env, "SETUP_ONLY": "true", "VERSION": "1.21.4",
"TYPE": "VANILLA", "SERVER": "server.jar"}.items():
args.extend(("-e", f"{name}={value}"))
output = subprocess.check_output([*args, self.image], text=True, encoding="utf-8")
command = next(line for line in output.splitlines() if line.startswith("SETUP_ONLY: java "))
return shlex.split(command)

def test_automatic_and_explicit_settings(self):
for name in ["INIT_MEMORY", "MAX_MEMORY", "JVM_OPTS", "JVM_XX_OPTS", "USE_AIKAR_FLAGS"]:
self.assertTrue(self.fields[name]["applyOnRestart"])
self.assertEqual(self.fields["USE_AIKAR_FLAGS"]["default"], "false")
# The operator sends whole MiB for each autoFromMemoryLimit field.
env = {}
for name in ["INIT_MEMORY", "MAX_MEMORY"]:
percent = self.fields[name]["autoFromMemoryLimit"]["percent"]
self.assertEqual(percent, 75)
env[name] = f"{4096 * percent // 100}M"
args = self.launch(**env)
self.assertIn("-Xms3072M", args)
self.assertIn("-Xmx3072M", args)
self.assertNotIn("-Daikars.new.flags=true", args)
args = self.launch(INIT_MEMORY="37.5%", MAX_MEMORY="2G", JVM_OPTS="-Dexample=true",
JVM_XX_OPTS="-XX:+PrintCommandLineFlags", USE_AIKAR_FLAGS="true")
for flag in ["-XX:InitialRAMPercentage=37.5", "-Xmx2G", "-Dexample=true",
"-XX:+PrintCommandLineFlags", "-Daikars.new.flags=true"]:
self.assertIn(flag, args)
args = self.launch(INIT_MEMORY="512M", MAX_MEMORY="75%")
self.assertIn("-Xms512M", args)
self.assertIn("-XX:MaxRAMPercentage=75", args)


if __name__ == "__main__":
unittest.main()
Loading