From a68192e5fba23850bc06eaecb6ee3d0fcfe9bb45 Mon Sep 17 00:00:00 2001 From: jklein Date: Sun, 23 Aug 2026 15:56:09 +0200 Subject: [PATCH] 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. --- .github/workflows/change-app-version.yml | 1 + .../workflows/publish-minecraft-server.yml | 15 ++ README.md | 1 + ci/minecraft-server/fixtures.yaml | 70 ++++++++ ci/minecraft-server/settings.env | 3 + ci/minecraft-server/test-values.yaml | 38 +++++ minecraft-server/.helmignore | 23 +++ minecraft-server/Chart.yaml | 24 +++ minecraft-server/templates/_helpers.tpl | 35 ++++ .../templates/init-script-configmap.yaml | 42 +++++ minecraft-server/templates/mc-pvc.yaml | 11 ++ minecraft-server/templates/mc-secret.yaml | 6 + .../templates/mc-server-service.yaml | 25 +++ minecraft-server/templates/mc-server-sfs.yaml | 153 ++++++++++++++++++ minecraft-server/values.yaml | 55 +++++++ 15 files changed, 502 insertions(+) create mode 100644 .github/workflows/publish-minecraft-server.yml create mode 100644 ci/minecraft-server/fixtures.yaml create mode 100644 ci/minecraft-server/settings.env create mode 100644 ci/minecraft-server/test-values.yaml create mode 100644 minecraft-server/.helmignore create mode 100644 minecraft-server/Chart.yaml create mode 100644 minecraft-server/templates/_helpers.tpl create mode 100644 minecraft-server/templates/init-script-configmap.yaml create mode 100644 minecraft-server/templates/mc-pvc.yaml create mode 100644 minecraft-server/templates/mc-secret.yaml create mode 100644 minecraft-server/templates/mc-server-service.yaml create mode 100644 minecraft-server/templates/mc-server-sfs.yaml create mode 100644 minecraft-server/values.yaml diff --git a/.github/workflows/change-app-version.yml b/.github/workflows/change-app-version.yml index d5187d3..49ee877 100644 --- a/.github/workflows/change-app-version.yml +++ b/.github/workflows/change-app-version.yml @@ -33,6 +33,7 @@ on: - rallly - voucher-vault - minecraft-router + - minecraft-server workflow_call: inputs: app-version: diff --git a/.github/workflows/publish-minecraft-server.yml b/.github/workflows/publish-minecraft-server.yml new file mode 100644 index 0000000..c6e57c5 --- /dev/null +++ b/.github/workflows/publish-minecraft-server.yml @@ -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 diff --git a/README.md b/README.md index 13bf0f8..7a72d32 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/ci/minecraft-server/fixtures.yaml b/ci/minecraft-server/fixtures.yaml new file mode 100644 index 0000000..7a42cec --- /dev/null +++ b/ci/minecraft-server/fixtures.yaml @@ -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 diff --git a/ci/minecraft-server/settings.env b/ci/minecraft-server/settings.env new file mode 100644 index 0000000..74bbe98 --- /dev/null +++ b/ci/minecraft-server/settings.env @@ -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 diff --git a/ci/minecraft-server/test-values.yaml b/ci/minecraft-server/test-values.yaml new file mode 100644 index 0000000..7e9da95 --- /dev/null +++ b/ci/minecraft-server/test-values.yaml @@ -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" diff --git a/minecraft-server/.helmignore b/minecraft-server/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/minecraft-server/.helmignore @@ -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/ diff --git a/minecraft-server/Chart.yaml b/minecraft-server/Chart.yaml new file mode 100644 index 0000000..51e8a53 --- /dev/null +++ b/minecraft-server/Chart.yaml @@ -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" diff --git a/minecraft-server/templates/_helpers.tpl b/minecraft-server/templates/_helpers.tpl new file mode 100644 index 0000000..73dd130 --- /dev/null +++ b/minecraft-server/templates/_helpers.tpl @@ -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 -}} diff --git a/minecraft-server/templates/init-script-configmap.yaml b/minecraft-server/templates/init-script-configmap.yaml new file mode 100644 index 0000000..56ccb4a --- /dev/null +++ b/minecraft-server/templates/init-script-configmap.yaml @@ -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 diff --git a/minecraft-server/templates/mc-pvc.yaml b/minecraft-server/templates/mc-pvc.yaml new file mode 100644 index 0000000..b9ff2bd --- /dev/null +++ b/minecraft-server/templates/mc-pvc.yaml @@ -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 diff --git a/minecraft-server/templates/mc-secret.yaml b/minecraft-server/templates/mc-secret.yaml new file mode 100644 index 0000000..5be48e7 --- /dev/null +++ b/minecraft-server/templates/mc-secret.yaml @@ -0,0 +1,6 @@ +apiVersion: onepassword.com/v1 +kind: OnePasswordItem +metadata: + name: {{ .Release.Name }}-mc-secret +spec: + itemPath: {{ .Values.secrets.minecraftServerRef }} diff --git a/minecraft-server/templates/mc-server-service.yaml b/minecraft-server/templates/mc-server-service.yaml new file mode 100644 index 0000000..5f7dd8e --- /dev/null +++ b/minecraft-server/templates/mc-server-service.yaml @@ -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 }} + \ No newline at end of file diff --git a/minecraft-server/templates/mc-server-sfs.yaml b/minecraft-server/templates/mc-server-sfs.yaml new file mode 100644 index 0000000..0f3daa5 --- /dev/null +++ b/minecraft-server/templates/mc-server-sfs.yaml @@ -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 + + diff --git a/minecraft-server/values.yaml b/minecraft-server/values.yaml new file mode 100644 index 0000000..88015f4 --- /dev/null +++ b/minecraft-server/values.yaml @@ -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 \ No newline at end of file