diff --git a/README.md b/README.md index 8be1591..3c273ff 100644 --- a/README.md +++ b/README.md @@ -15,7 +15,7 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo | `homeassistant` | Home Assistant | StatefulSet; the only chart carrying Helm tests (`templates/tests/`) | | `matrix-synapse` | Matrix Synapse | StatefulSet plus an nginx delegation Deployment/Service | | `minecraft-router` | itzg/mc-router | Deployment + routing ConfigMap + NodePort Service (TCP only, no ingress); `mappings` is a required value | -| `minecraft-server` | itzg/minecraft-server | StatefulSet + PVC + Service + init-script ConfigMap (no ingress); service type is a value (`ClusterIP` by default, routed via `minecraft-router`). Published once per Java line — the image tag comes from `.Chart.AppVersion`, so the Java line is chosen by picking the chart version, and `minecraft.version` ships a default matching that line. Since chart 2.0.0 the tag is an **immutable dated upstream release** (`appVersion: -java8` / `-java17` / `-java21` / `-java25`, e.g. `2026.8.2-java8`) instead of the continuously rebuilt rolling line tag. Pilot for the strict `values.schema.json` (#68), which is now binding for every chart | +| `minecraft-server` | itzg/minecraft-server | StatefulSet + PVC + Service + init-script ConfigMap (no ingress); service type is a value (`ClusterIP` by default, routed via `minecraft-router`). **Only the newest Java line is maintained** (chart 5.0.0, #158): the directory carries one line, older ones stay published as frozen artifacts (java8/java17 at `4.0.0`, java21 at `4.1.0`) and receive no further chart changes — publishing once per line out of one directory is what made the lines drift. To run an older JVM with the current chart, set `image.tagOverride`; the chart then checks that `minecraft.version` fits the Java line of the **effective** tag and fails the render with both values named. Two cases pass unchecked on purpose: a tag without a `java` part and a `minecraft.version` that is not a plain numeric `x[.y[.z]]`. `image.tagOverride` is deliberately outside Renovate's reach — `renovate.json5` tracks `appVersion` only. Since chart 2.0.0 the tag is an **immutable dated upstream release** (`appVersion: -java8` / `-java17` / `-java21` / `-java25`, e.g. `2026.8.2-java8`) instead of the continuously rebuilt rolling line tag. Pilot for the strict `values.schema.json` (#68), which is now binding for every chart | | `outline` | Outline | StatefulSet, separate DB/Redis secrets | | `paperless-ngx` | Paperless-ngx | Largest chart: consume CronJob, SMB-connector PV/PVC, pre/post-consumption scripts ConfigMap | | `patchmon` | PatchMon | Two Deployments (frontend + backend), OIDC secret | diff --git a/minecraft-server/Chart.yaml b/minecraft-server/Chart.yaml index ce8124f..85dc145 100644 --- a/minecraft-server/Chart.yaml +++ b/minecraft-server/Chart.yaml @@ -5,16 +5,25 @@ icon: https://avatars.githubusercontent.com/u/988985 type: application -# The Java line of this chart version is chosen through appVersion, which is -# the image tag: the chart is published once per line (java8 / java17 / java21 / -# java25) and minecraft.version in values.yaml must match the line published -# here. +# ONLY THE NEWEST JAVA LINE IS MAINTAINED (chart 5.0.0, issue #158). +# appVersion is the image tag and therefore fixes the JVM. Up to chart 4.1.0 the +# chart was published once per line (java8 / java17 / java21 / java25) out of +# this single directory, which is what made the lines drift apart: whatever +# change landed while a line happened to lie in the directory reached that line +# only. That is over. This directory carries exactly one line - the newest - +# and it is the only one that gets chart changes. +# The older lines stay published as frozen artifacts and are still installable: +# java8 and java17 at 4.0.0, java21 at 4.1.0. They will not be updated again. +# To run an older JVM with the CURRENT chart, set image.tagOverride in values +# instead of installing a frozen chart version; the chart then checks that +# minecraft.version fits the line of that tag and fails the render if it does +# not. # Since chart 2.0.0 the tag is a DATED upstream release ("-java") # instead of the rolling line tag ("java"): the rolling tags are rebuilt # continuously, so a pod restart could pull a different image than the last one # without anything being recorded anywhere. The dated tags are immutable, so the # image is now pinned by the chart version like every other chart in this repo. -version: 4.1.0+up2026.8.2.java25 +version: 5.0.0+up2026.8.2.java25 appVersion: "2026.8.2-java25" maintainers: diff --git a/minecraft-server/templates/NOTES.txt b/minecraft-server/templates/NOTES.txt index e1b83f6..5d6bfd8 100644 --- a/minecraft-server/templates/NOTES.txt +++ b/minecraft-server/templates/NOTES.txt @@ -9,7 +9,9 @@ {{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}} {{- $budget = printf "%.0f seconds" (mulf $startup.periodSeconds $startup.failureThreshold) -}} {{- end -}} -{{- $line := regexFind "java[0-9]+" (printf "%v" .Chart.AppVersion) -}} +{{- $tag := include "minecraft-server.imageTag" . -}} +{{- $line := include "minecraft-server.javaLine" . -}} +{{- $tagOverridden := ne (include "minecraft-server.imageTagSource" .) ".Chart.AppVersion" -}} {{- $svc := printf "%s-%s-service" .Release.Name .Chart.Name -}} {{- $sfs := printf "%s-%s-sfs" .Release.Name .Chart.Name -}} {{- $pod := printf "%s-%s-sfs-0" .Release.Name .Chart.Name -}} @@ -26,7 +28,7 @@ {{- end -}} {{- end -}} {{- $memPinned := not (kindIs "invalid" (index $givenReq "memory")) -}} -minecraft-server {{ .Chart.AppVersion }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}. +minecraft-server {{ $tag }} — release {{ .Release.Name }} in namespace {{ .Release.Namespace }}. ACCESS — A MINECRAFT SERVER, NOT A WEB SERVICE This release has no Ingress, no certificate and no URL. Clients speak the @@ -76,25 +78,53 @@ ACCESS — A MINECRAFT SERVER, NOT A WEB SERVICE kubectl -n {{ .Release.Namespace }} port-forward svc/{{ $svc }} {{ .Values.server.service.port }}:{{ .Values.server.service.port }} -THIS CHART IS PUBLISHED ONCE PER JAVA LINE — THIS IS {{ $line | default "an unrecognized line" | upper }} - The JVM is fixed by the image tag, which comes from the chart's appVersion - ({{ .Chart.AppVersion }}), not from a value. This build runs - minecraft.version {{ .Values.minecraft.version }}. +THE JAVA LINE OF THIS RELEASE IS {{ $line | default "NOT RECOGNIZABLE FROM ITS TAG" | upper }} + The JVM is fixed by the image tag, not by a value of the game. This release + runs - Java 8 runs Minecraft up to 1.16.5, 1.18-1.20.4 need Java 17, 1.20.5-1.21.11 - need Java 21 and 26.1 and later need Java 25 (read off Mojang's version - manifest, javaVersion.majorVersion per release). Nothing in this chart checks - that minecraft.version fits the line: a mismatch renders and installs without - a word, and only the container fails afterwards — CrashLoopBackOff once the - startup budget{{ if $budget }} ({{ $budget }}){{ end }} is spent, with the reason in the log and + itzg/minecraft-server:{{ $tag }} + + with minecraft.version {{ .Values.minecraft.version }}. +{{- if $tagOverridden }} + + That tag comes from image.tagOverride, NOT from the chart's appVersion + ({{ .Chart.AppVersion }}). So the chart version no longer says what this release runs: + "helm list" reports {{ .Chart.Version }} while the pod is on the tag + above. That is the price of the override, and the reason it is not called + image.tag. +{{- else }} + + That tag comes from the chart's appVersion; image.tagOverride is empty, so + the chart version tells the whole truth about the image. +{{- end }} + + Since chart 5.0.0 THAT PAIRING IS CHECKED: if the line of the tag above does + not fit minecraft.version, the render fails and names both values. Two cases + are let through on purpose — a tag without a recognizable "java" part, and + a minecraft.version that is not a plain numeric x[.y[.z]] (a snapshot, a + pre-release, "LATEST"). In those two the old failure mode still applies: the + release installs, and only the container fails afterwards — CrashLoopBackOff + once the startup budget{{ if $budget }} ({{ $budget }}){{ end }} is spent, with the reason in the log and nowhere else: kubectl -n {{ .Release.Namespace }} logs {{ $pod }} - So moving the server across a line boundary is a change of CHART VERSION, not - a change of minecraft.version: pick the published chart of the target line and - set minecraft.version to match it. Changing only minecraft.version leaves the - old JVM in place. + The boundaries are Mojang's (javaVersion.majorVersion in the piston-meta + version manifest, per release): + + up to 1.16.5 Java 8 + 1.17 - 1.17.1 Java 16 + 1.18 - 1.20.4 Java 17 + 1.20.5 - 1.21.11 Java 21 + 26.1 and later Java 25 + + ONLY THE NEWEST LINE IS MAINTAINED. This chart directory carries one line and + gets chart changes only for that one; java8 and java17 stay frozen at chart + 4.0.0, java21 at 4.1.0. So moving the server across a line boundary is no + longer a change of chart version: set image.tagOverride to a tag of the target + line AND minecraft.version to a release of it, in the same upgrade. Changing + only one of the two now fails the render instead of leaving a broken pod + behind. {{- if eq $replicas "0" }} SCALED TO ZERO diff --git a/minecraft-server/templates/_helpers.tpl b/minecraft-server/templates/_helpers.tpl index 857e772..1924406 100644 --- a/minecraft-server/templates/_helpers.tpl +++ b/minecraft-server/templates/_helpers.tpl @@ -183,6 +183,133 @@ last one silently won. Fail loudly instead. {{- end -}} {{- end -}} +{{/* +The tag of the itzg/minecraft-server image this release actually runs. + +Default: .Chart.AppVersion, exactly as before chart 5.0.0. image.tagOverride +replaces it when set - that is the one way to run a Java line other than the +one this chart version publishes, now that only the newest line is maintained +(see Chart.yaml). The value is deliberately NOT called image.tag: it is an +emergency exit, not a knob to turn in passing, and it is not managed by +Renovate (renovate.json5 tracks appVersion only). +*/}} +{{- define "minecraft-server.imageTag" -}} +{{- $override := "" -}} +{{- if kindIs "map" .Values.image -}} +{{- if not (kindIs "invalid" .Values.image.tagOverride) -}} +{{- $override = printf "%v" .Values.image.tagOverride | trim -}} +{{- end -}} +{{- end -}} +{{- if $override -}}{{ $override }}{{- else -}}{{ .Chart.AppVersion }}{{- end -}} +{{- end -}} + +{{/* +Where the effective tag came from - used in the guard's message so the reader +knows which of the two values to change. +*/}} +{{- define "minecraft-server.imageTagSource" -}} +{{- $override := "" -}} +{{- if kindIs "map" .Values.image -}} +{{- if not (kindIs "invalid" .Values.image.tagOverride) -}} +{{- $override = printf "%v" .Values.image.tagOverride | trim -}} +{{- end -}} +{{- end -}} +{{- if $override -}}image.tagOverride{{- else -}}.Chart.AppVersion{{- end -}} +{{- end -}} + +{{/* +The Java line the effective image tag carries ("java25", "java17", ...), or the +empty string when the tag does not name one. + +regexFind rather than a suffix comparison, so flavoured tags keep working: +"2026.8.2-java25", "java21-alpine" and "latest-java17-jdk" all resolve. +*/}} +{{- define "minecraft-server.javaLine" -}} +{{- regexFind "java[0-9]+" (include "minecraft-server.imageTag" .) -}} +{{- end -}} + +{{/* +The Java line minecraft.version requires, or the empty string when the value is +not a version this chart can compare. + +The boundaries are Mojang's, not this chart's: they are javaVersion.majorVersion +from the piston-meta version manifest, swept over all 102 releases on 2026-09-02. +The transitions are exactly four, and monotonic in release order: + + 1.0 - 1.16.5 java 8 (1.6.1-1.6.4 predate the field and carry none) + 1.17 - 1.17.1 java 16 + 1.18 - 1.20.4 java 17 + 1.20.5 - 1.21.11 java 21 + 26.1 and later java 25 + +DELIBERATE GAP: a minecraft.version that is not a plain numeric "x", "x.y" or +"x.y.z" - a snapshot ("26w13a"), a pre-release ("1.21.11-pre1"), "LATEST" - +yields the empty string and is let through unchecked. The chart cannot compare +it, and a guard that rejects everything it does not understand gets worked +around instead of respected. The mismatch then still surfaces the old way: +CrashLoopBackOff once the startup budget is spent, with the reason in the log. +*/}} +{{- define "minecraft-server.requiredJavaLine" -}} +{{- $v := printf "%v" .Values.minecraft.version | trim -}} +{{- if regexMatch "^[0-9]+(\\.[0-9]+){0,2}$" $v -}} +{{- $n := len (splitList "." $v) -}} +{{- $sv := $v -}} +{{- if eq $n 1 -}}{{- $sv = printf "%s.0.0" $v -}}{{- else if eq $n 2 -}}{{- $sv = printf "%s.0" $v -}}{{- end -}} +{{- if semverCompare "< 1.17.0" $sv -}}java8 +{{- else if semverCompare "< 1.18.0" $sv -}}java16 +{{- else if semverCompare "< 1.20.5" $sv -}}java17 +{{- else if semverCompare "< 26.1.0" $sv -}}java21 +{{- else -}}java25 +{{- end -}} +{{- end -}} +{{- end -}} + +{{/* +Guard: minecraft.version must fit the Java line of the effective image tag. + +Until chart 5.0.0 nothing checked this - a mismatch rendered and installed +without a word, and only the container failed afterwards. It now fails at render +time, which is a behaviour change for an installation that already runs a +mismatched pair (it does not run: it fails earlier and visibly instead). + +The check runs ALWAYS, not only when image.tagOverride is set: Helm merges user +values into the chart defaults indistinguishably, so a template cannot tell a +deliberately set minecraft.version from the chart's own default. + +DELIBERATE GAP, the second one: an image tag without a recognizable java part +is let through - see minecraft-server.javaLine. Same reasoning as for an +uncomparable minecraft.version. +*/}} +{{- define "minecraft-server.validateJavaLine" -}} +{{- $tag := include "minecraft-server.imageTag" . -}} +{{- $line := include "minecraft-server.javaLine" . -}} +{{- $required := include "minecraft-server.requiredJavaLine" . -}} +{{- if and $line $required -}} +{{- if ne $line $required -}} +{{- $source := include "minecraft-server.imageTagSource" . -}} +{{- $swapped := regexReplaceAll "java[0-9]+" $tag $required -}} +{{- fail (printf (join "\n" (list + "minecraft-server: the image tag and minecraft.version belong to different Java lines." + "" + " image tag %-22s is the %s line (from %s)" + " minecraft.version %-22s needs the %s line" + "" + "Java 8 runs Minecraft up to 1.16.5, 1.17-1.17.1 need Java 16, 1.18-1.20.4 need" + "Java 17, 1.20.5-1.21.11 need Java 21, and 26.1 and later need Java 25 (Mojang's" + "version manifest, javaVersion.majorVersion per release)." + "" + "Change one of the two:" + " - set minecraft.version to a release of the %s line, or" + " - set image.tagOverride to an itzg/minecraft-server tag of the %s line" + " (this tag with its line swapped would be \"%s\" - check that upstream" + " published it before using it).")) + (printf "%q" $tag) $line $source + (printf "%q" (printf "%v" .Values.minecraft.version)) $required + $line $required $swapped) -}} +{{- end -}} +{{- end -}} +{{- end -}} + {{/* Merge a user-supplied values sub-tree onto a dict of chart defaults so that a null / empty override falls back to the defaults instead of erasing them. diff --git a/minecraft-server/templates/minecraft-server.sfs.yaml b/minecraft-server/templates/minecraft-server.sfs.yaml index d6f8e04..e2bce2b 100644 --- a/minecraft-server/templates/minecraft-server.sfs.yaml +++ b/minecraft-server/templates/minecraft-server.sfs.yaml @@ -1,4 +1,5 @@ {{- include "minecraft-server.validateModpackSources" . }} +{{- include "minecraft-server.validateJavaLine" . }} apiVersion: apps/v1 kind: StatefulSet metadata: @@ -58,7 +59,11 @@ spec: {{- end }} containers: - name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs - image: "itzg/minecraft-server:{{ .Chart.AppVersion }}" + {{- /* Tag from .Chart.AppVersion unless image.tagOverride replaces + it; validateJavaLine above rejects a tag whose Java line does + not fit minecraft.version. Template comment on purpose - a + YAML comment here would change the rendered manifest. */}} + image: "itzg/minecraft-server:{{ include "minecraft-server.imageTag" . }}" imagePullPolicy: IfNotPresent securityContext: {{- include "minecraft-server.containerSecurityContext" .Values.securityContext | nindent 12 }} diff --git a/minecraft-server/values.schema.json b/minecraft-server/values.schema.json index f515791..680c9fe 100644 --- a/minecraft-server/values.schema.json +++ b/minecraft-server/values.schema.json @@ -12,6 +12,17 @@ "scaleDown": { "type": "boolean" }, + "image": { + "description": "May be null / empty: the effective tag then falls back to the chart's appVersion, which is the pre-5.0.0 behaviour.", + "type": ["object", "null"], + "additionalProperties": false, + "properties": { + "tagOverride": { + "description": "Tag of the itzg/minecraft-server image. Empty or null means the chart's appVersion. Deliberately not called \"tag\": it decouples the running image from the chart version and is not managed by Renovate. The Java line of the effective tag is validated against minecraft.version at render time; a tag without a recognizable \"java\" part is let through on purpose.", + "type": ["string", "null"] + } + } + }, "secrets": { "type": "object", "additionalProperties": false, diff --git a/minecraft-server/values.yaml b/minecraft-server/values.yaml index 75a6107..dd5420a 100644 --- a/minecraft-server/values.yaml +++ b/minecraft-server/values.yaml @@ -4,6 +4,38 @@ # Services stay in place; scaleDown: false brings the workloads back up. scaleDown: false +image: + # Tag of the itzg/minecraft-server image. Empty means "use the chart's + # appVersion" (2026.8.2-java25 here), which is what every release before chart + # 5.0.0 did unconditionally. + # + # WHY THIS EXISTS. Since chart 5.0.0 only the NEWEST Java line is maintained + # (see Chart.yaml). The older lines stay published as frozen artifacts - java8 + # and java17 at chart 4.0.0, java21 at 4.1.0 - and never receive another chart + # change. To run an older JVM with the CURRENT chart, set the tag here instead + # of installing a frozen chart version: + # + # image: + # tagOverride: "2026.8.2-java21" + # minecraft: + # version: "1.21.11" + # + # It is called tagOverride, not tag, because it is an emergency exit: it + # decouples the running image from the chart version, so the release no longer + # says in its metadata what it actually runs. Prefer moving to the current + # line; use this when a modpack pins you to an older Minecraft version. + # + # DELIBERATELY NOT MANAGED BY RENOVATE. renovate.json5 tracks appVersion in + # Chart.yaml (partitioned by its "java" compatibility group) and nothing + # else. A tag set here is never bumped by anything - it is yours to review. + # + # The Java line of the effective tag (this value, or appVersion when it is + # empty) is checked against minecraft.version at render time; a mismatch fails + # the render with both values named. Two cases pass unchecked on purpose: a + # tag without a recognizable "java" part, and a minecraft.version that is + # not a plain numeric x[.y[.z]] (snapshots, pre-releases, "LATEST"). + tagOverride: "" + secrets: # op:// item path for the 1Password operator. Only needs to carry a # "curseforge-api-key" key when curseforge.enabled is true and an @@ -223,14 +255,31 @@ minecraft: memory: "4" # Minecraft version to run. Pinned rather than "LATEST", which would silently # drift to a new release on every restart. - # This value must match the Java line of the chart version: the image tag - # comes from .Chart.AppVersion, so the JVM is fixed per published chart - # version. Java 8 runs Minecraft up to 1.16.5, 1.18-1.20.4 need Java 17, - # 1.20.5-1.21.11 need Java 21 and 26.1 and later need Java 25. The two upper - # boundaries are read off Mojang's version manifest (piston-meta, field - # javaVersion.majorVersion per release), not estimated: 1.21.11 (2025-12-09) - # is the last release asking for 21, and every release from 26.1 (2026-03-24) - # on asks for 25. This chart publishes appVersion "2026.8.2-java25". + # + # This value must match the Java line of the EFFECTIVE image tag - appVersion, + # or image.tagOverride when that is set. Since chart 5.0.0 the chart checks it + # and FAILS THE RENDER on a mismatch, naming both values; before 5.0.0 a + # mismatch installed silently and only the container failed afterwards. + # + # up to 1.16.5 Java 8 + # 1.17 - 1.17.1 Java 16 (never published as a chart line here) + # 1.18 - 1.20.4 Java 17 + # 1.20.5 - 1.21.11 Java 21 + # 26.1 and later Java 25 + # + # These are Mojang's boundaries, not this chart's: javaVersion.majorVersion + # from the piston-meta version manifest, swept over all 102 releases on + # 2026-09-02. There are exactly four transitions and they are monotonic in + # release order (1.6.1-1.6.4 predate the field and declare none). + # + # Only a plain numeric x[.y[.z]] is compared. A snapshot ("26w13a"), a + # pre-release ("1.21.11-pre1") or "LATEST" is let through unchecked - see + # image.tagOverride for why the two gaps are deliberate. + # + # Quote the value. Unquoted YAML reads 1.20 as the number 1.2, and the chart + # would pass "1.2" to the image as VERSION. + # + # This chart publishes appVersion "2026.8.2-java25". version: "26.2" # curseforge, ftb and curseforgeServerpack are mutually exclusive - enabling