Files
helm-charts/paperless-ngx/templates/paperless-server.sfs.yaml
T
jklein bd1fdc09a0
Validate Helm Charts / Detect changed charts (pull_request) Successful in 4s
Validate Helm Charts / Resource quantities (pull_request) Successful in 14s
Validate Helm Charts / Selector match (pull_request) Successful in 14s
Validate Helm Charts / HTTPRoute match (pull_request) Successful in 14s
Validate Helm Charts / Build freesailarr (pull_request) Successful in 7s
Validate Helm Charts / Build outline (pull_request) Successful in 7s
Validate Helm Charts / Build paperless-ngx (pull_request) Successful in 8s
Validate Helm Charts / Build zipline (pull_request) Successful in 8s
Validate Helm Charts / Build calibre-web-automated (pull_request) Successful in 7s
Validate Helm Charts / Trivy config calibre-web-automated (pull_request) Successful in 19s
Validate Helm Charts / Trivy config freesailarr (pull_request) Successful in 24s
Validate Helm Charts / Trivy config outline (pull_request) Successful in 17s
Validate Helm Charts / Trivy config paperless-ngx (pull_request) Successful in 19s
Validate Helm Charts / Trivy config zipline (pull_request) Successful in 19s
Install-test / Install-test calibre-web-automated (pull_request) Successful in 2m11s
Install-test / Install-test freesailarr (pull_request) Successful in 3m55s
Install-test / Install-test outline (pull_request) Successful in 2m44s
Install-test / Install-test paperless-ngx (pull_request) Successful in 3m52s
Install-test / Install-test zipline (pull_request) Successful in 2m6s
Validate Helm Charts / Install-test (pull_request) Successful in 14m50s
feat: Gateway-API as an alternative to the Ingress in five annotation charts
Group 2 of the #114 sweep: the charts whose behaviour hangs off an nginx
annotation - calibre-web-automated, outline, paperless-ngx, zipline
(proxy-body-size) and freesailarr (rewrite-target).

