feat(paperless-ngx): initial chart

This commit is contained in:
jklein committed 2026-03-23 17:43:12 +01:00
1 parent 8d0f3eaf96
commit 0d7adfd4ed
16 files changed
+499

No files matched your search

+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/
+12
View File
@@ -0,0 +1,12 @@
apiVersion: v2
name: paperless-ngx
description: A Helm chart to deploy Paperless-NGX on Kubernetes
type: application
version: 4.4.0+up2.20.6
appVersion: 2.20.6
maintainers:
- name: jklein
@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-db-secret
spec:
itemPath: {{ required "A secret-reference for the DB-Secret must be provided" .Values.secrets.databaseRef }}
@@ -0,0 +1,35 @@
{{- if .Values.smbConnection.enabled }}
apiVersion: batch/v1
kind: CronJob
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-consume-cronjob
spec:
schedule: {{ .Values.smbConnection.cronJob }}
jobTemplate:
spec:
template:
spec:
containers:
- name: paperless-consume-cronjob
image: busybox:1.28
imagePullPolicy: IfNotPresent
command:
- /bin/sh
- -c
- if [ "$(find /mnt/smb -maxdepth 1 -name "*.pdf" -print)" ]; then mv /mnt/smb/*.pdf /mnt/consume; fi
volumeMounts:
- name: smb-share
mountPath: "/mnt/smb"
readOnly: false
- name: consume-folder
mountPath: "/mnt/consume"
readOnly: false
restartPolicy: Never
volumes:
- name: smb-share
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-smb-volume-claim
- name: consume-folder
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-consume-volume-claim
{{- end }}
@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-consume-volume-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: longhorn
@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-secret
spec:
itemPath: {{ required "A secret-reference for the Paperless-Secret must be provided" .Values.secrets.paperlessRef }}
@@ -0,0 +1,190 @@
apiVersion: apps/v1
kind: StatefulSet
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
labels:
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
spec:
replicas: 1
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
containers:
- name: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
image: "ghcr.io/paperless-ngx/paperless-ngx:{{ .Chart.AppVersion }}"
imagePullPolicy: IfNotPresent
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_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 }}"
- name: PAPERLESS_URL
value: "https://{{ .Values.ingress.domain }}"
- name: PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE
value: "{{ .Values.paperless.files.barcodes.asn.enabled }}"
- name: PAPERLESS_CONSUMER_ENABLE_BARCODES
value: "{{ .Values.paperless.files.barcodes.enabled }}"
- name: PAPERLESS_CONSUMER_BARCODE_STRING
value: "{{ .Values.paperless.files.barcodes.barcodeString }}"
- name: PAPERLESS_CONSUMER_BARCODE_RETAIN_SPLIT_PAGES
value: "{{ .Values.paperless.files.barcodes.retainBarcodePage }}"
- name: PAPERLESS_CONSUMER_BARCODE_SCANNER
value: "{{ .Values.paperless.files.barcodes.scanner }}"
{{- 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 }}",
"uid_field": "sub"
}
}
]
}
}
- name: PAPERLESS_REDIRECT_LOGIN_TO_SSO
value: "{{ .Values.oidc.autoLoginWithProvider }}"
- name: PAPERLESS_DISABLE_REGULAR_LOGIN
value: "{{ .Values.oidc.disableRegularLogin }}"
{{- 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"
resources:
requests:
memory: "{{ .Values.paperless.resources.requests.memory }}"
cpu: "{{ .Values.paperless.resources.requests.cpu }}"
ephemeral-storage: "{{ .Values.paperless.resources.requests.ephemeralStorage }}"
limits:
memory: "{{ .Values.paperless.resources.limits.memory }}"
cpu: "{{ .Values.paperless.resources.limits.cpu }}"
ephemeral-storage: "{{ .Values.paperless.resources.limits.ephemeralStorage }}"
readinessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 30
periodSeconds: 10
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 5
livenessProbe:
httpGet:
path: /
port: 8000
initialDelaySeconds: 90
periodSeconds: 30
failureThreshold: 3
successThreshold: 1
timeoutSeconds: 5
restartPolicy: Always
volumes:
- name: paperless-volume
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-volume-claim
- name: paperless-consume-volume
persistentVolumeClaim:
claimName: {{ .Release.Name }}-{{ .Chart.Name }}-consume-volume-claim
@@ -0,0 +1,29 @@
{{- if .Values.smbConnection.enabled }}
apiVersion: v1
kind: PersistentVolume
metadata:
annotations:
pv.kubernetes.io/provisioned-by: smb.csi.k8s.io
name: {{ .Release.Name }}-{{ .Chart.Name }}-smb-pv
namespace: {{ .Release.Namespace }}
spec:
capacity:
storage: 1Gi
accessModes:
- ReadWriteMany
persistentVolumeReclaimPolicy: Retain
storageClassName: smb
mountOptions:
- dir_mode=0777
- file_mode=0777
csi:
driver: smb.csi.k8s.io
# volumeHandle format: {smb-server-address}#{sub-dir-name}#{share-name}
# make sure this value is unique for every share in the cluster
volumeHandle: "{{ .Values.smbConnection.server }}##"
volumeAttributes:
source: "//{{ .Values.smbConnection.server }}"
nodeStageSecretRef:
name: "{{ .Release.Name }}-{{ .Chart.Name }}-smb-secret"
namespace: {{ .Release.Namespace }}
{{- end }}
@@ -0,0 +1,14 @@
{{- if .Values.smbConnection.enabled }}
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-smb-volume-claim
spec:
accessModes:
- ReadWriteMany
resources:
requests:
storage: 1Gi
storageClassName: smb
volumeName: {{ .Release.Name }}-{{ .Chart.Name }}-smb-pv
{{- end }}
@@ -0,0 +1,8 @@
{{- if .Values.smbConnection.enabled }}
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-smb-secret
spec:
itemPath: {{ required "A secret-reference for the SMB-Connection-Secret must be provided" .Values.secrets.smbConnectionSecretRef }}
{{- end }}
@@ -0,0 +1,25 @@
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-ingress
annotations:
cert-manager.io/cluster-issuer: {{ required "A Cluster-Issuer must be provided" .Values.ingress.clusterIssuer}}
nginx.ingress.kubernetes.io/proxy-body-size: {{ .Values.paperless.files.maxUploadFileSize }}
nginx.org/client-max-body-size: {{ .Values.paperless.files.maxUploadFileSize }}
spec:
ingressClassName: nginx
rules:
- host: {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
http:
paths:
- backend:
service:
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
port:
name: pless-s-port
path: /
pathType: Prefix
tls:
- hosts:
- {{ required "A Domain/Host must be provided" .Values.ingress.domain }}
secretName: tls-{{ .Release.Name }}-{{ .Chart.Name }}-{{ .Values.ingress.domain }}-ingress
@@ -0,0 +1,11 @@
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-volume-claim
spec:
accessModes:
- ReadWriteOnce
resources:
requests:
storage: {{ .Values.paperless.files.storage }}
storageClassName: longhorn
@@ -0,0 +1,13 @@
apiVersion: v1
kind: Service
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-service
spec:
selector:
app: {{ .Release.Name }}-{{ .Chart.Name }}-sfs
ports:
- protocol: TCP
port: 80
targetPort: paperless-port
name: pless-s-port
type: ClusterIP
@@ -0,0 +1,53 @@
{{- if false }}
apiVersion: v1
kind: ConfigMap
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-pre-post-consumption-scripts
data:
pre-consume-script: |-
#!/bin/sh
set -x
# Pre-Consumption Scripts:
{{- range $key, $value := .Values.paperless.scripts.preConsumptionScripts }}
/scripts/{{ $value }}
{{- end }}
post-consume-script: |-
#!/bin/sh
set -x
# Post-Consumption Scripts:
{{- range $key, $value := .Values.paperless.scripts.postConsumptionScripts }}
/scripts/{{ $value }}
{{- end }}
remove-blank-lines: |-
#!/bin/bash
set -e -o pipefail
export LC_ALL=C
#IN="$1"
IN="$DOCUMENT_WORKING_PATH"
PAGES=$(pdfinfo "$IN" | grep ^Pages: | tr -dc '0-9')
THRESHOLD=0.002
non_blank() {
for i in $(seq 1 $PAGES) ; do
PERCENT=$(gs -o - -dFirstPage=${i} -dLastPage=${i} -sDEVICE=inkcov "${IN}" | grep CMYK | nawk 'BEGIN { sum=0; } {sum += $1 + $2 + $3 + $4;} END { printf "%.5f\n", sum } ')
if awk "BEGIN { exit !($PERCENT > $THRESHOLD) }"; then
echo $i
else
>&2 echo Color-sum is $PERCENT: will remove blank page $i of $IN
fi
done
}
NON_BLANK=$(non_blank)
if [ -n "$NON_BLANK" ]; then
NON_BLANK=$(echo $NON_BLANK | tr ' ' ",")
qpdf "$IN" --replace-input --pages . $NON_BLANK --
fi
{{- end }}
@@ -0,0 +1,6 @@
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
name: {{ .Release.Name }}-{{ .Chart.Name }}-redis-secret
spec:
itemPath: {{ required "A secret-reference for the Redis-Secret must be provided" .Values.secrets.redisRef }}
+57
View File
@@ -0,0 +1,57 @@
secrets:
redisRef: null
databaseRef: null
paperlessRef: null
smbConnectionSecretRef: null
oidcSecretRef: null
ingress:
clusterIssuer: "letsencrypt-issuer"
domain: null
paperless:
ocr:
languages: "eng"
mode: "skip"
rotate_pages: "true"
output_type: "pdfa"
files:
filenameFormat: "{{ created_year }}/{{ correspondent }}/{{ title }}"
storage: 20Gi
maxUploadFileSize: 10m
barcodes:
enabled: true
barcodeString: "PATCHT"
retainBarcodePage: false
asn:
enabled: true
scanner: PYZBAR
tikaGotenberg:
enabled: false
tikaEndpoint: null
gotenbergEndpoint: null
scripts:
postConsumptionScripts: []
preConsumptionScripts: []
resources:
requests:
cpu: 0.2
memory: 1024Mi
ephemeralStorage: 200Mi
limits:
cpu: 1
memory: 2048Mi
ephemeralStorage: 400Mi
smbConnection:
enabled: false
server: null
cronJob: "0 0,12 * * *"
oidc:
enabled: false
autoLoginWithProvider: false
disableRegularLogin: false
provider:
id: null
name: null
clientId: null
discoveryUrl: null