satisfactory: dev and server fall back instead of aborting (7.0.1)

--set dev=null aborted the render at .Values.dev.debug and --set server=null
at .Values.server.replicas, while the third block, game, has fallen back to
the chart defaults since 7.0.0. Half-safe is the worse state of the two:
whoever sees game=null work assumes server=null does too.

Both blocks now run through satisfactory.defaultedDict like game, via two new
helpers next to it. server.resources is deliberately NOT re-defaulted there -
it already runs through satisfactory.effectiveResources, which carries those
defaults and is null-safe on its own, and a second copy of limitFactor: 2 /
4Gi / 1 / 5Gi would be a third place for them to drift.

server is read in four files, not one, and the fourth is NOTES.txt, which
helm template never renders - a fix confined to the manifests would have
looked green and still aborted on a real helm install. All four go through
the helpers.

The single erased key was the quieter half of the bug and is fixed with it:
a nulled server.gamePort rendered "port:" and "nodePort:" empty into the
Service and an empty containerPort into the StatefulSet, and a nulled
server.storage a PVC asking for no size - manifests rejected by nothing until
the API server saw them. server.storage was documented in values.schema.json
as deliberately not null-safe; that reasoning does not survive the block-level
case, because it made "server: null" render a sizeless PVC instead of falling
back like everything else. The schema note is replaced rather than left lying.

Rendered output for valid values is unchanged: helm template against the
chart defaults and against a full override set is byte-identical to 7.0.0,
and so is NOTES.txt. Hence a patch bump, appVersion untouched.

Fixes #16
This commit is contained in:
jklein authored and Justin Klein committed 2026-09-23 20:45:24 +00:00
1 parent 8074e0de59
commit 5ff6e401c7
9 files changed
+109 -34

No files matched your search