Each gets the form the pilot (cyberchef, PR #115) fixed and group 1 (PR #116)
repeated: an httpRoute block off by default, a template rendering ONLY an
HTTPRoute with parentRefs - no Gateway, no gatewayClassName, no Certificate, no
implementation-specific policy object - render aborts on both modes at once
(naming both value paths), on an empty parentRefs and on a missing hostname,
empty hostnames meaning [ingress.domain], a schema entry closed against unknown
keys with an open parentRef definition, and a NOTES.txt that names the active
mode and what the mode costs.

Minor, additive, defaults unchanged: `helm template` with each chart's CI values
is byte-identical to origin/main for all five.

The upload limit is DOCUMENTED, not rebuilt (decision 2 of the issue). The
Gateway API knows no body size; a policy object would bind the chart to one
implementation. So the Gateway branch of NOTES.txt says, per chart and in the
chart's own terms, that the value is NOT ENFORCED and names where to set it: an
e-book library (calibre), an upload service (zipline), the web-UI half of the
document intake while the consume directory keeps no limit at all (paperless),
and the open #12 pair (outline).

Four findings that were not in the plan, each argued in the file it changes:

  freesailarr gated freesailarr.sharedIngressApps on ingress.enabled, and that
  list is read by the <APP>__SERVER__URLBASE variables as well as by the routing
  objects. In Gateway mode it would have come out EMPTY while the route served
  radarr, sonarr and prowlarr under their prefixes: UrlBase unset, assets and API
  calls linked at the host root, broken UI behind a Ready pod. The helper's two
  guards (seerr/mediathekarr only at "/", no duplicate paths) would have gone
  unenforced there too. Fixed with freesailarr.published (ingress.enabled OR
  httpRoute.enabled) - the same shape as sure.publishedOverTls in group 1, and
  identical to ingress.enabled while httpRoute.enabled is false. Measured: on
  origin/main with ingress.enabled: false the render emits ZERO URLBASE
  variables; with this change in Gateway mode it emits three.

  paperless-ngx was the last chart in the repository rendering its Ingress
  UNCONDITIONALLY. Without an ingress.enabled switch there is no second value
  path for the mutual-exclusion abort to name, and the Ingress plus its
  unconditional required() on clusterIssuer would have kept rendering in Gateway
  mode - a value demanded for nothing, which is issue #114's "Folge 1" and
  README guideline 12. The switch was added, defaulting to true, so the render is
  unchanged.

  paperless-ngx PAPERLESS_URL had no required() of its own: it relied on the
  Ingress template's, which stops firing the moment the Ingress is switchable.
  With httpRoute.hostnames set and ingress.domain empty it would have rendered
  "https://" and refused every login POST as a CSRF failure. It now carries its
  own required(), naming what the value is for.

  calibre-web-automated claimed, in values.yaml and in its schema, that
  ingress.domain is read nowhere but in the Ingress template. The HTTPRoute reads
  it, so the claim now holds only while both modes are off. Corrected rather than
  left standing - the same correction garage needed in group 1.

freesailarr is where the Gateway API is LESS machinery than what it replaces,
not a translation of it. rewrite-target applies per Ingress OBJECT, so
qBittorrent (no base path, prefix must be stripped) needed a second Ingress on
the shared host, which then had to repeat the first object's TLS secret name,
omit the cert-manager annotation conditionally so only one Certificate is
requested, and match an ImplementationSpecific regex path for the rewrite's
capture group. A URLRewrite filter applies per RULE: one route, one rule per
app, the filter on qBittorrent's rule, a plain PathPrefix. All four crutches
gone. It is also the only chart without httpRoute.hostnames - it has no
ingress.domain either, and a chart-global hostname list could not say which of
the ten apps it belonged to, so the hostnames stay in ingress.host and
<app>.ingress.host.

CI: one render overlay per chart under ci/httproute/, so the assertion is not
vacuum-green - ci/run-httproute-match-test.sh goes from 20 held assertions over
10 charts to 36 over 15. freesailarr's overlay moves bazarr onto a host of its
own, so the shared-host route AND the per-app route are both rendered; without
that the per-app branch would be exercised by nothing. The install-test fixtures
stay in Ingress mode: the install runs one configuration per chart, cyberchef
already pays that price against the real Gateway-API CRDs, and for paperless-ngx
that fixture is the only install of the valkey-subchart path anywhere.

README also carries a stale figure fixed here: the HTTPRoute test was still
documented at the pilot's 4 assertions over 2 charts, although group 1 had taken
it to 20 over 10.

Not proven, by construction: that traffic flows. No Gateway controller runs in
CI. Not run locally either: k3d, Docker, Trivy. The Gateway branch of NOTES.txt
is EXECUTED by the render test (a template error in it fails the job, verified)
but its text is printed by no pipeline stage - helm template does not emit
notes, and the install-test installs in Ingress mode.

Part of #114
2026-10-12 18:15:29 +00:00

264 lines
12 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 }}
{{- range $value := .Values.paperless.env }}
{{- if $value.value }}
- name: {{ $value.name }}
value: {{ tpl $value.value $ }}
{{- else if $value.valueFrom }}
{{- if $value.valueFrom.secretKeyRef }}
- name: {{ $value.name }}
valueFrom:
secretKeyRef:
name: {{ tpl $value.valueFrom.secretKeyRef.name $ }}
key: {{ tpl $value.valueFrom.secretKeyRef.key $ }}
{{- else if $value.valueFrom.configMapKeyRef }}
- name: {{ $value.name }}
valueFrom:
configMapKeyRef:
name: {{ tpl $value.valueFrom.configMapKeyRef.name $ }}
key: {{ tpl $value.valueFrom.configMapKeyRef.key $ }}
{{- 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