Files
feedc0de 3db0328124
Test and deploy / test (push) Successful in 11s
Test and deploy / deploy (push) Failing after 14s
Move Paperless to PostgreSQL and block-backed runtime storage
2026-10-05 09:24:50 +02:00

298 lines
10 KiB
YAML

apiVersion: apps/v1
kind: Deployment
metadata:
name: valkey
namespace: paperless-ngx
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels: {app: valkey}
template:
metadata:
labels: {app: valkey}
spec:
automountServiceAccountToken: false
containers:
- name: valkey
image: valkey/valkey:9.0.6
args: ["--save", "", "--appendonly", "no"]
ports: [{name: redis, containerPort: 6379}]
readinessProbe:
exec: {command: [valkey-cli, ping]}
periodSeconds: 5
livenessProbe:
exec: {command: [valkey-cli, ping]}
periodSeconds: 20
resources:
requests: {cpu: 25m, memory: 64Mi}
---
apiVersion: v1
kind: Service
metadata:
name: valkey
namespace: paperless-ngx
spec:
selector: {app: valkey}
ports: [{name: redis, port: 6379, targetPort: redis}]
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: paperless
namespace: paperless-ngx
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels: {app: paperless}
template:
metadata:
labels: {app: paperless}
spec:
automountServiceAccountToken: false
enableServiceLinks: false
tolerations:
- key: node-role.kubernetes.io/control-plane
operator: Exists
effect: NoSchedule
affinity:
nodeAffinity:
preferredDuringSchedulingIgnoredDuringExecution:
- weight: 100
preference:
matchExpressions:
- key: kubernetes.io/hostname
operator: In
values: [arschrock]
containers:
- name: paperless
image: ghcr.io/paperless-ngx/paperless-ngx:3.2.1
ports: [{name: http, containerPort: 8000}]
env:
- {name: PAPERLESS_URL, value: "https://paper.brunner.ninja"}
- {name: PAPERLESS_REDIS, value: "redis://valkey:6379"}
- {name: PAPERLESS_DBENGINE, value: postgresql}
- {name: PAPERLESS_DBHOST, value: postgresql}
- {name: PAPERLESS_DBNAME, value: paperless}
- {name: PAPERLESS_DBUSER, value: paperless}
- name: PAPERLESS_DBPASS
valueFrom:
secretKeyRef: {name: paperless-postgres, key: USERDB_PASSWORD}
- {name: PAPERLESS_TIME_ZONE, value: "Europe/Vienna"}
- {name: PAPERLESS_OCR_LANGUAGE, value: "deu+eng"}
- {name: PAPERLESS_TASK_WORKERS, value: "24"}
- {name: PAPERLESS_THREADS_PER_WORKER, value: "1"}
- {name: PAPERLESS_CONSUMER_POLLING_INTERVAL, value: "30"}
- {name: PAPERLESS_APPS, value: "allauth.socialaccount.providers.openid_connect"}
- {name: PAPERLESS_SOCIAL_AUTO_SIGNUP, value: "true"}
- {name: PAPERLESS_SOCIAL_ACCOUNT_SYNC_GROUPS, value: "true"}
- {name: PAPERLESS_SOCIAL_ACCOUNT_SYNC_SUPERUSER_GROUP, value: "Paperless Admins"}
- name: PAPERLESS_SOCIALACCOUNT_PROVIDERS
valueFrom:
secretKeyRef: {name: paperless-oidc, key: providers}
- name: PAPERLESS_SECRET_KEY
valueFrom:
secretKeyRef: {name: paperless-secret-key, key: secret-key}
volumeMounts:
- {name: runtime, mountPath: /usr/src/paperless/data}
- {name: data, mountPath: /usr/src/paperless/media, subPath: media}
- {name: data, mountPath: /usr/src/paperless/consume, subPath: consume}
- {name: data, mountPath: /usr/src/paperless/export, subPath: export}
startupProbe:
httpGet: {path: /, port: http}
periodSeconds: 10
failureThreshold: 60
readinessProbe:
httpGet: {path: /, port: http}
periodSeconds: 10
livenessProbe:
httpGet: {path: /, port: http}
periodSeconds: 30
resources:
requests: {cpu: 100m, memory: 1Gi}
volumes:
- name: runtime
persistentVolumeClaim: {claimName: paperless-runtime}
- name: data
persistentVolumeClaim: {claimName: paperless-data}
---
apiVersion: v1
kind: Service
metadata:
name: paperless
namespace: paperless-ngx
spec:
selector: {app: paperless}
ports: [{name: http, port: 8000, targetPort: http}]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: paperless
namespace: paperless-ngx
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
spec:
ingressClassName: traefik
rules:
- host: paper.brunner.ninja
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: paperless
port: {name: http}
---
apiVersion: v1
kind: ConfigMap
metadata:
name: scanner-config
namespace: paperless-ngx
data:
config.local.js: |
const fs = require('node:fs/promises');
const path = require('node:path');
const crypto = require('node:crypto');
const output = process.env.SCAN_OUTPUT_DIR || '/var/lib/scanservjs/output';
const consume = process.env.PAPERLESS_CONSUME_DIR || '/paperless-consume';
module.exports = {
actions: [{
name: 'Send to Paperless',
async execute(fileInfo) {
const source = await fs.realpath(fileInfo.fullname);
const images = new Set(['.jpg', '.jpeg', '.png', '.tif', '.tiff', '.gif', '.webp']);
const extension = path.extname(source).toLowerCase();
if (!source.startsWith(output + '/') || !(['.pdf', '.zip'].includes(extension) || images.has(extension))) {
throw new Error('Only staged PDFs, images, and image ZIPs can be sent');
}
let upload = source;
let temp;
let name = path.basename(source);
let pending;
try {
if (extension === '.zip') {
const AdmZip = require(require.resolve('adm-zip', {paths: ['/usr/lib/scanservjs']}));
const entries = new AdmZip(source).getEntries();
if (entries.length === 0 || entries.some(entry =>
entry.isDirectory || entry.entryName !== path.basename(entry.entryName) ||
!images.has(path.extname(entry.entryName).toLowerCase()))) {
throw new Error('ZIP must contain only scanned image pages');
}
temp = await fs.mkdtemp('/tmp/paperless-scan-');
const inputs = [];
for (const [index, entry] of entries.entries()) {
const input = path.join(temp, `${String(index).padStart(4, '0')}${path.extname(entry.entryName)}`);
await fs.writeFile(input, entry.getData());
inputs.push(input);
}
upload = path.join(temp, 'merged.pdf');
const {execFile} = require('node:child_process');
const {promisify} = require('node:util');
await promisify(execFile)('convert', [...inputs, upload]);
name = path.basename(source, path.extname(source)) + '.pdf';
}
const destination = path.join(consume, `${Date.now()}-${crypto.randomBytes(6).toString('hex')}-${name}`);
pending = destination + '.part';
await fs.copyFile(upload, pending);
await fs.rename(pending, destination);
await fs.unlink(source);
} catch (error) {
if (pending) await fs.rm(pending, {force: true});
throw error;
} finally {
if (temp) await fs.rm(temp, {recursive: true, force: true});
}
},
}],
};
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: scanner
namespace: paperless-ngx
spec:
replicas: 1
strategy:
type: Recreate
selector:
matchLabels: {app: scanner}
template:
metadata:
labels: {app: scanner}
spec:
automountServiceAccountToken: false
securityContext:
supplementalGroups: [1000]
initContainers:
- name: prepare-output
image: busybox:1.37.0
command: [sh, -c, 'mkdir -p /volume/scanner-staging && chown 102:100 /volume/scanner-staging && chmod 775 /volume/scanner-staging && chmod g+w /volume/consume']
volumeMounts:
- {name: data, mountPath: /volume}
containers:
- name: scanner
image: sbs20/scanservjs:3.3.0
command: [sh, -c, "sed -i '/^\\[options\\]/a discovery = disable' /etc/sane.d/airscan.conf && exec /entrypoint.sh"]
env:
- {name: AIRSCAN_DEVICES, value: '"HP OfficeJet Pro 8020" = http://192.168.4.189:8080/eSCL, eSCL'}
ports: [{name: http, containerPort: 8080}]
volumeMounts:
- {name: data, mountPath: /var/lib/scanservjs/output, subPath: scanner-staging}
- {name: data, mountPath: /paperless-consume, subPath: consume}
- {name: config, mountPath: /etc/scanservjs/config.local.js, subPath: config.local.js, readOnly: true}
startupProbe:
httpGet: {path: /, port: http}
periodSeconds: 5
failureThreshold: 60
readinessProbe:
httpGet: {path: /, port: http}
periodSeconds: 10
livenessProbe:
httpGet: {path: /, port: http}
periodSeconds: 30
resources:
requests: {cpu: 100m, memory: 256Mi}
volumes:
- name: data
persistentVolumeClaim: {claimName: paperless-data}
- name: config
configMap: {name: scanner-config}
---
apiVersion: v1
kind: Service
metadata:
name: scanner
namespace: paperless-ngx
spec:
selector: {app: scanner}
ports: [{name: http, port: 8080, targetPort: http}]
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: scanner
namespace: paperless-ngx
annotations:
traefik.ingress.kubernetes.io/router.entrypoints: websecure
traefik.ingress.kubernetes.io/router.middlewares: default-authentik@kubernetescrd
spec:
ingressClassName: traefik
rules:
- host: scanner.brunner.ninja
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: scanner
port: {name: http}