Zum Inhalt

Runbook: Paperless v3 in Betrieb nehmen (E7)

Schritt-für-Schritt-Anleitung für Robin. Sie setzt die Teile C bis F des Umsetzungsplans in die Reihenfolge um, in der du sie ausführst. Jeder Schritt hat einen Befehl, eine erwartete Ausgabe und ein Stopp-Kriterium. Nach jedem Block (◆) meldest du Claude die Ausgabe, bevor es weitergeht. Claude führt hier nichts selbst aus.

Voraussetzungen: homeserver#243, #244 und homelab-documentation#60 sind gemergt. Du hast Zugang zur Infisical-UI (EU), zum Embedded Web Server des HP 4302 (PIN), zu Proton Mail und zu Uptime Kuma auf dem VPS.

Rollen auf dem NUC: ssh nuc = Benutzer robin (Docker, Lesen). ssh nuc-root = root (alles unter /etc, /opt, Systembenutzer, git pull). Der Deploy-Pfad ist /opt/rpi-docker-compose/.

Was diese Inbetriebnahme löscht: den Inhalt der alten Paperless-Verzeichnisse (genau ein Testdokument) in Phase 4. /mnt/scanner wird nie angefasst.


Phase 0 · Vorbereitung am Laptop (10 min)

0.1 Secrets erzeugen. Zwei Werte, die du gleich in Infisical einträgst. Nicht in den Chat kopieren.

uv run python3 -c "import secrets; print('SECRET_KEY', secrets.token_urlsafe(64))"
openssl rand -hex 32

0.2 Korrespondenten eintragen. In ~/repos/homeserver/paperless-ngx/seed/schema.yaml unter correspondents: deine regelmäßigen Absender, Name wie im Briefkopf:

correspondents:
  - name: DRK-Blutspendedienst
  - name: HUK-Coburg

Branch, PR, Merge (Claude kann den PR für dich anlegen, wenn du die Liste schickst). Das kann auch später vor Phase 6 passieren.


Phase 1 · Infisical (15 min)

1.1 UI: Projekt wie ollama/speedtest-tracker, Env dev, neuer Pfad /paperless-ngx, drei Secrets:

Key Wert
PAPERLESS_SECRET_KEY SECRET_KEY aus 0.1
POSTGRES_PASSWORD Hex-Wert aus 0.1
PAPERLESS_DBPASS derselbe Hex-Wert

Dann Access Control → Identities → nuc-agent: Lesezugriff auf /paperless-ngx ergänzen.

1.2 Agent-Template [nuc-root]: Den Block aus dem Repo in die lokale Agent-Config übernehmen.

grep -n 'paperless-ngx' /etc/infisical/agent.yaml || echo "FEHLT"

