Import minecraft-server chart for Fleet migration stage 1 (java8 line)

Imports the chart from cluster-config functionally unchanged, wires it into
the publish/validate/install-test pipelines and publishes it as the first of
three Java lines (0.4.0+upjava8).

The only deviation from the source chart is the chart-global scaleDown flag
plus a server.replicas value, following the satisfactory pattern: two of the
three servers stand at 0 replicas in the cluster, which the chart could not
express, and Fleet would otherwise start them on reconcile. The default
(scaleDown: false) still renders replicas: 1, so the rendering of the running
server is unchanged - verified byte-identical against the three real values
files for all three Java lines.

Known deficiencies (hardening, probes, naming scheme, hardcoded values,
missing icon, the misleading mc-router auto-discovery annotations) are left
untouched on purpose and are scheduled for stage 2.
This commit is contained in:
jklein committed 2026-08-23 15:57:17 +02:00
1 parent d1f2ef0027
commit a68192e5fb
15 files changed
+502

No files matched your search

+1
View File
@@ -33,6 +33,7 @@ on:
- rallly
- voucher-vault
- minecraft-router
- minecraft-server
workflow_call:
inputs:
app-version:
@@ -0,0 +1,15 @@
name: Publish Minecraft-Server Helm Chart
on:
workflow_dispatch:
push:
branches:
- main
paths:
- minecraft-server/**
jobs:
publish-minecraft-server:
name: Publish Minecraft-Server Helm Chart
uses: ./.github/workflows/publish-helm-chart.yml
with:
chart-name: 'minecraft-server'
secrets: inherit
+1
View File
@@ -13,6 +13,7 @@ Collection of some self-made helm-charts. Each top-level directory is a standalo
| `homeassistant` | Home Assistant | StatefulSet, has `NOTES.txt` and Helm 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 + NodePort Service + init-script ConfigMap (no ingress); pre-existing chart, still on its legacy conventions. Published once per Java line (`appVersion: java8` / `java17` / `java21`) — the image tag comes from `.Chart.AppVersion`, so the Java line is chosen by picking the chart version |
| `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 |
+70
View File
@@ -0,0 +1,70 @@
# Fixtures for the minecraft-server install-test (release name: ci).
#
# The chart hardcodes storageClassName "longhorn" in its PVC, which does not
# exist in k3d: a no-provisioner StorageClass named "longhorn" plus a pre-bound
# hostPath PV stand in for it. A node-setup Job pre-creates the hostPath
# directory world-writable, because the image's entrypoint chowns /data to
# uid 1000 and hostPath volumes do not support fsGroup.
#
# The Secret is what the 1Password operator would otherwise materialize from
# the OnePasswordItem CR the chart renders (stub CRD in ci/common/).
apiVersion: storage.k8s.io/v1
kind: StorageClass
metadata:
name: longhorn
provisioner: kubernetes.io/no-provisioner
---
apiVersion: batch/v1
kind: Job
metadata:
name: node-setup
spec:
backoffLimit: 2
template:
spec:
restartPolicy: Never
containers:
- name: setup
image: public.ecr.aws/docker/library/busybox:1.36
command:
- sh
- -c
- >
mkdir -p /pv/minecraft-server/mc-data /pv/minecraft-server/mc-modpacks &&
chmod -R 0777 /pv/minecraft-server
volumeMounts:
- name: pv
mountPath: /pv
volumes:
- name: pv
hostPath:
path: /var/lib/ci-pv
type: DirectoryOrCreate
---
apiVersion: v1
kind: PersistentVolume
metadata:
name: ci-minecraft-server-data
spec:
capacity:
storage: 5Gi
accessModes:
- ReadWriteOnce
persistentVolumeReclaimPolicy: Retain
storageClassName: longhorn
claimRef:
namespace: default
name: ci-mc-volume-claim
hostPath:
path: /var/lib/ci-pv/minecraft-server
type: DirectoryOrCreate
---
apiVersion: v1
kind: Secret
metadata:
name: ci-mc-secret
stringData:
rcon-password: ci-dummy-rcon-password
# Only consumed when curseforge.enabled is true; provided so the CI install
# keeps working if the test values ever enable a CurseForge modpack.
curseforge-api-key: ci-dummy-curseforge-key
+3
View File
@@ -0,0 +1,3 @@
# The itzg/minecraft-server image is large and the server downloads its vanilla
# server jar on first start before the container settles.
INSTALL_TIMEOUT=600
+38
View File
@@ -0,0 +1,38 @@
# Values for the minecraft-server install-test. Kept as close to the real
# deployments as possible; only what a CI runner cannot provide is overridden.
#
# 1. minecraft.version: the image tag comes from .Chart.AppVersion, so the JVM
# in the image is fixed per published chart version and the Minecraft
# version has to match that Java line - Java 8 runs Minecraft up to 1.16.5,
# modern versions need Java 17/21 and refuse to start otherwise. This chart
# version publishes appVersion "java8", hence 1.12.2 (a classic Java 8 era
# release). This value is bumped together with appVersion in the java17 and
# java21 follow-up PRs.
# The production default is "LATEST", which would pull a 1.21.x server that
# a Java 8 JVM cannot load at all.
# 2. minecraft.resources.storage: the production 32Gi world volume is backed by
# a static hostPath PV in CI (see fixtures.yaml); 2Gi is plenty for a server
# that only has to boot.
# 3. minecraft.resources.memory: sets the JVM heap (-Xmx); 1G instead of 4G
# keeps the runner comfortable. The container resource *requests* are
# hardcoded in the template (cpu 2 / memory 4Gi) and cannot be overridden -
# they fit a 4-vCPU/16GB GitHub runner, and making them configurable is part
# of the stage-2 convention alignment.
# 4. server.outwardPort stays at the chart default 30565: unlike minecraft-router
# that default is already inside the Kubernetes node-port range
# (30000-32767), so the k3d API server accepts it unchanged.
secrets:
minecraftServerRef: vaults/CI/items/minecraft-server
minecraft:
version: "1.12.2"
management:
# opPlayers deliberately stays at the default (null): the image resolves
# every name in OPS against the external Playerdb API and aborts the whole
# startup when a name does not resolve, so any placeholder name would break
# the install-test and a real name would make CI depend on a third-party
# lookup.
maxPlayers: 2
resources:
storage: 2Gi
memory: "1"
+23
View File
@@ -0,0 +1,23 @@
# Patterns to ignore when building packages.
# This supports shell glob matching, relative path matching, and
# negation (prefixed with !). Only one pattern per line.
.DS_Store
# Common VCS dirs
.git/
.gitignore
.bzr/
.bzrignore
.hg/
.hgignore
.svn/
# Common backup files
*.swp
*.bak
*.tmp
*.orig
*~
# Various IDEs
.project
.idea/
*.tmproj
.vscode/
+24
View File
@@ -0,0 +1,24 @@
apiVersion: v2
name: minecraft-server
description: A Helm chart for deploying a itzg/minecraft-server
# A chart can be either an 'application' or a 'library' chart.
#
# Application charts are a collection of templates that can be packaged into versioned archives
# to be deployed.
#
# Library charts provide useful utilities or functions for the chart developer. They're included as
# a dependency of application charts to inject those utilities and functions into the rendering
# pipeline. Library charts do not define any templates and therefore cannot be deployed.
type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.4.0+upjava8
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "java8"
+35
View File
@@ -0,0 +1,35 @@
{{/*
Render the replica count for a Deployment/StatefulSet.
Input: dict
"scaleDown" the chart-global .Values.scaleDown flag (may be nil)
"replicas" the workload's replicas value (may be nil)
Rules (issue #25):
- scaleDown: true takes precedence and always yields 0.
- otherwise an explicitly set replicas value is used as given, including an
explicit 0 — Helm's "default" would swallow 0, hence the explicit
kindIs "invalid" nil check.
- a missing replicas value defaults to 1.
Usage (the values path to the workload's replicas varies per chart):
replicas: {{ include "minecraft-server.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.server.replicas) }}
NOTE: charts in this repo deliberately have no dependencies, so this helper is
duplicated into every chart's templates/_helpers.tpl with the chart's name as
define prefix. The reference implementation lives in template-chart.
This chart is a stage-1 import (issue #67) and does not yet follow the repo
conventions; only the replicas helper was taken over, because the scaleDown
flag is needed before the Fleet migration. The resources helper follows in
stage 2 together with the rest of the convention alignment.
*/}}
{{- define "minecraft-server.replicas" -}}
{{- if .scaleDown -}}
0
{{- else if not (kindIs "invalid" .replicas) -}}
{{- .replicas -}}
{{- else -}}
1
{{- end -}}
{{- end -}}
@@ -0,0 +1,42 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-init-config
data:
init.sh: |
echo "downloading serverpack"
wget -O /modpacks/serverpack.zip {{ .Values.curseforgeServerpack.downloadUrl }}
SERVERPACK_DIR="{{ .Values.curseforgeServerpack.workingDirectory }}"
BACKUP_DIR="/data/serverpack-backup"
if [ -d "$SERVERPACK_DIR" ]; then
echo "found installed server. Migrating..."
if [ -d "$BACKUP_DIR" ]; then
echo "clearing old Backup"
rm -rf $BACKUP_DIR
fi
echo "backing up old server-files"
cp -r $SERVERPACK_DIR $BACKUP_DIR
rm -rf $SERVERPACK_DIR
mkdir $SERVERPACK_DIR
echo "extracting new server-files"
tar -xvf /modpacks/serverpack.zip -C $SERVERPACK_DIR
echo "copying old configuration-data"
cp -r $BACKUP_DIR/world $SERVERPACK_DIR/world
cp $BACKUP_DIR/banned-ips.json $SERVERPACK_DIR/banned-ips.json
cp $BACKUP_DIR/banned-players.json $SERVERPACK_DIR/banned-players.json
cp $BACKUP_DIR/ops.json $SERVERPACK_DIR/ops.json
cp $BACKUP_DIR/whitelist.json $SERVERPACK_DIR/whitelist.json
echo "change owner of files"
chown -R 1000:1000 $SERVERPACK_DIR
echo "successfully migrated server"
# echo "deleting serverpack.zip"
# rm /modpacks/serverpack.zip
fi
+11
View File
@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Name }}-mc-volume-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.minecraft.resources.storage }}
storageClassName: longhorn
@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: {{ .Release.Name }}-mc-secret
spec:
itemPath: {{ .Values.secrets.minecraftServerRef }}
@@ -0,0 +1,25 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-service
{{- if .Values.mcRouterIntegration.enabled }}
annotations:
"mc-router.itzg.me/externalServerName": "{{ .Values.mcRouterIntegration.domain }}"
{{- end }}
spec:
selector:
app: {{ .Release.Name }}-server-deployment
{{- if .Values.mcRouterIntegration.enabled }}
ports:
- name: mc-port
port: 25565
targetPort: 25565
type: ClusterIP
{{- else }}
ports:
- nodePort: {{ .Values.server.outwardPort }}
port: 25565
targetPort: 25565
type: NodePort
{{- end }}
@@ -0,0 +1,153 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Release.Name }}-server-deployment
labels:
app: {{ .Release.Name }}
spec:
replicas: {{ include "minecraft-server.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.server.replicas) }}
selector:
matchLabels:
app: {{ .Release.Name }}-server-deployment
template:
metadata:
name: {{ .Release.Name }}-server-deployment
labels:
app: {{ .Release.Name }}-server-deployment
spec:
automountServiceAccountToken: false
containers:
- name: {{ .Release.Name }}-mc-server-container
image: "itzg/minecraft-server:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
ports:
- name: mc-port
containerPort: 35565
- name: minecraft-rcon
containerPort: 25575
- name: metrics
containerPort: 19565
env:
- name: EULA
value: "true"
- name: DUMP_SERVER_PROPERTIES
value: "true"
- name: MOTD
value: "{{ .Values.server.motd }}"
- name: DIFFICULTY
value: {{ .Values.minecraft.difficulty }}
{{- if .Values.minecraft.whitelist.enabled }}
- name: EXISTING_WHITELIST_FILE
value: {{ .Values.minecraft.whitelist.overridePolicy }}
- name: WHITELIST
value: {{ .Values.minecraft.whitelist.players }}
{{- end }}
- name: OPS
value: {{ .Values.minecraft.management.opPlayers }}
- name: EXISTING_OPS_FILE
value: "SYNCHRONIZE"
- name: RCON_PASSWORD
valueFrom:
secretKeyRef:
key: rcon-password
name: {{ .Release.Name }}-mc-secret
- name: MAX_PLAYERS
value: "{{ .Values.minecraft.management.maxPlayers }}"
- name: FORCE_GAMEMODE
value: "{{ .Values.minecraft.management.forceGameMode }}"
- name: SNOOPER_ENABLED
value: "false"
- name: MODE
value: "{{ .Values.minecraft.management.gameMode }}"
- name: LEVEL
value: "{{ .Values.minecraft.management.worldSaveName }}"
- name: SERVER_NAME
value: "{{ .Values.minecraft.management.serverName }}"
- name: VERSION
value: "{{ .Values.minecraft.version }}"
- name: ENABLE_RCON
value: "false"
- name: ENABLE_AUTOPAUSE
value: "false"
- name: MEMORY
value: "{{ .Values.minecraft.resources.memory }}G"
{{- if .Values.curseforge.enabled }}
- name: TYPE
value: "AUTO_CURSEFORGE"
- name: CF_API_KEY
valueFrom:
secretKeyRef:
key: curseforge-api-key
name: {{ .Release.Name }}-mc-secret
- name: CF_SLUG
value: "{{ .Values.curseforge.modpack.slug }}"
- name: CF_FILE_ID
value: "{{ .Values.curseforge.modpack.versionId }}"
- name: CF_EXCLUDE_MODS
value: "{{ .Values.curseforge.modpack.exclude }}"
- name: CF_OVERRIDES_EXCLUSIONS
value: "{{ .Values.curseforge.modpack.excludeOverride }}"
- name: CF_FORCE_INCLUDE_MODS
value: "{{ .Values.curseforge.modpack.include }}"
- name: CF_FORCE_SYNCHRONIZE
value: "{{ .Values.curseforge.forceReload }}"
{{- end }}
{{- if .Values.curseforgeServerpack.enabled }}
- name: TYPE
value: "CURSEFORGE"
- name: CF_SERVER_MOD
value: "/modpacks/serverpack.zip"
- name: CF_BASE_DIR
value: "{{ .Values.curseforgeServerpack.workingDirectory }}"
{{- end }}
{{- if .Values.ftb.enabled }}
- name: TYPE
value: FTBA
- name: FTB_MODPACK_ID
value: "{{ .Values.ftb.modpackId }}"
- name: FTB_MODPACK_VERSION_ID
value: "{{ .Values.ftb.versionId }}"
{{- end }}
resources:
requests:
cpu: "2"
memory: "4Gi"
ephemeral-storage: "100Mi"
limits:
cpu: "4"
memory: "12Gi"
ephemeral-storage: "200Mi"
volumeMounts:
- name: "mc-volume"
mountPath: /data
subPath: mc-data/
- name: "mc-volume"
mountPath: /modpacks
subPath: mc-modpacks/
{{- if .Values.curseforgeServerpack }}
initContainers:
- name: download-serverpack
image: alpine
command: [ 'sh', '/init/init.sh' ]
volumeMounts:
- name: "mc-volume"
mountPath: /modpacks
subPath: mc-modpacks/
- name: "mc-volume"
mountPath: /data
subPath: mc-data/
- name: init-script
mountPath: /init/init.sh
subPath: init.sh
{{- end }}
restartPolicy: Always
volumes:
- name: "mc-volume"
persistentVolumeClaim:
claimName: {{ .Release.Name }}-mc-volume-claim
- name: init-script
configMap:
name: {{ .Release.Name }}-init-config
serviceName: {{ .Release.Name }}-service
+55
View File
@@ -0,0 +1,55 @@
# Scale the whole chart down without uninstalling it: scaleDown: true renders
# every Deployment/StatefulSet with replicas: 0 (and suspends CronJobs) and
# takes precedence over any per-workload replicas value. PVCs, Secrets and
# Services stay in place; scaleDown: false brings the workloads back up.
scaleDown: false
secrets:
minecraftServerRef: null
server:
# Replica count for this workload; 0 is allowed and scales it down.
# The chart-global scaleDown flag takes precedence.
replicas: 1
outwardPort: 30565
motd: 'Running \u00A72\u00A7o%MODPACK_NAME% \u00A70version \u00A7r%env:MODPACK_VERSION%'
minecraft:
# Valid values are: peaceful, easy, normal, and hard
difficulty: "easy"
whitelist:
enabled: "true"
overridePolicy: "MERGE"
# Comma-separated List of Players
players: null
management:
# Comma-separated List of Players
opPlayers: null
maxPlayers: 10
forceGameMode: true
# Valid values are: creative, survival, adventure, spectator (only for Minecraft 1.8 or later)
gameMode: survival
worldSaveName: world
serverName: mc-server
resources:
storage: "32Gi"
memory: "4"
version: "LATEST"
curseforge:
enabled: false
forceReload: false
modpack:
slug: null
versionId: null
exclude: null
excludeOverride: null
include: null
ftb:
enabled: false
modpackId: null
versionId: null
curseforgeServerpack:
enabled: false
downloadUrl: null
workingDirectory: '/data/serverpack'
mcRouterIntegration:
enabled: false
domain: null