7.5 KiB
5. Nacharbeit
Diese Schritte laufen nach allen Tranchen, nicht zwischendurch. Zeitstempel und Verknüpfungen brauchen ein vollständiges Journal.
Vorher einen Snapshot anlegen — die ersten beiden Schritte greifen direkt in die Datenbank ein.
1. Zeitstempel setzen
Der Endpunkt update_version nimmt keinen Zeitstempel entgegen. Alle
Versionen tragen deshalb zunächst das Importdatum statt des
ecoDMS-Zeitstempels.
python3 paperless_import.py --emit-timestamps > fix_added.py
wc -l fix_added.py
head -20 fix_added.py
Kurz hineinsehen: Die Liste sollte plausible Datumswerte enthalten und ungefähr so viele Einträge haben wie Versionen vorhanden sind.
docker compose exec -T webserver python manage.py shell < fix_added.py
Das erzeugte Skript benutzt Document.objects.filter(pk=…).update(added=…)
statt save(). Das ist wichtig: update() umgeht auto_now-Felder und
Signal-Handler, die den Wert sonst wieder überschreiben und nebenbei
Reindexierungen auslösen würden.
Prüfen
curl -s -H "Authorization: Token $PT" \
"http://localhost:8000/api/documents/?archive_serial_number=<docid>" \
| python3 -c "import sys,json; print(json.dumps(json.load(sys.stdin)['results'][0]['versions'], indent=2))"
Die added-Werte müssen aus dem Quellzeitraum stammen, nicht vom
Importdatum.
Die
versions-Liste ist absteigend sortiert, neueste zuerst.
2. Notizen übertragen
python3 ecodms_notizen.py --exports /pfad/export-daten --tranchen 1-6 \
--dry-run --csv notizen.csv
Der Trockenlauf schreibt nichts, erzeugt aber die vollständige Liste. In
die CSV sehen, bevor übertragen wird: Bei jeder Zeile stehen die
aufgelöste ecodms_docid, die Paperless-ID, der Titel und der Notiztext.
Passen Titel und Notiz inhaltlich zusammen, stimmt die Zuordnung.
python3 ecodms_notizen.py --exports /pfad/export-daten --tranchen 1-6 \
--csv notizen-uebertragen.csv
Der Lauf ist wiederholbar — textgleiche Notizen am selben Dokument werden übersprungen.
Warum über die Exporte
econotice.nid enthält die docs_id. Die Auflösung nach docid steht in
der docs-Tabelle, und die existiert nur im Export, nicht in der
Live-Datenbank.
Das Skript liest deshalb alle Tranchen ein und baut eine gemeinsame Zuordnungstabelle, bevor es überträgt — eine Notiz kann zu einem Dokument aus einer anderen Tranche gehören.
3. Klassifikator und Suchindex
docker compose exec webserver python manage.py document_create_classifier
docker compose exec webserver python manage.py document_index reindex
Der Reindex über einen großen Bestand dauert.
Das ist der unterschätzte Nebeneffekt der Migration: Der Klassifikator hat jetzt einen vollständig klassifizierten Bestand als Trainingsbasis. Neue Dokumente werden dadurch von Anfang an brauchbar vorgeschlagen.
4. Verknüpfungen
Dokumentverknüpfungen sind im Export nicht enthalten und in der Live-Datenbank nicht auffindbar. Sie müssen von Hand nachgetragen werden.
Falls die Paare aus einer anderen Quelle vorliegen, geht es automatisch:
src_docid,dst_docid
10346,10351
python3 paperless_import.py --links verknuepfungen.csv
Das Skript setzt beide Richtungen ausdrücklich. Die automatische Gegenrichtung von Paperless greift bei Massenbearbeitung nachweislich nicht.
Von Hand geht es über das Zusatzfeld vom Typ Document Link. Die Archiv-Seriennummer entspricht der ecoDMS-ID, das Auffinden ist also unkompliziert.
5. Ausschlusslisten abarbeiten
Zwei Gruppen sind liegengeblieben:
Nicht unterstützte Dateitypen — aus exportN-nacharbeit.csv.
Typischerweise .html, .zip, .indd. Je nach Inhalt: HTML über den
Browser als PDF drucken, ZIP entpacken und einzeln ablegen, oder gar nicht
übernehmen.
Fehlschläge aus dem Journal:
sqlite3 -header -csv migration.sqlite \
"SELECT ecodms_docid, tranche, last_error FROM mapping
WHERE status IN ('failed','uebersprungen');" > nacharbeit-journal.csv
Ein wiederkehrendes Muster: .eml-Dateien, die Paperless als text/html
erkennt. Es sind gültige E-Mails mit korrekten Kopfzeilen, aber die
Inhaltserkennung übergewichtet den HTML-Rumpf. Ein ausdrücklich
mitgegebener MIME-Typ hilft nicht — Paperless prüft den Inhalt.
Behandlung: Mail im Mailprogramm öffnen und als PDF drucken. Das Ergebnis ist meist besser lesbar als Paperless' eigene Darstellung.
6. Abschlussprüfung
python3 paperless_import.py --verify
sqlite3 -header -column migration.sqlite \
"SELECT status, COUNT(*) FROM mapping GROUP BY status;"
Stichproben in der Oberfläche:
- Dokument mit mehreren Versionen — Kette und Zeitstempel korrekt?
- Dokument mit Notiz — Text vorhanden und beim richtigen Dokument?
- Mehrfach klassifiziertes Dokument — alle Tags vorhanden?
- ZUGFeRD-Rechnung — eingebettete XML noch da?
docker compose exec webserver bash -c \ "pdfdetach -list /usr/src/paperless/media/documents/originals/*/*.pdf" | head - Volltextsuche nach einem bekannten Begriff
- Anmeldung als eingeschränkter Benutzer — sieht er nur seine Dokumente?
Der letzte Punkt ist der wichtigste. Ein Dokument ohne Eigentümer ist in Paperless für jeden sichtbar, und in der Oberfläche fällt das als Superuser nicht auf.
export PT2=token_eines_eingeschraenkten_benutzers
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Token $PT2" \
http://localhost:8000/api/documents/<fremde-id>/
403 ist richtig.
7. Umzug in die Produktivumgebung
Falls die Migration auf einer anderen Maschine lief:
# Quelle
docker compose stop webserver
docker compose exec -T db pg_dump -U paperless -Fc paperless > paperless.dump
rsync -rlt --no-perms --no-owner --no-group --info=progress2 \
data/media/ ziel:/pfad/media/
rsync -rlt --no-perms --no-owner --no-group --info=progress2 \
data/data/ ziel:/pfad/data/
--no-perms --no-owner --no-group ist wichtig, wenn das Ziel eigene
Besitzverhältnisse oder ACLs hat — sonst überschreibt rsync sie mit denen
der Quelle.
Auf dem Ziel erst nur Datenbank und Broker starten, damit Paperless nicht auf ein leeres Schema trifft und ein frisches anlegt:
docker compose up -d db broker
docker compose logs -f db # auf "ready to accept connections" warten
docker compose exec -T db \
pg_restore -U paperless -d paperless --clean --if-exists < paperless.dump
docker compose exec -T db psql -U paperless -d paperless -c \
"SELECT count(*) FROM documents_document;"
docker compose up -d
Was identisch sein muss
| Wert | Warum |
|---|---|
PAPERLESS_SECRET_KEY |
entwertet sonst alle Tokens und Freigabelinks |
PAPERLESS_FILENAME_FORMAT |
benennt sonst den gesamten Bestand um |
| Postgres-Hauptversion | ein Dump aus 17 läuft nicht in 16 |
| Paperless-Version | gleich oder neuer, nie älter |
Die Quellinstanz stehen lassen, bis die Zielinstanz nachweislich läuft. Ein paar Wochen produktiver Betrieb sind eine bessere Freigabe als jede Prüfliste.
8. Betrieb
Was nach der Migration eingerichtet werden sollte, aber nicht Teil davon ist:
- Reverse Proxy mit TLS, Paperless nie direkt exponieren
- Zwei-Faktor für alle Konten
- Sicherung:
pg_dumpplusmediaunddata. Das Datenverzeichnis von Postgres nicht auf Dateiebene sichern — der Stand wäre inkonsistent - Consume-Ordner und Mail-Abruf
- Papierkorb-Frist und Audit-Log
Und der Teil, den man auslässt: einmal wiederherstellen. Eine Sicherung, die nie zurückgespielt wurde, ist eine Vermutung.