Falls FEHLT: den paperless-ngx-Block aus /opt/rpi-docker-compose/infrastructure/infisical-agent/agent.yaml.example (nach dem Merge von #243 dort enthalten) an die templates:-Liste in /etc/infisical/agent.yaml anhängen, gleiche Einrückung wie die Nachbarblöcke. Dann:

systemctl reload infisical-agent.service
sleep 5
cut -d= -f1 /run/infisical/paperless-ngx.env

Erwartet: drei Zeilen PAPERLESS_SECRET_KEY, POSTGRES_PASSWORD, PAPERLESS_DBPASS. Stopp, wenn die Datei fehlt oder leer ist: journalctl -u infisical-agent.service -n 30.

◆ Melde die drei Key-Namen (nur Namen, cut -d= -f1 zeigt keine Werte).


Phase 2 · Systembenutzer und Verzeichnisse [nuc-root] (5 min)

2.1 IDs frei?

getent passwd 977; getent group 988; echo "leer = frei"

Erwartet: nur leer = frei. Stopp, wenn eine ID belegt ist.

2.2 Benutzer anlegen.

groupadd --gid 988 paperless
useradd --system --uid 977 --gid 988 --no-create-home --home-dir /nonexistent --shell /usr/sbin/nologin paperless
id paperless

Erwartet: uid=977(paperless) gid=988(paperless) groups=988(paperless)

2.3 Verzeichnisse.

install -d -o paperless -g paperless -m 0770 /mnt/ssd/container-data/paperless/consume/robin
install -d -o 1000 -g 1000 -m 0700 /mnt/ssd/container-data/paperless/protonmail-bridge
ls -ln /mnt/ssd/container-data/paperless/

Erwartet: consume mit 977 988, protonmail-bridge mit 1000 1000, daneben die alten data, db, redis.

◆ Melde die ls -ln-Ausgabe.


Phase 3 · Samba [nuc-root] (15 min)

3.1 Spricht noch jemand SMB1?

smbstatus -b
journalctl -u smbd --since '-30d' | grep -i 'NT1\|SMB1' | tail -5

Erwartet: keine NT1-Sitzungen. Der 4302 und macOS können SMB2/3.

3.2 smb.conf anpassen. In /etc/samba/smb.conf im Abschnitt [global]:

  • server min protocol = NT1 → server min protocol = SMB2
  • map to guest = Bad Password → map to guest = Never
  • als letzte Zeile von [global] (direkt vor [homes]) ergänzen: include = /opt/rpi-docker-compose/paperless-ngx/host-config/smb-paperless.conf

Warum letzte Zeile: Samba fügt die Datei wörtlich ein, ihr [paperless]-Header wechselt den Abschnitt. Alles, was in smb.conf nach dem Include stünde, landete still in der Freigabe.

3.3 Prüfen und laden.

testparm -s 2>&1 | grep -A12 '^\[paperless\]'
testparm -s 2>&1 | grep -E 'server min protocol|map to guest'
testparm -s 2>&1 | grep -i 'global parameter'
systemctl reload smbd

Erwartet: Freigabe paperless mit force user = paperless; server min protocol = SMB2; map to guest = Never; die dritte Zeile bleibt leer (sonst steht das Include zu früh, Stopp).

3.4 Schreibtest vom Laptop (Passwort = das des Samba-Benutzers scanner, wie im Quick Set „Ablage"):

echo test > /tmp/smbtest.txt
smbclient //10.10.10.3/paperless -U scanner -c 'put /tmp/smbtest.txt smbtest.txt; ls'

Dann [nuc-root]:

ls -ln /mnt/ssd/container-data/paperless/consume/robin/
rm /mnt/ssd/container-data/paperless/consume/robin/smbtest.txt

Erwartet: smbtest.txt gehört 977 988, Rechte -rw-rw----. Die Datei danach löschen, falls sie noch da ist.

Meist ist sie schon weg: Der alte Stack läuft noch und liest denselben Consume-Ordner rekursiv mit, die Datei wird sofort zum Dokument. Das ist der bessere Beweis, denn der Ordner ist 0770 paperless, und scanner hätte ohne force user nicht hineinschreiben können. Die alte Instanz hat dann zwei Dokumente statt einem (siehe 4.3).

◆ Melde die testparm-Zeilen und die ls -ln-Zeile.


Phase 4 · Alten Stack ersetzen [nuc-root] (20 min)

4.1 Repo holen, Rechte prüfen.

cd /opt/rpi-docker-compose && git pull --ff-only
find /opt/rpi-docker-compose -type f ! -perm -o=r
ls paperless-ngx/

Erwartet: nur traefik/.env und ts3/.env; in paperless-ngx/ liegen docker-compose.yaml, docs.md, host-config/, scripts/, seed/, keine paperless.env mehr.

4.2 Alten Stack stoppen.

docker compose -f /opt/rpi-docker-compose/paperless-ngx/docker-compose.yaml down
docker ps -a --filter name=paperless --filter name=gotenberg --filter name=tika

Erwartet: keine Container in der Liste.

4.3 Aufräumen: Sicherheitsblick, dann leeren. Das ist der destruktive Schritt. Erst schauen, was drin ist:

find /mnt/paperless/media -type f | wc -l
find /mnt/paperless/media -type f | head -5
du -sh /mnt/ssd/container-data/paperless/db /mnt/ssd/container-data/paperless/data

Erwartet: die Dateien des Testdokuments und der smbtest.txt aus 3.4 (je Original, Archiv, Thumbnail), kleine db/data. Stopp, wenn hier mehr liegt, als du erwartest. Erst dann:

for d in /mnt/ssd/container-data/paperless/db /mnt/ssd/container-data/paperless/redis \
         /mnt/ssd/container-data/paperless/data /mnt/paperless/media /mnt/paperless/export /mnt/paperless/trash; do
  find "$d" -mindepth 1 -delete
done
find /mnt/paperless /mnt/ssd/container-data/paperless -mindepth 2 | wc -l

Erwartet: letzte Zeile 1 (nur consume/robin). Die Verzeichnisse selbst bleiben stehen, consume/ und protonmail-bridge/ unberührt, /mnt/scanner nicht im Spiel.

4.4 Starten.

docker compose -f /opt/rpi-docker-compose/paperless-ngx/docker-compose.yaml config --quiet && echo OK
docker compose -f /opt/rpi-docker-compose/paperless-ngx/docker-compose.yaml up -d
sleep 90
docker ps --filter name=paperless --filter name=gotenberg --filter name=tika --filter name=protonmail --format '{{.Names}}\t{{.Status}}'

Erwartet: paperless_ngx, paperless_db, paperless_redis mit (healthy); gotenberg, tika Up; protonmail-bridge Up (healthy) (noch ohne Konto). Kein protonmail-bridge-init. Stopp, wenn paperless_ngx nicht healthy wird: docker logs paperless_ngx --tail 50.

4.5 Startlog auf v3-Warnungen.

docker logs paperless_ngx 2>&1 | grep -iE 'deprecat|removed|warning|error' | head -20

Erwartet: keine Zeile zu entfernten oder veralteten Einstellungen.

◆ Melde die docker ps-Tabelle und die Warnungszeilen (falls welche).


Phase 5 · Erster Login (10 min)

5.1 Superuser [nuc], Passwort in den Passwortmanager:

docker exec -it paperless_ngx python3 manage.py createsuperuser

Benutzername robin.

5.2 Login unter https://paperless.home.robinwerner.net, ohne 403. Einstellungen → Sprache Deutsch.

5.3 API-Token: Mein Profil → API-Auth-Token erzeugen, am Laptop in ~/.paperless-token ablegen (chmod 600). Der Token bleibt bei dir.

5.4 Anwendungskonfiguration: Einstellungen → Anwendungskonfiguration → Abschnitt KI: alle Felder leer lassen. Ein Wert dort schlägt die Compose-Datei.

◆ Melde: Login ohne 403 ja/nein.


Phase 6 · Startschema einspielen (Laptop, 10 min)

Voraussetzung: 0.2 ist gemergt und ~/repos/homeserver auf main aktuell.

cd ~/repos/homeserver && git pull --ff-only
PAPERLESS_URL=https://paperless.home.robinwerner.net PAPERLESS_TOKEN="$(cat ~/.paperless-token)" \
  uv run paperless-ngx/seed/seed.py --dry-run

Erwartet: je Abschnitt „0 vorhanden, N fehlen" mit den Namen aus dem Schema. Dann ohne --dry-run ausführen, und ein zweites Mal:

Erwartet: erster Lauf legt 7 + 14 + 9 + deine Korrespondenten an; zweiter Lauf meldet überall „0 fehlen" und 0 Objekte angelegt. Ein HTTP 400 bei storage_paths wäre neu (die Vorlagen nutzen asn | int): Server-Body aus der Skriptausgabe an Claude.

Sichtprüfung in der UI: Tags (Inbox mit Inbox-Häkchen), Dokumenttypen, Speicherpfade. Bei einem Speicherpfad die Vorschau öffnen: sie muss robin/Arbeit/…/… ASN12345 zeigen.

◆ Melde die Ausgabe des zweiten Laufs und den Vorschau-Pfad.


Phase 7 · Workflows und Ansichten (UI, 15 min)

7.1 Einstellungen → Workflows:

Nr. Name Auslöser Aktion
1 Scan von Robin Verarbeitung gestartet, Quelle Consume-Ordner, Pfadfilter */robin/* Zuweisung: Eigentümer robin, Tag Inbox
2 KI-Vorschläge anwenden Dokument hinzugefügt, kein Filter KI-Vorschläge anwenden: Titel ✔ Tags ✔ Korrespondent ✔ Dokumenttyp ✔ Speicherpfad ✔ Erstelldatum ✔ · Fehlende anlegen: nur Korrespondent · Vorhandene überschreiben: Titel und Erstelldatum

Reihenfolge 1 vor 2, beide aktiv.

Die Schalter „Fehlende anlegen" und „Vorhandene überschreiben" gelten je Aktion, nicht je Feld. Workflow 2 besteht deshalb aus drei Aktionen „KI-Vorschläge anwenden": (a) Korrespondent mit „Fehlende anlegen", (b) Titel und Erstelldatum mit „Vorhandene überschreiben", (c) Tags, Dokumenttyp, Speicherpfad ohne Schalter. Im Quelltext von 3.1.3 fragt jede Aktion die KI eigenständig, es gibt keinen Zwischenspeicher: drei Anfragen je Dokument, im Hintergrund, der Import wartet nicht darauf. Ob das trägt, misst Phase 12; bei zu hoher Last auf zwei Aktionen zusammenziehen (Korrespondent getrennt, Rest mit „Überschreiben", dann überschreibt die KI auch Typ und Pfad aus dem klassischen Matching).

7.2 Dokumente → Filter → Ansicht speichern:

Name Filter Anzeige
Inbox Tag Inbox Seitenleiste, Dashboard
Handeln Tag Handeln Seitenleiste, Dashboard
Steuer laufendes Jahr Tag Steuer und Erstellt ab 01.01.<Jahr> Seitenleiste
Unsortiert Speicherpfad ohne Seitenleiste

Phase 8 · Quick Set am HP 4302 (20 min)

8.1 EWS (IP des Druckers im Browser, PIN) → Settings → Control Panel Customization → Quick Sets → + → Typ Scan to Network Folder, Name Paperless.

8.2 Ziel: Netzwerkpfad \\10.10.10.3\paperless, Benutzer scanner + Passwort. Apply and Test muss grün sein. Stopp bei rot: Passwort, dann [nuc-root] journalctl -u smbd -n 20.

8.3 Datei-Einstellungen: Dateityp PDF, Farbe Farbe, Auflösung 300 dpi, Original beidseitig, Dateinamen-Präfix scan.

8.4 Erster Scan: ein einseitiges Dokument mit ASN-Etikett. Direkt danach [nuc]:

ls -ln /mnt/ssd/container-data/paperless/consume/robin/
sleep 60; ls -ln /mnt/ssd/container-data/paperless/consume/robin/
docker logs paperless_ngx --since 5m 2>&1 | grep -iE 'consum|asn|barcode|error' | tail -20

Erwartet: Datei taucht auf und verschwindet nach dem Import; in Paperless steht das Dokument in der Inbox mit ASN, Eigentümer Robin, nach Workflow 2 mit Titel und Korrespondent. Typ und Speicherpfad bleiben am ersten Tag meist leer, weil die KI nur Kandidaten aus ähnlichen Dokumenten kennt und der Index noch leer ist (siehe 8.7). Auf der Platte:

ls -R /mnt/paperless/media/documents/archive/ | head -20

Erwartet: robin/<Lebensbereich oder _Unsortiert>/<Korrespondent>/2026/… ASN00042.pdf. Stopp, wenn dort eine 0000001.pdf-artige Datei im Wurzelverzeichnis liegt (Vorlage rendert nicht).

8.5 Stapeltest: drei Dokumente mit je einem Etikett auf Seite 1 in einem Durchgang. Erwartet: drei Dokumente, drei ASN, richtige Seiten.

8.6 Nur falls kein ASN erkannt wird: Claude ergänzt per PR PAPERLESS_CONSUMER_BARCODE_DPI: "600" und PAPERLESS_CONSUMER_BARCODE_UPSCALE: "1.5", du machst git pull und up -d --force-recreate paperless_ngx.

8.7 Bootstrap der KI. Das Modell sieht nie deine Liste der Speicherpfade, Typen und Tags, nur Kandidaten aus ähnlichen Dokumenten im Embedding-Index. Der ist am ersten Tag leer, deshalb landet alles in _Unsortiert. Ordne die ersten zehn bis zwanzig Dokumente in der Inbox von Hand ein (Speicherpfad, Typ, Tags, Inbox-Tag entfernen). Kandidaten liest Paperless live aus der Datenbank der ähnlichen Dokumente, ein Dokument muss dafür nur im Index sein. Der Index wird nachts um 03:30 ergänzt; Dokumente, die seither dazugekommen sind, holst du sofort hinein:

docker exec paperless_ngx document_llmindex update

Voraussetzung auf dem aiserver: embed-llamacpp läuft mit --ubatch-size 2048 (aiserver#18). Vorher scheiterte der Lauf mit input (959 tokens) is too large to process … batch size: 512, weil Paperless 1024-Token-Abschnitte schickt und llama-server jede Eingabe in einem physischen Batch einbettet.

(rebuild statt update baut alles neu und bettet jedes Dokument erneut ein, nur nötig, wenn der Index kaputt ist.) Ab dann bekommen ähnliche neue Dokumente deine Pfade, Typen und Tags als Kandidaten. Die Abnahme in Phase 12 zählt deshalb den zweiten Stapel.

◆ Melde die Log-Zeilen und den ls -R-Baum.


Phase 9 · Mail über Proton Bridge (30 min)

9.1 Proton vorbereiten: Label Paperless anlegen (Seitenleiste → Labels → +). Für regelmäßige Post: Einstellungen → Filter → Filter hinzufügen, Bedingung Absender enthält <Domain>, Aktion Label Paperless, kein Verschieben. Postfachgröße notieren (Konto → Speicher), Bridge cached alles lokal auf der SSD.

9.2 Bridge anmelden [nuc]. Erst den Run-Container stoppen (Single-Instance-Lock), dann interaktiv, 2FA bereithalten:

docker compose -f /opt/rpi-docker-compose/paperless-ngx/docker-compose.yaml stop protonmail-bridge
docker compose -f /opt/rpi-docker-compose/paperless-ngx/docker-compose.yaml --profile init run --rm protonmail-bridge-init

Im Bridge-CLI: login → Adresse, Passwort, 2FA → nach „Account … was added" info → Bridge-Passwort in den Passwortmanager → exit. Dann:

docker compose -f /opt/rpi-docker-compose/paperless-ngx/docker-compose.yaml up -d protonmail-bridge
sleep 60
docker ps --filter name=protonmail-bridge --format '{{.Names}}\t{{.Status}}'
docker logs protonmail-bridge --since 2m 2>&1 | grep -iE 'sync|error|logged' | tail -10
docker exec paperless_ngx python3 -c "import socket; s=socket.create_connection(('protonmail-bridge',1243),5); print(s.recv(200)); s.close()"

Erwartet: Up … (healthy), Synchronisation läuft, keine Fehler; die letzte Zeile beginnt mit b'* OK und enthält Proton Mail Bridge.

9.3 Paperless, Einstellungen → E-Mail:

  • Konto „Proton (Bridge)": IMAP-Server protonmail-bridge, Port 1243. Nicht die Werte aus info übernehmen: 127.0.0.1 und 1143 gelten nur innerhalb des Bridge-Containers; aus dem Paperless-Container heißt der Server protonmail-bridge, und nur die socat-Weiterleitung auf 1243 antwortet, alles andere endet in Connection refused. Sicherheit Keine, Benutzername = Proton-Adresse, Passwort = Bridge-Passwort. Verbindung testen → grün.
  • Regel „Label Paperless → Anhänge": Ordner Labels/Paperless, Maximales Alter 0, Anhangstyp Nur Anhänge, Dateinamensfilter *.pdf, Umfang Nur Anhänge; Titel aus Betreff, Korrespondent aus Absender (Name), Tags Inbox + E-Mail, Eigentümer robin; Aktion nach Verarbeitung „In angegebenen Ordner verschieben" mit Ziel Labels/Paperless-erledigt (das Label vorher in Proton anlegen). Bei Bridge ist ein „Ordner" unter Labels/ ein Label: Das Verschieben tauscht nur das Label „Paperless" gegen „Paperless-erledigt", die Mail bleibt im Posteingang. Eine Aktion „Nichts" gibt es nicht, und die Aktion legt fest, welche Mails Paperless holt: „Als gelesen markieren" (Standard) nur ungelesene, „Markieren" nur Mails ohne Stern (Robin nutzt Sterne), „Verschieben" alle. Maximales Alter 0 heißt unbegrenzt; der Standard 30 Tage ließe ältere Mails liegen. Mails ohne passenden Anhang bleiben unter „Paperless" stehen und werden alle 10 Minuten erneut geprüft.

9.4 Test: einer Mail mit PDF-Anhang das Label geben, Jetzt abrufen oder 10 min warten. Erwartet: Dokument in der Inbox mit Tag E-Mail, Eintrag unter E-Mail → Verarbeitete E-Mails, Mail liegt weiter im Posteingang.

◆ Melde docker ps-Zeile, IMAP-Banner und ob die Testmail ankam.


Phase 10 · Timer, Alarme, Kuma [nuc-root] (15 min)

10.1 Units installieren.

cp /opt/rpi-docker-compose/paperless-ngx/host-config/paperless-*.service /opt/rpi-docker-compose/paperless-ngx/host-config/paperless-*.timer /etc/systemd/system/
systemctl daemon-reload
systemctl enable --now paperless-export.timer paperless-consume-collector.timer
systemctl list-timers 'paperless-*'

Erwartet: beide Timer mit nächstem Lauf (Export 01:15, Collector in ≤ 15 min).

10.2 Collector einmal von Hand, Wirkung prüfen.

systemctl start paperless-consume-collector.service
cat /mnt/ssd/container-data/monitoring-stack/textfile/paperless.prom
docker exec prometheus wget -qO- 'http://localhost:9090/api/v1/query?query=paperless_consume_collector_timestamp_seconds' | head -c 300

Erwartet: drei Gauges in der Datei; Prometheus antwortet mit "result":[{ und einem Wert. Stopp, wenn die Datei fehlt: journalctl -u paperless-consume-collector -n 20 (dann fehlt meist das Gruppenrecht).

10.3 Export einmal von Hand.

systemctl start paperless-export.service
ls /mnt/paperless/export
ls /mnt/paperless/export/json | head -3

Erwartet: archive json manifest.json originals thumbnails, je Dokument eine JSON-Datei.

10.4 Alarmregeln laden (sind mit dem git pull aus 4.1 schon da):

docker exec prometheus wget -qO- --post-data='' http://localhost:9090/-/reload; echo "exit=$?"
docker exec prometheus wget -qO- http://localhost:9090/api/v1/rules | grep -o 'PaperlessConsume[A-Za-z]*' | sort -u

Erwartet: exit=0, PaperlessConsumeCollectorStale und PaperlessConsumeStuck.

10.5 Gegenprobe der Metrik (kein Dotfile, die zählt der Collector nicht):

touch -d '-45 minutes' /mnt/ssd/container-data/paperless/consume/robin/alerttest.txt
systemctl start paperless-consume-collector.service
docker exec prometheus wget -qO- 'http://localhost:9090/api/v1/query?query=paperless_consume_stale_files' | grep -o '"value":\[[^]]*\]'
rm /mnt/ssd/container-data/paperless/consume/robin/alerttest.txt
systemctl start paperless-consume-collector.service

Erwartet: erst "1", nach dem Löschen "0". Zügig löschen, sonst versucht Paperless die Datei zu konsumieren.

10.6 Kuma (VPS-UI): Neuer Monitor → HTTP(s), Name „Paperless", URL https://paperless.home.robinwerner.net/, Intervall 60 s, Status 200.

◆ Melde list-timers, den Inhalt von paperless.prom und die Regelnamen.


Phase 11 · Nachtlauf-Nachweis (am Folgetag) [nuc-root]

/root/.local/bin/borgmatic list --archive latest --path mnt/paperless/export | head -5
/root/.local/bin/borgmatic list --archive latest --path var/lib/db-dumps | grep -i paperless
journalctl -u paperless-export.service --since yesterday | tail -3

Erwartet: Export-Pfade im Archiv, postgres-paperless_db.sql.gz im Dump-Verzeichnis, Export-Lauf 01:15 „Finished". (--path, nicht --find: das liefert falsche Nullen.)


Phase 12 · Modell-Turnier und Abnahme (eine Session mit Claude)

12.0 Last der drei KI-Aktionen (Robins Auftrag 22.09.; je Aktion zwei Anfragen, Klassifikation plus Übersetzung nach de-de, also sechs je Dokument): Beim Stapel aus 12.2 die Zeit vom Import bis zum fertig getaggten Dokument messen und die Länge der Warteschlange beobachten. Das rechnet paperless-ngx/scripts/ai_load_report.py aus der Aufgaben-API aus (je Dokument Import→letzte KI-Aktion, Zahl der KI-Aufrufe, Fehlschläge, offene; dazu Median, Maximum, Sekunden je Aufruf):

PAPERLESS_URL=https://paperless.home.robinwerner.net PAPERLESS_TOKEN="$(cat ~/.paperless-token)" \
  uv run paperless-ngx/scripts/ai_load_report.py --hours 6
``` Auf dem aiserver parallel
`docker logs llama-swap --since 30m | grep -c brain` für die Zahl der Anfragen und die Zeit je Anfrage. Läuft die
Inbox dem Stapel um mehr als etwa eine Stunde hinterher oder blockiert brain die anderen Rollen dauerhaft, wird
Workflow 2 auf zwei Aktionen zusammengezogen (siehe 7.1) oder das Modell gewechselt.

**12.1 Turnier:** fünf **von Hand eingeordnete** Dokumente (Rechnung, Schreiben, Bescheid, Vertrag, Zeugnis oder
Befund), die Dokument-IDs stehen in der URL (`/documents/<id>/`). `paperless-ngx/scripts/ai_duel.py` setzt je Modell
`llm_model` über die Konfigurations-API, holt die Vorschläge, zählt Treffer bei Korrespondent, Typ und Speicherpfad
gegen die eingeordneten Werte, misst die Zeit, druckt die Titel nebeneinander und setzt das Feld am Ende zurück:

```bash
PAPERLESS_URL=https://paperless.home.robinwerner.net PAPERLESS_TOKEN="$(cat ~/.paperless-token)" \
  uv run paperless-ngx/scripts/ai_duel.py --docs <id1>,<id2>,<id3>,<id4>,<id5>

Titel bewertet Robin von Hand aus der Ausgabe. Verliert brain, tauscht Claude das Modell per PR.

Erledigt 27.09.: brain 3/4/5 Treffer bei 60 s, coder 2/4/4 bei 14 s, translator 4/4/5 bei 56 s; die 60 s waren Denktext. Neue Rolle paperless-llamacpp (brain ohne Denken, aiserver#29): 3/4/4 bei 9 s → Paperless nutzt sie (homeserver#252). 12.0 damit gelöst: rund 40 s je Dokument für sechs Anfragen, drei Aktionen bleiben. Bei handeingeordneten Dokumenten zählt der Titel-Vergleich nicht, weil der Dateiname den Titel trägt. Herausforderer (Stand 24.09.): coder-llamacpp (qwen3-coder, Tool-Calls erprobt, 93 t/s) und translator-llamacpp (gemma4-26B, stark im Deutschen). Nicht json-llamacpp: seit Testrunde 3 ist das NuExtract3, ein Bild-zu-Felder-Modell ohne Chat. Paperless fragt über openai-like per Tool-Call ab (tool_required), ein Kandidat muss also Tool-Calls über --jinja sauber beantworten, sonst kommt gar kein Vorschlag. Zwischenspeicher: Paperless hält Vorschläge je Dokument 50 Minuten vor, aber getrennt je Modell (Schlüssel aus Modell, Endpunkt, Sprache, Benutzer). Ein Modellwechsel liefert also frische Antworten, ein zweiter Klick beim selben Modell dagegen die gespeicherte in Nullzeit. Je Dokument und Modell zählt nur der erste Klick. Zeitfenster: 12.0 und 12.1 nur messen, wenn auf dem aiserver keine großen Testrunde-3-Stichproben laufen (Qwen3.8-Flash-Next, Ling); die lassen neben sich weder die docs-Gruppe noch brain zu, und Paperless' Timeout von 300 s würde die Messung verfälschen. Wechselt Testrunde 3 später das Brain-Modell, braucht Paperless keine Änderung (Alias brain-llamacpp), nur 12.1 wird wiederholt.

12.2 Abnahme (Spec Abschnitt 11):

  • 20 echte Dokumente als Stapel: Trennung richtig, jede ASN gesetzt, ≥ 15 ohne Handkorrektur richtig in Korrespondent, Typ, Speicherpfad. Das ist der zweite Stapel nach dem Bootstrap aus 8.7
  • ls -R /mnt/paperless/media/documents/archive/robin/ | head -40 zeigt den Ordnerbaum, nichts liegt im Wurzelverzeichnis von archive/
  • OCR-Schwelle: je Dokument einen Betrag oder ein Aktenzeichen per Volltextsuche finden; ≥ 2 von 20 unbrauchbar → Vision-OCR nach E7b, sonst bleibt Tesseract
  • eine Mail mit PDF-Anhang kam automatisch an
  • Chat beantwortet eine Frage zu einem Dokument mit Quellenlink (Index nach 03:30 vorhanden)
  • paperless_consume_stale_files hat Serien, Kuma grün, Export im Archiv (Phase 11)

Danach: Doku-Seite, Issue #190, #87 schließen, E7b-Issue anlegen, Log-Eintrag (Claude).


Neustart mit leerem Bestand (Robins Entscheid 28.09.)

Nach dem Experimentieren alle Dokumente löschen und frisch anfangen, ohne den Stack anzufassen:

  1. Dokumente → alle markieren → Löschen. Sie landen im Papierkorb und behalten dort ihre ASN.
  2. Papierkorb → alle endgültig löschen. Sonst wird ein neuer Scan mit derselben Etiketten-Nummer abgewiesen („ASN existiert bereits", sichtbar auf der Aufgaben-Seite).
  3. Mail-Dokumente kommen nicht von allein zurück: unter E-Mail → Verarbeitete E-Mails den Eintrag löschen und der Mail in Proton wieder das Label „Paperless" geben (statt „Paperless-erledigt").
  4. Aufgaben-Seite auf gescheiterte Importe prüfen; deren Datei liegt noch in consume/robin/ und muss von Hand weg, sonst feuert nach 30 Minuten PaperlessConsumeStuck.
  5. Tags, Typen, Pfade und Korrespondenten bleiben. Nach den ersten zwanzig Einordnungen wieder docker exec paperless_ngx document_llmindex update (8.7).

Der Export-Timer räumt gelöschte Dokumente beim nächsten Lauf aus /mnt/paperless/export (--delete).

Wenn etwas schiefgeht

  • Phase 4 bricht ab: Es gibt nichts zu verlieren, der alte Bestand war ein Testdokument. docker compose down, Ursache mit Claude klären, Phase 4.4 wiederholen.
  • Postgres meldet Passwortfehler: POSTGRES_PASSWORD greift nur bei leerem Datadir. Prüfen, dass 4.3 das db-Verzeichnis wirklich geleert hat.
  • Login 403: PAPERLESS_TRUSTED_PROXIES steht auf 10.100.; docker network inspect proxy_network zeigt, ob Traefik wirklich aus diesem Netz kommt.
  • Scan bleibt im Consume-Ordner liegen: Eigentümer der Datei prüfen (ls -ln, muss 977 988 sein), dann docker logs paperless_ngx. Ab Phase 10 meldet das die Alarmregel nach 30 Minuten.
  • Index-Lauf meldet input (N tokens) is too large to process: Das ist llama-server auf dem aiserver, nicht Paperless. embed-llamacpp braucht --ubatch-size mindestens so groß wie der längste Abschnitt (2048 = Kontext, aiserver#18); nach git pull auf dem aiserver docker compose -f /opt/aiserver/llama-swap/docker-compose.yaml restart llama-swap, dann den Index-Lauf wiederholen.
  • Mail: „Verbindung zum Mailserver nicht möglich": meist läuft der Bridge-Run-Container nicht (nach einem info-Abstecher über das Init-Profil vergessen). docker ps --filter name=protonmail-bridge, dann up -d protonmail-bridge; danach der IMAP-Banner-Test aus 9.2.