Three issues on one chart, bundled because they share values.yaml, values.schema.json and the version line, and because the first half of #11 lives in the same template as #101. Gitea #11, first half: the consume CronJob's schedule was rendered bare, so a schedule in the commonest cron notation there is - a star, a slash, an interval - aborted the render with a YAML parse error pointing at the template while the operator's value appeared nowhere in the message. Quoting it in the values file does not help; Helm passes the content through. Reproduced before the fix, at the line the issue names, and fixed with | quote. Gitea #11, second half: secrets.oidcSecretRef is removed, values and schema both. No template ever read it, so no OnePasswordItem was created and nothing reached the pod - the quiet kind of defect that looks like successful configuration. It is not reimplemented, and the reason is in paperless: the whole OIDC configuration travels in one json environment variable with no _FILE variant, so a secretKeyRef cannot substitute a fragment into it. The operational question is answered by #83 - Keycloak marks a client public per client, so nothing forces a confidential one here. In the same breath, oidc.provider.tokenAuthMethod is restricted to "none". Its comment named client_secret_basic as the example, which asks the provider for an authentication this chart cannot perform: it configures no client secret and cannot. The two values read together looked like a working secret setup, and the symptom was a login dying at the callback with invalid_client. No live release sets it (measured against all three instances). Gitea #112 asked for an extraEnv and the chart already had the standard additive env block - the issue was measured against a deployed 12.2.0 and read only the top-level keys. What was actually broken is the block itself: it rendered its operand bare, so a "*"-leading value aborted the render and "true" or "0755" arrived as a bool or an integer, which the API server rejects after a green render. It is brought to the repository's documented quoted form (template-chart carries the rule), which is what makes an arbitrary PAPERLESS_* variable settable in practice. PAPERLESS_LOGGING_DIR is documented as the worked example, with the cross-reference it needs: the root cause is the NFS-exported RWX data volume in jk-cluster-config #360 and this does not fix it - it only moves the log off that volume. The two Ingress upload-size annotations had the same bare render and are quoted too. Gitea #101: the consume mover now runs on the node of the StatefulSet pod, via a required podAffinity on its label. It mounts the RWX consume volume, and Longhorn's CSI driver refuses that mount unless it finds a workload pod in Pending, so a mover on another node left the StatefulSet node's kubelet retrying every two minutes - 291 events in 10 hours in both releases with SMB on, none in the release without the CronJob. A static nodeSelector would be wrong because the StatefulSet pod is not pinned either, and preferred would reproduce the bug more rarely and harder to find. The cost of a required term is handled rather than left open: concurrencyPolicy: Forbid, a startingDeadlineSeconds that also keeps the controller away from its 100-missed-schedules stop, and an activeDeadlineSeconds that fails a job which cannot be placed instead of leaving one Pending pod per schedule. scaleDown already suspends the CronJob; what remains is paperless.replicas: 0 with SMB on, which now reports a bounded, visible failure. successfulJobsHistoryLimit is deliberately untouched: Longhorn's check asks for the existence of a Pending pod, not the absence of Succeeded ones, so those three pods are the error message's evidence and not its cause. Side defect fixed on the way: every read of smbConnection.cron dereferenced the block directly, so an override that had erased it aborted with a nil-pointer error naming an internal template path instead of falling back to the defaults this chart promises everywhere else. Measured on origin/main; all reads now go through a merged-defaults helper that preserves an explicit false. Major: a value is removed and fails the strict schema afterwards, and runtime placement changes. Fixes #11 Fixes #101 Fixes #112
282 lines
13 KiB
YAML
282 lines
13 KiB
YAML
{{- /*
|
|
Barcode settings, defended against a null / empty "barcodes:" override:
|
|
an empty block falls back to the chart defaults (issue #61).
|
|
*/ -}}
|
|
{{- $barcodes := fromYaml (include "paperless-ngx.barcodes" .Values.paperless.files.barcodes) -}}
|
|
apiVersion: apps/v1
|
|
kind: StatefulSet
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
spec:
|
|
replicas: {{ include "paperless-ngx.replicas" (dict "scaleDown" .Values.scaleDown "replicas" .Values.paperless.replicas) }}
|
|
serviceName: {{ .Release.Name }}-{{ .Chart.Name }}-service
|
|
selector:
|
|
matchLabels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
template:
|
|
metadata:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
labels:
|
|
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
spec:
|
|
automountServiceAccountToken: false
|
|
securityContext:
|
|
{{- include "paperless-ngx.podSecurityContext" .Values.paperless.securityContext | nindent 8 }}
|
|
initContainers:
|
|
# s6-overlay refuses to start unless /run is owned by the runtime uid,
|
|
# but emptyDirs are always created root-owned. This minimal init
|
|
# container hands /run over to the paperless uid (see
|
|
# paperless.securityContext in values.yaml) so the main container can
|
|
# run fully hardened (non-root, no privilege escalation).
|
|
- name: init-run-ownership
|
|
image: "{{ .Values.paperless.busybox.image.repository }}:{{ .Values.paperless.busybox.image.tag }}"
|
|
imagePullPolicy: IfNotPresent
|
|
command:
|
|
- sh
|
|
- -c
|
|
{{- $podCtx := fromYaml (include "paperless-ngx.podSecurityContext" .Values.paperless.securityContext) }}
|
|
- chown {{ $podCtx.runAsUser }}:{{ $podCtx.runAsGroup }} /run
|
|
securityContext:
|
|
runAsNonRoot: false
|
|
runAsUser: 0
|
|
allowPrivilegeEscalation: false
|
|
readOnlyRootFilesystem: true
|
|
capabilities:
|
|
drop:
|
|
- ALL
|
|
add:
|
|
- CHOWN
|
|
volumeMounts:
|
|
- mountPath: /run
|
|
name: run
|
|
resources:
|
|
requests:
|
|
cpu: 10m
|
|
memory: 16Mi
|
|
ephemeral-storage: 1Mi
|
|
limits:
|
|
cpu: 100m
|
|
memory: 32Mi
|
|
ephemeral-storage: 10Mi
|
|
containers:
|
|
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
|
|
image: "ghcr.io/paperless-ngx/paperless-ngx:{{ .Chart.AppVersion }}"
|
|
imagePullPolicy: IfNotPresent
|
|
securityContext:
|
|
{{- include "paperless-ngx.containerSecurityContext" .Values.paperless.securityContext | nindent 12 }}
|
|
ports:
|
|
- containerPort: 8000
|
|
protocol: TCP
|
|
name: paperless-port
|
|
env:
|
|
- name: PAPERLESS_REDIS
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
|
|
key: redis-connection-url
|
|
- name: PAPERLESS_REDIS_PREFIX
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
|
|
key: key-prefix
|
|
- name: PAPERLESS_DBENGINE
|
|
value: "postgresql"
|
|
- name: PAPERLESS_DBHOST
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
|
|
key: server
|
|
- name: PAPERLESS_DBPORT
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
|
|
key: port
|
|
- name: PAPERLESS_DBNAME
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
|
|
key: database
|
|
- name: PAPERLESS_DBUSER
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
|
|
key: username
|
|
- name: PAPERLESS_DBPASS
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
|
|
key: password
|
|
- name: PAPERLESS_ADMIN_USER
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
|
key: username
|
|
- name: PAPERLESS_ADMIN_MAIL
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
|
key: username
|
|
- name: PAPERLESS_ADMIN_PASSWORD
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
|
key: password
|
|
- name: PAPERLESS_SECRET_KEY
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
|
|
key: paperless-secret-key
|
|
- name: PAPERLESS_OCR_LANGUAGE
|
|
value: "{{ .Values.paperless.ocr.languages }}"
|
|
- name: PAPERLESS_OCR_MODE
|
|
value: "{{ .Values.paperless.ocr.mode }}"
|
|
- name: PAPERLESS_ARCHIVE_FILE_GENERATION
|
|
value: "{{ .Values.paperless.ocr.archiveFileGeneration }}"
|
|
- name: PAPERLESS_OCR_ROTATE_PAGES
|
|
value: "{{ .Values.paperless.ocr.rotate_pages }}"
|
|
- name: PAPERLESS_OCR_OUTPUT_TYPE
|
|
value: "{{ .Values.paperless.ocr.output_type }}"
|
|
- name: PAPERLESS_FILENAME_FORMAT
|
|
value: "{{ .Values.paperless.files.filenameFormat }}"
|
|
{{- /*
|
|
ingress.domain is not a pure Ingress value: it is also
|
|
PAPERLESS_URL, the ONLY origin this release declares to Django, so
|
|
it stays required when ingress.enabled is false and in Gateway mode
|
|
(#114). The required() is here and not only in the Ingress template
|
|
because this is the read that cannot be switched off - an empty
|
|
value would render "https://" and every login POST would be refused
|
|
as a CSRF failure, with nothing in the log naming a chart value.
|
|
|
|
A template comment and not a YAML one on purpose: a "#" line here
|
|
is part of the rendered manifest, and this change has to leave
|
|
`helm template` byte-identical for unchanged values.
|
|
*/}}
|
|
- name: PAPERLESS_URL
|
|
value: "https://{{ required "A Domain/Host must be provided (ingress.domain) - it is also PAPERLESS_URL, the only origin this release declares" .Values.ingress.domain }}"
|
|
- name: PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE
|
|
value: "{{ $barcodes.asn.enabled }}"
|
|
- name: PAPERLESS_CONSUMER_ENABLE_BARCODES
|
|
value: "{{ $barcodes.enabled }}"
|
|
- name: PAPERLESS_CONSUMER_BARCODE_STRING
|
|
value: "{{ $barcodes.barcodeString }}"
|
|
- name: PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES
|
|
value: "{{ $barcodes.retainBarcodePage }}"
|
|
- name: PAPERLESS_CONSUMER_DELETE_DUPLICATES
|
|
value: "{{ .Values.paperless.files.deleteDuplicates }}"
|
|
{{- if .Values.paperless.files.tikaGotenberg.enabled }}
|
|
- name: PAPERLESS_TIKA_ENABLED
|
|
value: "{{ .Values.paperless.files.tikaGotenberg.enabled }}"
|
|
- name: PAPERLESS_TIKA_ENDPOINT
|
|
value: "http://{{ required "If Tika is enabled, a Tika-Endpoint must be provided" .Values.paperless.files.tikaGotenberg.tikaEndpoint }}"
|
|
- name: PAPERLESS_TIKA_GOTENBERG_ENDPOINT
|
|
value: "http://{{ required "If Tika is enabled, a Gotenberg-Endpoint must be provided" .Values.paperless.files.tikaGotenberg.gotenbergEndpoint }}"
|
|
{{- end }}
|
|
{{- if .Values.oidc.enabled }}
|
|
- name: PAPERLESS_APPS
|
|
value: "allauth.socialaccount.providers.openid_connect"
|
|
- name: PAPERLESS_SOCIALACCOUNT_PROVIDERS
|
|
value: |-
|
|
{
|
|
"openid_connect": {
|
|
"OAUTH_PKCE_ENABLED": true,
|
|
"APPS": [
|
|
{
|
|
"provider_id": "{{ required "A OIDC-provider-id must be provided" .Values.oidc.provider.id }}",
|
|
"name": "{{ required "A OIDC-provider-name must be provided" .Values.oidc.provider.name }}",
|
|
"client_id": "{{ required "A OIDC-client-id must be provided" .Values.oidc.provider.clientId }}",
|
|
"settings": {
|
|
"fetch_userinfo": true,
|
|
"oauth_pkce_enabled": true,
|
|
"server_url": "{{ required "A OIDC-discovery-document-url must be provided" .Values.oidc.provider.discoveryUrl }}",
|
|
{{- if .Values.oidc.provider.tokenAuthMethod }}
|
|
"token_auth_method": "{{ .Values.oidc.provider.tokenAuthMethod }}",
|
|
{{- end }}
|
|
"uid_field": "sub"
|
|
}
|
|
}
|
|
]
|
|
}
|
|
}
|
|
- name: PAPERLESS_REDIRECT_LOGIN_TO_SSO
|
|
value: "{{ .Values.oidc.autoLoginWithProvider }}"
|
|
- name: PAPERLESS_DISABLE_REGULAR_LOGIN
|
|
value: "{{ .Values.oidc.disableRegularLogin }}"
|
|
{{- end }}
|
|
{{- /*
|
|
QUOTED SINCE 13.0.0, and it is a fix rather than a tidy-up (Gitea
|
|
#112, same genus as #11). This block is the chart's additive env
|
|
interface, and until 13.0.0 it rendered its operand bare. Two
|
|
measured consequences, both of them the operator's value arriving
|
|
as something else:
|
|
|
|
- a value beginning with "*" aborted the render, with the error
|
|
pointing at THIS template rather than at the value;
|
|
- "true" and "0755" rendered as a YAML bool and an integer, and
|
|
the API server rejects those with "cannot unmarshal number
|
|
into Go struct field EnvVar.value of type string" - the chart
|
|
renders, the install fails.
|
|
|
|
The quoted form is the repository's documented one
|
|
(template-chart/templates/xxx.deployment.yaml carries the rule);
|
|
this chart was one of seven that had not been brought to it.
|
|
*/}}
|
|
{{- range $value := .Values.paperless.env }}
|
|
{{- if $value.value }}
|
|
- name: {{ $value.name | quote }}
|
|
value: {{ tpl $value.value $ | quote }}
|
|
{{- else if $value.valueFrom }}
|
|
{{- if $value.valueFrom.secretKeyRef }}
|
|
- name: {{ $value.name }}
|
|
valueFrom:
|
|
secretKeyRef:
|
|
name: {{ tpl $value.valueFrom.secretKeyRef.name $ | quote }}
|
|
key: {{ tpl $value.valueFrom.secretKeyRef.key $ | quote }}
|
|
{{- else if $value.valueFrom.configMapKeyRef }}
|
|
- name: {{ $value.name }}
|
|
valueFrom:
|
|
configMapKeyRef:
|
|
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ | quote }}
|
|
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ | quote }}
|
|
{{- end }}
|
|
{{- end }}
|
|
{{- end }}
|
|
volumeMounts:
|
|
- mountPath: "/usr/src/paperless/data"
|
|
name: "paperless-volume"
|
|
subPath: data/
|
|
- mountPath: "/usr/src/paperless/media"
|
|
name: "paperless-volume"
|
|
subPath: media/
|
|
- mountPath: "/usr/src/paperless/export"
|
|
name: "paperless-volume"
|
|
subPath: export/
|
|
- mountPath: "/usr/src/paperless/consume"
|
|
name: "paperless-consume-volume"
|
|
# Writable emptyDirs so the root filesystem can stay read-only:
|
|
# s6-overlay needs /run, OCR/consumption uses /tmp for scratch files.
|
|
- mountPath: /tmp
|
|
name: tmp
|
|
- mountPath: /run
|
|
name: run
|
|
resources:
|
|
{{- include "paperless-ngx.effectiveResources" .Values.paperless.resources | nindent 12 }}
|
|
readinessProbe:
|
|
{{- include "paperless-ngx.readinessProbe" .Values.paperless.readinessProbe | nindent 12 }}
|
|
livenessProbe:
|
|
{{- include "paperless-ngx.livenessProbe" .Values.paperless.livenessProbe | nindent 12 }}
|
|
startupProbe:
|
|
{{- include "paperless-ngx.startupProbe" .Values.paperless.startupProbe | nindent 12 }}
|
|
restartPolicy: Always
|
|
volumes:
|
|
- name: tmp
|
|
emptyDir: {}
|
|
- name: run
|
|
emptyDir: {}
|
|
- name: paperless-volume
|
|
persistentVolumeClaim:
|
|
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-volume-claim
|
|
- name: paperless-consume-volume
|
|
persistentVolumeClaim:
|
|
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-consume-volume-claim |