Zum Inhalt

E7 · Paperless v3 Neustart — Design

Datum: 2026-09-19 Epic: homeserver#190 (dazu #87, #242) Status: Design abgestimmt mit Robin (Planungssession 18./19.09.2026), bereit für den Umsetzungsplan Löst ab: die nur lokal vorliegende Juni-Spec homeserver/docs/superpowers/specs/2026-06-14-paperless-ngx-v3-ollama-design.md (v3-Beta gegen ollama-ipex auf dem NUC — überholt durch paperless-ngx 3.1 und den Entscheid, dass alles KI-seitige auf den aiserver wandert). Pfadangaben wie paperless-ngx/… und infrastructure/… beziehen sich auf das Repo homeserver.

1. Kontext und Ziel

Paperless soll produktiv werden: Robins gesamter Papier- und Mailbestand wandert hinein, der Zulauf läuft über den Netzwerk-Scanner und Proton Mail, die KI-Kette über llama-swap auf dem aiserver. Der heutige Stack (paperless-ngx:2.20, ein Testdokument) war nur zum Ausprobieren da und wird komplett neu aufgesetzt, ohne Datenübernahme.

Nicht Ziel dieser Epic: Zugriff von außen, Lars' Konto, SSO, Dropbox-Spiegel, Restore-Probe (alles bewusst in E7b, siehe Abschnitt 12).

2. Entscheidungen

