Add authenticated PDF editor for Paperless scans
This commit is contained in:
@@ -24,9 +24,15 @@ If an ADF scan immediately says `Document feeder out of documents` and `0 pages
|
||||
|
||||
To merge separate Paperless documents imported from JPG scans, enable **Try to include archive version in merge for non-PDF files** in the merge dialog. Paperless's default merge tries to open the original JPG as a PDF and skips it. Enable **Delete original documents after successful merge** if you want only the merged entry in the active document list; the source entries go to Paperless's trash. A single feeder batch sent through the scanner action already becomes one document, without a later merge.
|
||||
|
||||
## Rotate or crop an existing document
|
||||
|
||||
Paperless's **PDF Editor** works only when the selected file version is a PDF. For a scanned JPG, download its archived PDF from the document's download menu; Paperless creates that PDF during OCR. Open <https://scanner.brunner.ninja/pdf/> to use the self-hosted Stirling PDF editor, protected by the same Authentik gate as ScanservJS. Its **Rotate** and **Crop** tools let you turn pages and visually select the area to keep. Download the edited PDF, then return to the original Paperless document and choose **Versions → Upload new version**. The document ID, title, tags, correspondent, document type, permissions, and custom fields stay with that Paperless entry; the previous file remains available in version history. Once the new PDF version has imported, Paperless's own PDF Editor can rotate or rearrange it too.
|
||||
|
||||
Paperless has no native crop tool or custom button that sends a document to another editor. Cropping a PDF changes its visible page boundary without resampling the scan image. For sensitive content outside the crop, use a redaction tool rather than relying on crop alone. Stirling PDF uses the pinned upstream Ultra-Lite image and temporary container storage; no document PVC or cloud account is involved.
|
||||
|
||||
## CI/CD
|
||||
|
||||
`./test.sh` checks syntax, the deployment manifest, and the scanner PDF handoff. `./deploy.sh` updates the existing workloads, services, ConfigMap, and ingress using `app.yaml`. The GitLab and Gitea workflows call these same scripts. Bootstrap once with `./create-ci-kubeconfig.sh`, then save its single output line as the protected CI variable / Gitea Actions secret `KUBE_CONFIG_BASE64`. Do not commit it. The deployer has named-object read and patch permissions and cannot read Secrets. Run `./install.sh` manually for first installation or changes to `storage.yaml`, `postgresql-values.yaml`, or Authentik setup.
|
||||
`./test.sh` checks syntax, the deployment manifests, and the scanner PDF handoff. `./deploy.sh` updates the existing workloads, services, ConfigMap, and ingresses using `app.yaml` and `pdf-editor.yaml`. The GitLab and Gitea workflows call these same scripts. Bootstrap once with `./create-ci-kubeconfig.sh`, then save its single output line as the protected CI variable / Gitea Actions secret `KUBE_CONFIG_BASE64`. Do not commit it. The deployer has named-object read and patch permissions and cannot read Secrets. Run `./install.sh` manually for first installation or changes to `storage.yaml`, `postgresql-values.yaml`, or Authentik setup.
|
||||
|
||||
The Git remote is `gitea@brunner.ninja:feedc0de/paperless-ngx-deployment.git`. No credentials or generated kubeconfig are committed. To publish changes: `git add . && git commit -m 'Deploy Paperless-ngx' && git push origin main`.
|
||||
|
||||
|
||||
+3
-3
@@ -12,15 +12,15 @@ metadata:
|
||||
rules:
|
||||
- apiGroups: [apps]
|
||||
resources: [deployments]
|
||||
resourceNames: [paperless, valkey, scanner]
|
||||
resourceNames: [paperless, valkey, scanner, pdf-editor]
|
||||
verbs: [get, patch, watch]
|
||||
- apiGroups: [""]
|
||||
resources: [services]
|
||||
resourceNames: [paperless, valkey, scanner]
|
||||
resourceNames: [paperless, valkey, scanner, pdf-editor]
|
||||
verbs: [get, patch]
|
||||
- apiGroups: [networking.k8s.io]
|
||||
resources: [ingresses]
|
||||
resourceNames: [paperless, scanner]
|
||||
resourceNames: [paperless, scanner, pdf-editor]
|
||||
verbs: [get, patch]
|
||||
- apiGroups: [""]
|
||||
resources: [configmaps]
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
set -euo pipefail
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")"
|
||||
./test.sh
|
||||
apply_output=$(kubectl apply -f app.yaml)
|
||||
apply_output=$(kubectl apply -f app.yaml -f pdf-editor.yaml)
|
||||
printf '%s\n' "$apply_output"
|
||||
if [[ $apply_output == *'configmap/scanner-config configured'* ]]; then
|
||||
kubectl -n paperless-ngx rollout restart deployment/scanner
|
||||
@@ -10,3 +10,4 @@ fi
|
||||
kubectl -n paperless-ngx rollout status deployment/valkey --timeout=5m || { kubectl -n paperless-ngx describe deployment valkey; exit 1; }
|
||||
kubectl -n paperless-ngx rollout status deployment/paperless --timeout=15m || { kubectl -n paperless-ngx describe deployment paperless; exit 1; }
|
||||
kubectl -n paperless-ngx rollout status deployment/scanner --timeout=10m || { kubectl -n paperless-ngx describe deployment scanner; kubectl -n paperless-ngx logs deployment/scanner --tail=100; exit 1; }
|
||||
kubectl -n paperless-ngx rollout status deployment/pdf-editor --timeout=10m || { kubectl -n paperless-ngx describe deployment pdf-editor; kubectl -n paperless-ngx logs deployment/pdf-editor --tail=100; exit 1; }
|
||||
|
||||
+3
-2
@@ -21,8 +21,8 @@ fi
|
||||
./setup-authentik.sh
|
||||
./test.sh
|
||||
helm upgrade --install postgresql postgres --repo https://groundhog2k.github.io/helm-charts/ --version 1.6.7 -n paperless-ngx -f postgresql-values.yaml --wait --timeout 10m
|
||||
kubectl apply --dry-run=server -f storage.yaml -f app.yaml
|
||||
apply_output=$(kubectl apply -f storage.yaml -f app.yaml)
|
||||
kubectl apply --dry-run=server -f storage.yaml -f app.yaml -f pdf-editor.yaml
|
||||
apply_output=$(kubectl apply -f storage.yaml -f app.yaml -f pdf-editor.yaml)
|
||||
printf '%s\n' "$apply_output"
|
||||
if [[ $apply_output == *'configmap/scanner-config configured'* ]]; then
|
||||
kubectl -n paperless-ngx rollout restart deployment/scanner
|
||||
@@ -30,4 +30,5 @@ fi
|
||||
kubectl -n paperless-ngx rollout status deployment/valkey --timeout=5m
|
||||
kubectl -n paperless-ngx rollout status deployment/paperless --timeout=15m
|
||||
kubectl -n paperless-ngx rollout status deployment/scanner --timeout=10m
|
||||
kubectl -n paperless-ngx rollout status deployment/pdf-editor --timeout=10m
|
||||
kubectl -n paperless-ngx exec deployment/paperless -- python manage.py shell -c 'from django.contrib.auth.models import Group; [Group.objects.get_or_create(name=name) for name in ("Paperless Users", "Paperless Admins")]'
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: pdf-editor
|
||||
namespace: paperless-ngx
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels: {app: pdf-editor}
|
||||
template:
|
||||
metadata:
|
||||
labels: {app: pdf-editor}
|
||||
spec:
|
||||
automountServiceAccountToken: false
|
||||
containers:
|
||||
- name: pdf-editor
|
||||
image: docker.io/stirlingtools/stirling-pdf:3.1.0-ultra-lite@sha256:b19e480530e5812f96f1cddcf467bc0e652a252de98fdd36587b33ea04e75251
|
||||
ports: [{name: http, containerPort: 8080}]
|
||||
env:
|
||||
- {name: SYSTEM_ROOTURIPATH, value: /pdf}
|
||||
- {name: SYSTEM_ENABLEANALYTICS, value: "false"}
|
||||
- {name: SECURITY_ENABLELOGIN, value: "false"}
|
||||
startupProbe:
|
||||
httpGet: {path: /pdf/, port: http}
|
||||
periodSeconds: 5
|
||||
failureThreshold: 60
|
||||
readinessProbe:
|
||||
httpGet: {path: /pdf/, port: http}
|
||||
periodSeconds: 10
|
||||
livenessProbe:
|
||||
httpGet: {path: /pdf/, port: http}
|
||||
periodSeconds: 30
|
||||
resources:
|
||||
requests: {cpu: 100m, memory: 512Mi}
|
||||
limits: {memory: 2Gi}
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: pdf-editor
|
||||
namespace: paperless-ngx
|
||||
spec:
|
||||
selector: {app: pdf-editor}
|
||||
ports: [{name: http, port: 8080, targetPort: http}]
|
||||
---
|
||||
apiVersion: networking.k8s.io/v1
|
||||
kind: Ingress
|
||||
metadata:
|
||||
name: pdf-editor
|
||||
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: /pdf
|
||||
pathType: Prefix
|
||||
backend:
|
||||
service:
|
||||
name: pdf-editor
|
||||
port: {name: http}
|
||||
@@ -10,12 +10,17 @@ from pathlib import Path
|
||||
import yaml
|
||||
|
||||
documents = []
|
||||
for path in ("storage.yaml", "app.yaml", "ci-deployer.yaml"):
|
||||
for path in ("storage.yaml", "app.yaml", "pdf-editor.yaml", "ci-deployer.yaml"):
|
||||
with open(path, encoding="utf-8") as stream:
|
||||
documents.extend(doc for doc in yaml.safe_load_all(stream) if doc)
|
||||
images = [container["image"] for doc in documents if doc["kind"] == "Deployment"
|
||||
for container in doc["spec"]["template"]["spec"]["containers"]]
|
||||
assert images and all(":" in image and not image.endswith(":latest") for image in images)
|
||||
editor_ingress = next(doc for doc in documents if doc["kind"] == "Ingress"
|
||||
and doc["metadata"]["name"] == "pdf-editor")
|
||||
assert editor_ingress["spec"]["rules"][0]["host"] == "scanner.brunner.ninja"
|
||||
assert editor_ingress["spec"]["rules"][0]["http"]["paths"][0]["path"] == "/pdf"
|
||||
assert "authentik" in editor_ingress["metadata"]["annotations"]["traefik.ingress.kubernetes.io/router.middlewares"]
|
||||
config = next(doc["data"]["config.local.js"] for doc in documents
|
||||
if doc["kind"] == "ConfigMap" and doc["metadata"]["name"] == "scanner-config")
|
||||
with tempfile.TemporaryDirectory() as temp:
|
||||
|
||||
Reference in New Issue
Block a user