+9 -7
View File
@@ -1,17 +1,19 @@
{{- $replicas := include "satisfactory.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.server.replicas) -}}
{{- $gamePort := int .Values.server.gamePort -}}
{{- $msgPort := int .Values.server.messagingPort -}}
{{- $server := fromYaml (include "satisfactory.server" .Values.server) -}}
{{- $dev := fromYaml (include "satisfactory.dev" .Values.dev) -}}
{{- $replicas := include "satisfactory.replicas" (dict "scaleDown" .Values.scaleDown "replicas" (include "satisfactory.formatNumber" $server.replicas)) -}}
{{- $gamePort := int $server.gamePort -}}
{{- $msgPort := int $server.messagingPort -}}
{{- $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 -}}
{{- $pvc := printf "%s-%s-vc" .Release.Name .Chart.Name -}}
{{- $storage := .Values.server.storage | default "unset" -}}
{{- $res := fromYaml (include "satisfactory.effectiveResources" .Values.server.resources) -}}
{{- $storage := $server.storage | default "unset" -}}
{{- $res := fromYaml (include "satisfactory.effectiveResources" $server.resources) -}}
{{- $lim := $res.limits | default dict -}}
{{- $req := $res.requests | default dict -}}
{{- $eph := index $lim "ephemeral-storage" | default "the computed ephemeral-storage limit" -}}
{{- $mem := index $lim "memory" | default "the computed memory limit" -}}
{{- $factor := (.Values.server.resources | default dict).limitFactor | default 2 -}}
{{- $factor := ($server.resources | default dict).limitFactor | default 2 -}}
{{- $startup := fromYaml (include "satisfactory.startupProbe" .Values.startupProbe) -}}
{{- $budget := "" -}}
{{- if and (kindIs "float64" $startup.periodSeconds) (kindIs "float64" $startup.failureThreshold) -}}
@@ -21,7 +23,7 @@
{{- $seasonalEnv := include "satisfactory.disableSeasonalEvents" $game.enableSeasonalEvents -}}
{{- $seasonalOff := eq $seasonalEnv "true" -}}
{{- $beta := eq (lower (printf "%v" $game.experimental)) "true" -}}
{{- $debug := eq (lower (printf "%v" .Values.dev.debug)) "true" -}}
{{- $debug := eq (lower (printf "%v" $dev.debug)) "true" -}}
{{- $outOfRange := or (lt $gamePort 30000) (gt $gamePort 32767) (lt $msgPort 30000) (gt $msgPort 32767) -}}
{{- $skipRaw := "" -}}
{{- range $entry := .Values.env -}}
+48
View File
@@ -308,6 +308,54 @@ DISABLESEASONALEVENTS at "true", the value this chart has always rendered.
{{- include "satisfactory.defaultedDict" (dict "defaults" $defaults "given" . "path" "game") -}}
{{- end -}}
{{/*
The effective dev block (Gitea issue #16, the rest of #99).
"--set dev=null" — or a leftover "dev:" whose last sub-key was deleted — used
to abort the render with a nil-pointer error at .Values.dev.debug, in the
StatefulSet and in NOTES.txt. It now falls back to the default values.yaml
documents, the same way the game block has since 7.0.0.
The defaults below MUST stay in sync with the dev block in values.yaml, which
stays the documented, user-facing place for them; this copy is only the
fallback for the case where an override has erased the block.
*/}}
{{- define "satisfactory.dev" -}}
{{- $defaults := dict
"debug" false -}}
{{- include "satisfactory.defaultedDict" (dict "defaults" $defaults "given" . "path" "dev") -}}
{{- end -}}
{{/*
The effective server block (Gitea issue #16, the rest of #99).
"--set server=null" used to abort the render at .Values.server.replicas, and a
single erased sub-key was worse than that: it rendered silently. A nulled
server.gamePort produced a Service with "port:" and "nodePort:" empty and a
StatefulSet with an empty containerPort — a manifest the API server rejects
only at apply time — and a nulled server.storage produced a PVC asking for no
size at all. Both now fall back to the documented defaults instead.
Note what is NOT defaulted here: server.resources. That block already runs
through satisfactory.effectiveResources, which carries its own defaults and is
null-safe on its own; repeating limitFactor/4Gi/1/5Gi here would create a
third place for them to drift apart. Keys the defaults do not mention are
passed through by satisfactory.defaultedDict unchanged, so an erased
"server:" simply leaves resources unset and effectiveResources supplies them.
The defaults below MUST stay in sync with the server block in values.yaml,
which stays the documented, user-facing place for them; this copy is only the
fallback for the case where an override has erased the block.
*/}}
{{- define "satisfactory.server" -}}
{{- $defaults := dict
"replicas" 1
"gamePort" 30777
"messagingPort" 30888
"storage" "20Gi" -}}
{{- include "satisfactory.defaultedDict" (dict "defaults" $defaults "given" . "path" "server") -}}
{{- end -}}
{{/*
Render the DISABLESEASONALEVENTS env value from game.enableSeasonalEvents
(issue #215).
+2 -1
View File
@@ -1,3 +1,4 @@
{{- $server := fromYaml (include "satisfactory.server" .Values.server) -}}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
@@ -7,5 +8,5 @@ spec:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.server.storage }}
storage: {{ $server.storage }}
storageClassName: longhorn
@@ -1,3 +1,6 @@
{{- $server := fromYaml (include "satisfactory.server" .Values.server) -}}
{{- $gamePort := include "satisfactory.formatNumber" $server.gamePort -}}
{{- $msgPort := include "satisfactory.formatNumber" $server.messagingPort -}}
apiVersion: v1
kind: Service
metadata:
@@ -8,19 +11,19 @@ spec:
type: NodePort
ports:
- name: "api"
port: {{ .Values.server.gamePort }}
port: {{ $gamePort }}
protocol: TCP
nodePort: {{ .Values.server.gamePort }}
targetPort: {{ .Values.server.gamePort }}
nodePort: {{ $gamePort }}
targetPort: {{ $gamePort }}
- name: "game"
port: {{ .Values.server.gamePort }}
port: {{ $gamePort }}
protocol: UDP
nodePort: {{ .Values.server.gamePort }}
targetPort: {{ .Values.server.gamePort }}
nodePort: {{ $gamePort }}
targetPort: {{ $gamePort }}
- name: "messaging"
port: {{ .Values.server.messagingPort }}
port: {{ $msgPort }}
protocol: TCP
nodePort: {{ .Values.server.messagingPort }}
targetPort: {{ .Values.server.messagingPort }}
nodePort: {{ $msgPort }}
targetPort: {{ $msgPort }}
selector:
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
+10 -8
View File
@@ -1,4 +1,6 @@
{{- $game := fromYaml (include "satisfactory.game" .Values.game) -}}
{{- $dev := fromYaml (include "satisfactory.dev" .Values.dev) -}}
{{- $server := fromYaml (include "satisfactory.server" .Values.server) -}}
apiVersion: apps/v1
kind: StatefulSet
metadata:
@@ -10,7 +12,7 @@ spec:
matchLabels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-service
replicas: {{ include "satisfactory.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.server.replicas) }}
replicas: {{ include "satisfactory.replicas" (dict "scaleDown" .Values.scaleDown "replicas" (include "satisfactory.formatNumber" $server.replicas)) }}
template:
metadata:
labels:
@@ -24,7 +26,7 @@ spec:
image: "ghcr.io/wolveix/satisfactory-server:{{ .Chart.AppVersion }}"
env:
- name: DEBUG
value: "{{ .Values.dev.debug }}"
value: "{{ $dev.debug }}"
- name: MAXPLAYERS
value: "{{ include "satisfactory.formatNumber" $game.maxPlayers }}"
# PUID/PGID are only used by the image's root entrypoint path;
@@ -42,9 +44,9 @@ spec:
- name: DISABLESEASONALEVENTS
value: "{{ include "satisfactory.disableSeasonalEvents" $game.enableSeasonalEvents }}"
- name: SERVERGAMEPORT
value: "{{ .Values.server.gamePort }}"
value: "{{ include "satisfactory.formatNumber" $server.gamePort }}"
- name: SERVERMESSAGINGPORT
value: "{{ .Values.server.messagingPort }}"
value: "{{ include "satisfactory.formatNumber" $server.messagingPort }}"
{{- range $value := .Values.env }}
{{- if $value.value }}
- name: {{ $value.name }}
@@ -67,13 +69,13 @@ spec:
{{- end }}
ports:
- name: "api"
containerPort: {{ .Values.server.gamePort }}
containerPort: {{ include "satisfactory.formatNumber" $server.gamePort }}
protocol: TCP
- name: "game"
containerPort: {{ .Values.server.gamePort }}
containerPort: {{ include "satisfactory.formatNumber" $server.gamePort }}
protocol: UDP
- name: "messaging"
containerPort: {{ .Values.server.messagingPort }}
containerPort: {{ include "satisfactory.formatNumber" $server.messagingPort }}
protocol: TCP
livenessProbe:
{{- include "satisfactory.livenessProbe" .Values.livenessProbe | nindent 12 }}
@@ -82,7 +84,7 @@ spec:
startupProbe:
{{- include "satisfactory.startupProbe" .Values.startupProbe | nindent 12 }}
resources:
{{- include "satisfactory.effectiveResources" .Values.server.resources | nindent 12 }}
{{- include "satisfactory.effectiveResources" $server.resources | nindent 12 }}
volumeMounts:
- name: satisfactory-volume
mountPath: /config