Frage Entscheidung Quelle
Datenübernahme Keine. Greenfield, alte Daten werden geleert Robin 18.09.
Zugriff von außen Keiner. Nur interner Traefik-Router Robin 19.09.
Nutzerkreis Zielbild: Robin und Lars mit getrennten Beständen. Jetzt nur Robins Konto; Lars in E8 oder E7b Robin 19.09.
Gemeinsame Dokumente Ohne Eigentümer (Paperless zeigt sie allen Konten = Robin und Lars), Speicherpfad gemeinsam/… neben robin/ und lars/. Bis Lars' Konto kommt: normal scannen, Tag Gemeinsam, später in einem Schritt umstellen. Kommt ein drittes Konto dazu → Gruppe „Haushalt" statt eigentümerlos. Ein ASN-Kreis für den Haushalt (zweite Etikettenrolle nur mit getrenntem Bereich) Robin 10.10.
Ablagelogik Ordnerbaum über Speicherpfade je Lebensbereich, oberste Ebene = Eigentümer, _Unsortiert als Auffang Robin 19.09.
Steuer Tag, kein Ordner. Ein Tag je Steuerjahr (Steuer 2024, Steuer 2025) mit gespeicherter Ansicht; ein Datumsfilter trifft das Steuerjahr nicht, weil die Belege erst im Folgejahr kommen Robin 19.09., 29.09.
Taxonomie Startschema (Abschnitt 6), wächst im Betrieb; Korrespondenten darf die KI anlegen, Typen/Tags/Pfade nicht Robin 19.09.
ASN Barcode-Etiketten mit Präfix ASN auf jedem Papier, das behalten wird. ASN-Barcodes trennen zugleich den Stapel Robin 19.09.
Zulauf Papier HP Color LaserJet Pro MFP 4302 (fdn/fdw, Duplex-Einzug) → neue SMB-Freigabe → Consume. /mnt/scanner bleibt unangetastet (überwiegend Lars' Dokumente) Robin 18./19.09.
Scan-Profil Farbe, 300 dpi, beidseitig, PDF Robin 19.09.
Zulauf Mail Proton Mail Bridge headless im Stack, Proton-Label „Paperless" (kein Ordnerzwang), Robin hat Proton Unlimited Robin 19.09.
KI-Kette Native v3-KI über openai-like → llama-swap 10.10.10.7:9292. Vorschläge brain-llamacpp, Embeddings embed-llamacpp. Vision-OCR nur bei gerissener Schwelle (Abschnitt 7) Robin 19.09.
Speicherort Bleibt: Dokumente auf /mnt/paperless (UNAS, NFS), DB/Index/Consume auf der SSD Robin 19.09.
Tika/Gotenberg Bleiben (Mail-als-PDF, Office-Anhänge) Robin 19.09.
Secrets Infisical-Agent, Pfad /paperless-ngx; getrackte paperless.env wird gelöscht; neuer PAPERLESS_SECRET_KEY (#194) Roadmap
Exporter Nächtlicher document_exporter in E7; die Restore-Probe daraus in E7b Robin 19.09.

3. Architektur

Ein Compose-Stack paperless-ngx/ mit sechs Containern auf den bestehenden Netzen proxy_network (extern) und paperlessngx_internal (bridge).

Container Image Rolle
paperless_ngx ghcr.io/paperless-ngx/paperless-ngx:3.1.x App, Consumer, KI-Client
paperless_db docker.io/library/postgres:18.x Datenbank (SSD)
paperless_redis docker.io/library/redis:8.x Broker/Cache (SSD)
gotenberg docker.io/gotenberg/gotenberg:8.x Mail/Office → PDF
tika docker.io/apache/tika:3.x Textextraktion Office/Mail
protonmail-bridge Auswahl im Plan (Abschnitt 8) IMAP-Brücke zu Proton, nur im internen Netz

Genaue Patch-Versionen werden im Plan nachgeschlagen, nicht aus dem Kopf gesetzt.

Mounts:

Host Container Zweck
/mnt/ssd/container-data/paperless/data /usr/src/paperless/data Index, Klassifikator, LLM-Index
/mnt/ssd/container-data/paperless/db Postgres-Datadir Datenbank
/mnt/ssd/container-data/paperless/redis /data Redis
/mnt/ssd/container-data/paperless/consume /usr/src/paperless/consume Zulauf, Unterordner je Eigentümer
/mnt/ssd/container-data/paperless/protonmail-bridge Bridge-Datenverzeichnis Bridge-Vault und Mail-Cache
/mnt/paperless/media /usr/src/paperless/media Originale, Archiv-PDFs, Thumbnails (UNAS)
/mnt/paperless/export /usr/src/paperless/export Exporter-Ziel (UNAS)
/mnt/paperless/trash /usr/src/paperless/trash Papierkorb (UNAS)

Speicherort-Begründung: UNAS ist RAID6, beide Pfade stehen in beiden borgmatic-Ketten, der Mount-Guard aus #240 prüft /mnt/paperless, und Immich folgt demselben Muster. Die Alternative „alles auf die SSD" wäre operativ einfacher, hinge aber bis zum nächsten Nachtlauf an einer einzelnen Platte. Datenbanken bleiben grundsätzlich auf der SSD.

Erreichbarkeit: https://paperless.home.robinwerner.net über Traefik, Router nur intern. PAPERLESS_TRUSTED_PROXIES auf das Präfix 10.100. des Traefik-Netzes, sonst 403 beim Login (v3-Änderung an der Ratenbegrenzung). Präfix statt CIDR, weil python-ipware die Liste per startswith vergleicht und 10.100.0.0/16 nie träfe.

4. Zulauf Papier

4.1 Samba-Freigabe auf dem NUC (nativ, kein Container)

Neue Freigabe neben der bestehenden [scanner]:

[paperless]
   path = /mnt/ssd/container-data/paperless/consume/robin
   valid users = scanner
   force user = paperless
   force group = paperless
   create mask = 0660
   directory mask = 0770
   browseable = no
   guest ok = no
   writable = yes

Die include-Zeile dafür muss die letzte Zeile von [global] sein: Samba fügt die Datei wörtlich ein, und der [paperless]-Header wechselt den Abschnitt für alles, was danach kommt (testparm warnt dann nur mit „Global parameter … found in service section", die globalen Werte wären wirkungslos).

Dafür bekommt der NUC einen Systembenutzer paperless mit UID 977 / GID 988 — das ist die Identität, mit der der Container läuft (USERMAP_UID/GID) und die auf dem UNAS-Export gilt. Beide IDs sind auf dem NUC noch unbelegt (getent liefert nichts). Damit gehören gescannte Dateien von Anfang an dem Consumer, der sie nach dem Import löscht.

Zwei globale Korrekturen im selben Eingriff:

  • server min protocol = SMB2 (heute NT1, also SMB1; der 4302 braucht es nicht, HP schreibt SMB2/3 vor). Vorher prüfen, ob noch ein SMB1-Client existiert (smbstatus, Logs).
  • map to guest = Never (heute Bad Password: ein falsches Passwort wird still zum Gastzugang).

[scanner] → /mnt/scanner bleibt unverändert. Alle Host-Schritte sind Robins Hand, per Runbook, mit testparm -s vor dem Reload.

4.2 Quick Sets am HP 4302

Quick Set Netzwerkpfad Einstellungen
Paperless \\10.10.10.3\paperless PDF, Farbe, 300 dpi, beidseitig, Dateiname-Präfix scan
Ablage \\10.10.10.3\scanner bestehend, unverändert

Einrichtung im Embedded Web Server unter Settings → Control Panel Customization → Quick Sets, Benutzer scanner. IP statt NetBIOS-Name, weil die Namensauflösung am Gerät die häufigste Fehlerquelle ist. Der Dateiname vom Gerät ist egal, die KI setzt den Titel neu.

4.3 Stapel trennen über ASN

Trennung per Barcode und ASN-Barcode sind beide aktiv. Paperless beginnt dann an jeder Seite mit ASN-Etikett ein neues Dokument und behält diese Seite (anders als bei PATCHT-Trennblättern, die verworfen werden). Ein Stapel mit je einem Etikett auf Seite 1 wird also in einem Scanvorgang zu n Dokumenten mit je ihrer ASN. Eine schon vergebene ASN lässt Paperless das Dokument ablehnen und protokollieren — doppelte Nummern fallen sofort auf.

PAPERLESS_CONSUMER_ENABLE_BARCODES: "true"          # Trennung
PAPERLESS_CONSUMER_ENABLE_ASN_BARCODE: "true"       # ASN setzen + an ASN trennen
PAPERLESS_CONSUMER_ASN_BARCODE_PREFIX: "ASN"
PAPERLESS_CONSUMER_BARCODE_MAX_PAGES: "0"           # alle Seiten prüfen (Einzug: 50 Blatt)
# nur falls kleine Etiketten nicht erkannt werden (Probelauf):
# PAPERLESS_CONSUMER_BARCODE_DPI: "600"
# PAPERLESS_CONSUMER_BARCODE_UPSCALE: "1.5"

4.4 Consumer

PAPERLESS_CONSUMER_RECURSIVE: "true"                # consume/<eigentümer>/
PAPERLESS_CONSUMER_SUBDIRS_AS_TAGS: "false"         # Änderung: Unterordner = Eigentümer, kein Tag
PAPERLESS_CONSUMER_DELETE_DUPLICATES: "true"        # v3 ließe Duplikate sonst durch
PAPERLESS_CONSUMER_ENABLE_COLLATE_DOUBLE_SIDED: "false"  # Duplex kommt vom Einzug

Eigentümer und Inbox-Tag setzt Workflow W1 (Abschnitt 6.5) über den Pfadfilter */robin/*. Lars' Zweig ist später ein zweiter Unterordner, eine zweite Freigabe und ein zweiter Workflow — kein Umbau.

Probelauf: ausschließlich frisch gescannte eigene Dokumente. /mnt/scanner wird weder verschoben noch kopiert.

5. Ordnerlogik und Dateinamen

Die Ordnerstruktur in media/documents/archive/ (und später im Dropbox-Spiegel) entsteht aus Speicherpfaden; die globale PAPERLESS_FILENAME_FORMAT ist nur der Auffang, wenn kein Speicherpfad gesetzt ist.

robin/
  Arbeit/<Korrespondent>/<Jahr>/<Datum> <Titel> ASN<Nummer>.pdf
  Finanzen/…
  Versicherungen/…
  Wohnung/…
  Gesundheit/…
  Behörden/…
  Studium & Zeugnisse/…
  Fahrzeug/…
  Verträge & Abos/…
  _Unsortiert/<Korrespondent>/<Jahr>/…

Gemeinsamer Dateinamenteil aller Vorlagen (Jinja):

{{ owner_username }}/<Lebensbereich>/{{ correspondent }}/{{ created_year }}/{{ created }} {{ title }}{% if asn %} ASN{{ '%05d' | format(asn | int) }}{% endif %}

Auffang: PAPERLESS_FILENAME_FORMAT mit _Unsortiert als Lebensbereich. Der Unterstrich sortiert den Ordner nach oben, Robin sieht sofort, was noch einzuordnen ist. Regeln:

  • Paperless leitet Ordner aus Metadaten ab, nie umgekehrt. Zuordnung passiert in Paperless (Speicherpfad setzen), die Datei wandert automatisch. Dateien im Spiegel von Hand zu verschieben ist tabu.
  • Die ASN im Dateinamen ist der Rückweg zum Papier.
  • Die Nullauffüllung (%05d) spiegelt die Etiketten (ASN00042). Paperless übergibt asn als Text, deshalb steht asn | int vor dem format-Filter; ohne die Umwandlung wirft die Vorlage einen TypeError, Paperless fängt ihn still und legt die Datei als 0000042.pdf im Wurzelverzeichnis ab (Befund des Abschlussreviews 20.09., in der Sandbox verifiziert). Der Fall ohne ASN bleibt durch {% if asn %} übersprungen.

6. Ablage-Startschema

Das Schema liegt als YAML unter paperless-ngx/seed/ im Repo. Ein uv run-Skript legt es über die REST-API idempotent an (vorhandene Objekte werden nicht dupliziert). Damit ist die Struktur versioniert, reviewbar und beim Neuaufbau reproduzierbar. Der API-Token kommt aus der Umgebung (PAPERLESS_TOKEN), nie aus dem Repo.

6.1 Dokumenttypen (feste Liste, KI legt keine an)

Rechnung · Vertrag · Bescheid · Schreiben · Zeugnis/Urkunde · Gehaltsabrechnung · Kontoauszug · Versicherungspolice · Kündigung · Beleg/Quittung · Antrag/Formular · Steuerbescheid · Bestellung/Auftragsbestätigung · Sonstiges

6.2 Speicherpfade (neun plus Auffang, KI legt keine an)

Arbeit · Finanzen · Versicherungen · Wohnung · Gesundheit · Behörden · Studium & Zeugnisse · Fahrzeug · Verträge & Abos

6.3 Tags (KI vergibt nur bestehende)

  • Status: Inbox (automatisch beim Import, Robin entfernt ihn beim Durchsehen) · Handeln · Steuer · Dauerhaft (Urkunden, nie wegwerfen) · E-Mail (Herkunft Mail)
  • Thema: nur, wo der Speicherpfad nicht reicht — Start mit Gemeinsam (vormals Familie), Garantie; wächst im Betrieb

6.4 Korrespondenten

Startliste aus Robins bekannten Absendern — die Liste liefert Robin beim Seed, sie ist bewusst nicht Teil dieser Spec. Die KI darf neue anlegen, weil ein Absendername eine Tatsache ist. Auto-Matching lernt mit.

6.5 Workflows

Nr. Trigger Aktionen
W1 Verarbeitung gestartet, Pfadfilter */robin/* Eigentümer robin, Tag Inbox
W2 Dokument hinzugefügt KI-Vorschläge anwenden: Titel und Erstelldatum überschreiben; Korrespondent mit Anlegen fehlender; Dokumenttyp, Speicherpfad und Tags nur bestehende

Mails brauchen keinen eigenen Workflow: Eigentümer und Tags setzt die Mail-Regel selbst (Abschnitt 8), W2 greift danach wie bei Scans. Beim Entfernen von Inbox passiert nichts Automatisches — der Gang durch die Inbox ist Robins Kontrolle über die KI.

6.6 Gespeicherte Ansichten

Inbox · Handeln · Steuer laufendes Jahr · Unsortiert (kein Speicherpfad)

7. KI-Kette

7.1 Konfiguration

PAPERLESS_AI_ENABLED: "true"
PAPERLESS_AI_LLM_BACKEND: "openai-like"
PAPERLESS_AI_LLM_ENDPOINT: "http://10.10.10.7:9292/v1"
PAPERLESS_AI_LLM_MODEL: "brain-llamacpp"            # qwen3.6 35B-A3B, 32k Kontext
PAPERLESS_AI_LLM_API_KEY: "none"                    # llama-swap hat keine Auth; Client verlangt trotzdem einen Wert
PAPERLESS_AI_LLM_CONTEXT_SIZE: "16384"
PAPERLESS_AI_LLM_REQUEST_TIMEOUT: "300"             # Kaltstart von brain (Swap-Gruppe) + Generierung
PAPERLESS_AI_LLM_EMBEDDING_BACKEND: "openai-like"
PAPERLESS_AI_LLM_EMBEDDING_MODEL: "embed-llamacpp"  # embeddinggemma 300m, resident, Kontext 2048
PAPERLESS_AI_LLM_EMBEDDING_CHUNK_SIZE: "1024"
PAPERLESS_LLM_INDEX_TASK_CRON: "30 3 * * *"         # nach dem Borg-Lauf 02:04
PAPERLESS_AI_LLM_OUTPUT_LANGUAGE: "de-de"          # Übersetzung = zweite LLM-Anfrage je Klassifikation

Geändert 22.09.: Ursprünglich sollte die Sprache der UI-Einstellung folgen; die ersten Scans bekamen englische Titel, die Benutzereinstellung wurde nicht übernommen. Fest gesetzt ist das Verhalten unabhängig vom Benutzer.

PAPERLESS_AI_LLM_ALLOW_INTERNAL_ENDPOINTS bleibt auf dem Default true (private Adresse erlaubt).

7.2 Was die KI leistet

Vorschläge für Titel, Tags, Korrespondent, Typ, Speicherpfad und Datum aus dem OCR-Text; automatisches Anwenden über W2; ähnliche Dokumente und Dokumenten-Chat über den Embedding-Index. Sie liest keine Bilder (OCR bleibt Tesseract) und setzt keine ASN (kommt vom Barcode). Der klassische lernende Klassifikator läuft parallel weiter.

Wie die KI die Taxonomie wählt (Befund 22.09., v3.1.3-Quelltext): Das Modell bekommt nicht die Liste der Speicherpfade, Typen und Tags, sondern nur „Kandidaten aus ähnlichen Dokumenten", die Paperless aus dem Embedding- Index holt. Ohne Kandidaten schlägt es freie Namen vor (englisch, dann übersetzt), die exakt oder unscharf gegen den Bestand abgeglichen werden; Speicherpfade werden nie angelegt. Am ersten Tag ist der Index leer, alles landet in _Unsortiert. Bootstrap: die ersten Dokumente von Hand einordnen; Kandidaten liest Paperless live aus der Datenbank der ähnlichen Dokumente, die müssen nur im Index sein (nachts 03:30, sofort per document_llmindex update). Danach bekommen ähnliche neue Dokumente Pfad, Typ und Tags als Kandidaten mit ID. Die Regel „nur Bestehendes" gilt weiter, die Annahme „die KI wählt aus der festen Liste" war falsch. Kosten: je Aktion zwei Anfragen (Klassifikation + Übersetzung), bei drei Aktionen in W2 sechs je Dokument; Messung in der Abnahme.

7.3 Modellentscheid per Mini-Turnier

Kandidat 1 ist brain-llamacpp (Qualität, 32k Kontext, swappt in der heavy-Gruppe). Kandidat 2 ist json-llamacpp (granite 8B in der docs-Gruppe, kein Swap, 2 s je Dokument, Turnier-erprobt für JSON). Auf den ersten Dokumenten werden beide über die Vorschlagsfunktion verglichen (Trefferquote je Feld, Latenz). Der Sieger bleibt in der Compose-Datei, das Ergebnis geht ins Umsetzungs-Log.

Ergebnis 27.09. (fünf von Hand eingeordnete Dokumente, ai_duel.py): brain 3/5 Korrespondent, 4/5 Typ, 5/5 Pfad bei 60 s je Anfrage; coder 2/5, 4/5, 4/5 bei 14 s; translator 4/5, 4/5, 5/5 bei 56 s. Die 60 s waren Denktext (13 KB Antwort gegen 1,5 KB bei coder). Neue Rolle paperless-llamacpp = brain-Gewichte mit --reasoning off (aiserver#29, Gruppe docs): 3/5, 4/5, 4/5 bei 9 s. Damit ist sie das Paperless-Modell (homeserver#252); sechs Anfragen je Dokument dauern rund 40 s statt drei Minuten, die drei Aktionen bleiben. json schied als Kandidat aus (seit Testrunde 3 ein Bild-zu-Felder-Modell ohne Chat).

Bekannte Einschränkung: Paperless setzt embeddinggemmas Task-Präfixe nicht; die Trefferquote liegt etwas unter den 93 % R@5 aus dem Turnier.

7.4 Vision-OCR-Schwelle (Entscheid „Vision-OCR ja/nein")

Nach dem Probelauf (20 Dokumente) wird die OCR-Qualität geprüft: Beträge, Namen, IBANs, Aktenzeichen über die Volltextsuche auffindbar? Sind mindestens 2 von 20 unbrauchbar, wird in E7b paperless-gpt nur für OCR (paperless-gpt-ocr-auto, Vision-Modell ocr-llamacpp = german-ocr 3.1) ergänzt; Metadaten bleiben bei der nativen KI, sonst kämpfen zwei Systeme um dieselben Felder. Unter der Schwelle bleibt es bei Tesseract.

8. Zulauf Mail (Proton)

Fakten: Proton bietet kein IMAP; die Brücke ist Proton Mail Bridge (bezahlter Plan nötig, Robin hat Unlimited). Bridge zeigt jedes Proton-Label als IMAP-Ordner Labels/<Name>; die Mail bleibt dabei im Posteingang. Paperless' Aktion „Custom Tag setzen" nutzt Gmails Label-Erweiterung, die Bridge nicht implementiert — also keine Rückmeldung per Label. Paperless merkt sich verarbeitete Mails per UID (Mail → Processed Mails), doppelte Verarbeitung ist damit unabhängig von der Aktion ausgeschlossen.

Design:

  • Container protonmail-bridge nur im Netz paperlessngx_internal, keine veröffentlichten Ports, Daten unter /mnt/ssd/container-data/paperless/protonmail-bridge (in den Borg-Quellen). Läuft --noninteractive.
  • Image-Auswahl im Plan nach Kriterien: aus offiziellem Bridge-Quelltext gebaut, Release jünger als 6 Monate, amd64, Healthcheck, kein privileged. Kandidaten: ghcr.io/leakix/proton-bridge-docker, asyncura/proton-mail-bridge, Enucatl/docker-protonmail-bridge (mit cap_drop).
  • Einmalige Einrichtung durch Robin: Run-Container stoppen (Bridge hält einen Single-Instance-Lock unter /data, das beide Container teilen), dann bridge --cli über das Init-Profil, Login mit 2FA, info liefert das Bridge-Passwort (nicht das Proton-Passwort), danach den Run-Container wieder starten. Das Bridge-Passwort landet nur im Paperless-Mailkonto (Paperless verschlüsselt es mit dem SECRET_KEY), zusätzlich in Robins Passwortmanager.
  • In Proton: Label „Paperless". Robin setzt es von Hand oder per Proton-Filter (Sieve) nach Absender, z. B. alle Mails der Versicherer. Kein Ordnerzwang, die Mail bleibt im Posteingang.
  • Paperless-Mailkonto: Host protonmail-bridge, Port laut Image (143 oder 1143), Sicherheit keine — der Verkehr bleibt im Docker-internen Netz auf dem NUC, Bridge selbst spricht TLS zu Proton, und ein selbstsigniertes Bridge-Zertifikat würde am Client scheitern.
  • Mail-Regel: Ordner Labels/Paperless, Anhangsfilter *.pdf, Umfang nur Anhänge (Start; „Mail als PDF" ist eine Betriebsentscheidung, Gotenberg ist dafür da), Titel aus Betreff, Korrespondent aus Absender, Eigentümer robin, Tags Inbox + E-Mail. Aktion nach Verarbeitung: Verschieben nach Labels/Paperless-erledigt. Korrigiert 23.09.: Eine Aktion „keine" existiert nicht (Auswahl: Löschen, Verschieben, Als gelesen markieren, Markieren, Tag), und die Aktion legt zugleich die Suchkriterien fest: „Als gelesen" holt nur ungelesene Mails (gelabelte Post ist meist gelesen), „Markieren" nur Mails ohne Stern (Robin nutzt Sterne), „Tag" braucht IMAP-Keywords, die Bridge nicht anbietet. „Verschieben" holt alle; und weil Bridge Labels als Ordner unter Labels/ zeigt, tauscht das Verschieben zwischen zwei Labels nur das Label, die Mail bleibt im Posteingang. Maximales Alter 0 = unbegrenzt (Standard 30 Tage).
  • Abfrage alle 10 Minuten (Default PAPERLESS_EMAIL_TASK_CRON).

Zu prüfen im Plan: Bridge synchronisiert den gesamten Postkasten lokal (verschlüsselter Cache). Die Größe von Robins Postfach bestimmt den Platzbedarf auf der SSD.

9. Secrets und Konfiguration

Infisical (EU, Projekt wie bei ollama/speedtest-tracker), Pfad /paperless-ngx, Env dev:

Key Zweck
PAPERLESS_SECRET_KEY neu generiert (secrets.token_urlsafe(64)); der alte liegt im Git-Verlauf
POSTGRES_PASSWORD Postgres-Cluster (greift nur bei leerem Datadir → Wipe ist Pflicht)
PAPERLESS_DBPASS derselbe Wert für die App

Agent-Template-Block in infrastructure/infisical-agent/agent.yaml.example und auf dem NUC in /etc/infisical/agent.yaml; Compose bindet env_file: /run/infisical/paperless-ngx.env an paperless_ngx und paperless_db. Der Superuser wird interaktiv angelegt (createsuperuser), das Passwort gehört in Robins Passwortmanager, nicht nach Infisical. Der API-Token für das Seed-Skript wird nach dem ersten Login in der UI erzeugt und Robin gibt ihn dem Skript per Umgebungsvariable.

Nicht-geheime Konfiguration wandert inline in docker-compose.yaml; paperless-ngx/paperless.env wird per git rm entfernt (getrackter, geleakter SECRET_KEY, #194).

v3-Anpassungen gegenüber der 2.20-Konfiguration:

Alt Neu Grund
— PAPERLESS_DBENGINE=postgresql in v3 Pflicht
PAPERLESS_OCR_MODE=skip PAPERLESS_OCR_MODE=auto skip entfernt
PAPERLESS_OCR_SKIP_ARCHIVE_FILE=never PAPERLESS_ARCHIVE_FILE_GENERATION=always jedes Dokument bekommt ein Archiv-PDF — nur so ist documents/archive/ (Spiegelquelle in E7b) vollständig, auch bei born-digitalen PDFs
PAPERLESS_DB_TIMEOUT=30 PAPERLESS_DB_OPTIONS=connect_timeout=30 konsolidiert
PAPERLESS_CONSUMER_IGNORE_PATTERNS=[…] entfällt v3-Defaults (Regex) decken .*, ._*, .DS_Store, Thumbs.db ab
— PAPERLESS_TRUSTED_PROXIES=10.100. Login-403 hinter Traefik (Präfix-Match, kein CIDR)
SUBDIRS_AS_TAGS=true false Unterordner = Eigentümer
COLLATE_DOUBLE_SIDED=true false Duplex-Einzug
PAPERLESS_TASK_WORKERS=4 2 der NUC teilt die CPU mit rund 60 Containern

Unverändert: USERMAP_UID/GID 977/988, OCR_LANGUAGE=deu+eng, Deskew/Rotate/Clean, OCR_OUTPUT_TYPE=pdfa, THREADS_PER_WORKER=2, TRASH_DIR, EMPTY_TRASH_DELAY=30, AUDIT_LOG_ENABLED, Session-Cookies, Postgres-Tuning.

10. Backup und Betrieb

  • Borg-Quellen: /mnt/ssd/container-data (DB, Index, Consume, Bridge) und /mnt/paperless (Media, Export, Trash) stehen bereits in beiden borgmatic-Ketten. Der DB-Dump greift automatisch (predeploy-backup.sh erkennt Postgres-Container am Image, #241). Keine Backup-Änderung nötig — nur der Nachweis nach dem ersten Nachtlauf.
  • Exporter-Timer paperless-export.timer, täglich 01:15 (vor Borg 02:04), versioniert unter paperless-ngx/host-config/: docker exec -T paperless_ngx document_exporter ../export --delete --use-folder-prefix --split-manifest --no-progress-bar. Der Export ist Paperless' eigenes Sicherungsformat: alle Originale und Archiv-PDFs mit lesbaren Namen plus manifest.json mit dem kompletten Datenbankinhalt (Tags, Korrespondenten, Typen, Pfade, ASN, Notizen, Workflows, Mail-Regeln, Rechte). document_importer liest ihn in eine leere Instanz zurück, unabhängig von Postgres-Version und Pfaden. Inkrementell; Kosten einmal die Dokumentgröße auf der UNAS.
  • Überwachung:
  • Uptime-Kuma-Monitor (VPS) auf https://paperless.home.robinwerner.net/ — Kuma erreicht interne Domains.
  • Consume-Stau-Prüfung: Timer alle 15 min schreibt paperless_consume_stale_files (Dateien älter als 30 min unter consume/) in den node-exporter-Textfile-Collector; Prometheus-Regel feuert bei > 0 für 30 min. Die Unit läuft ohne Capabilities, aber mit SupplementaryGroups=paperless, weil der Consume-Baum 0770 ist. Genau dieses stille Versagen blieb mit Syncthing neun Monate unbemerkt. Nach dem Deploy prüfen, dass die Metrik Serien hat (E3-Prinzip).
  • Deploy-Reihenfolge (NUC, Robins Hand): Infisical-Secrets + Agent-Template → Samba (Benutzer, Freigabe, SMB2) → alten Stack down → Datenverzeichnisse leeren (Sicherheitsblick vorher: genau ein Dokument) → git pull als root → docker compose config → up -d → Superuser → Seed → Quick Sets → Bridge-Login → Mailkonto/-regel → Timer.

11. Abnahme E7

  1. 20 echte Dokumente über den Scanner (Quick Set Paperless), Stapel mit ASN-Etiketten in einem Durchgang: Trennung stimmt, jede ASN gesetzt, mindestens 15 ohne Handkorrektur richtig in Korrespondent, Typ und Speicherpfad. Gilt für den zweiten Stapel: Der erste wird von Hand eingeordnet und der Index neu gebaut, erst dann hat die KI Kandidaten (Abschnitt 7.2). Und auf der Platte: media/documents/archive/robin/… zeigt den Ordnerbaum aus Abschnitt 5, keine Datei liegt als 0000042.pdf im Wurzelverzeichnis (der stille Rückfall, wenn eine Vorlage nicht rendert).
  2. Eine Mail mit PDF-Anhang mit Label „Paperless" landet ohne Zutun in der Inbox, Eigentümer Robin, Tag E-Mail.
  3. Export liegt im Nachtarchiv: borgmatic list --archive latest --path mnt/paperless/export zeigt Manifest und Dokumente (nicht --find, das liefert falsche Nullen).
  4. Volltextsuche findet einen Betrag und eine Aktennummer aus einem Scan (OCR-Nachweis, Grundlage für 7.4).
  5. Dokumenten-Chat beantwortet eine Frage zu einem Dokument mit Quellenlink (Index-Lauf erfolgt).
  6. Consume-Stau-Metrik hat Serien, Kuma-Monitor grün.

12. Bewusst in E7b

  • Dropbox-Spiegel (#242): rclone sync von archive/robin/ nach Dokumente/Paperless, --max-delete und --backup-dir als Löschschutz, eigener Timer. Klartext (Robins Entscheid), Ordner ist ein Spiegel, kein Ablageort.
  • Restore-Probe: Export aus dem Borg-Archiv holen, auf dem Laptop in eine Wegwerf-Instanz importieren, ein Dokument mit Tags und ASN öffnen. Bis dahin gilt ehrlich: Dateien belegt, Rückweg ungeprüft.
  • Lars' Konto und Eigentümertrennung (zweiter Consume-Zweig, Freigabe, Workflow), SSO über Authelia (E8; läuft auf dem VPS, OIDC-Clients fehlen noch).
  • Vision-OCR über paperless-gpt, nur wenn die Schwelle aus 7.4 reißt.
  • „Mail als PDF" in der Mail-Regel, Custom Fields (Betrag, Fälligkeit), weitere Themen-Tags — nach Betriebserfahrung.

13. Prüfpunkte für den Umsetzungsplan

  • Aktuelle Patch-Tags für paperless-ngx 3.1, Postgres 18, Redis 8, Gotenberg 8, Tika 3 nachschlagen.
  • Bridge-Image nach den Kriterien aus Abschnitt 8 wählen; Datenpfad und IMAP-Port des gewählten Images.
  • ~~format-Filter im Dateinamen-Jinja~~ — geklärt 20.09.: Filter vorhanden, asn kommt als Text, asn | int löst es (Abschnitt 5).
  • Barcode-Erkennung an Robins realen Etiketten (DPI/Upscale).
  • SMB1-Clients vor server min protocol = SMB2.
  • ~~Format von PAPERLESS_AI_LLM_OUTPUT_LANGUAGE~~ — geklärt 22.09.: Sprachcode aus Djangos LANGUAGES, für Deutsch de-de; gesetzt, weil die UI-Sprache nicht reichte.
  • Größe von Robins Proton-Postfach (Bridge-Cache auf der SSD).
  • Nach dem ersten Nachtlauf: DB-Dump und Export im Archiv belegen.

14. Quellen

  • Issue #190 (Agenda-Kommentar 18.09.), #87, #242, #240/#241
  • paperless-ngx 3.1.3: docs/configuration.md (AI, Barcodes, Consumer), docs/migration-v3.md, docs/usage.md (Workflows → Apply AI Suggestions, Incoming Email, Processed Mail), docs/advanced_usage.md (AI features, Document Splitting / ASN), docs/administration.md (Exporter)
  • HP Support: Set up Scan to Network Folder (Quick Sets, EWS), User Guide 4301/4302
  • Proton: Labels in Bridge; paperless-ngx Discussion #12133 (Custom Tag vs. Bridge)
  • paperless-gpt README (OCR-Provider, PDF_UPLOAD, Tag-Steuerung)
  • aiserver: llama-swap/config.yaml, docs/testrunde-2/ (Turniere OCR/JSON/Embeddings)
  • NUC (lesend, 18./19.09.): smb.conf, /mnt/paperless-Mount, Container-Datenverzeichnis, Infisical-Agent