Initial commit
This commit is contained in:
250
docs/05-nacharbeit.md
Normal file
250
docs/05-nacharbeit.md
Normal file
@@ -0,0 +1,250 @@
|
||||
# 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.
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```csv
|
||||
src_docid,dst_docid
|
||||
10346,10351
|
||||
```
|
||||
|
||||
```bash
|
||||
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:**
|
||||
|
||||
```bash
|
||||
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
|
||||
|
||||
```bash
|
||||
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?
|
||||
```bash
|
||||
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.
|
||||
|
||||
```bash
|
||||
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:
|
||||
|
||||
```bash
|
||||
# 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:
|
||||
|
||||
```bash
|
||||
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_dump` plus `media` und `data`. 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.
|
||||
Reference in New Issue
Block a user