Initial commit
This commit is contained in:
288
docs/01-paperless-vorbereiten.md
Normal file
288
docs/01-paperless-vorbereiten.md
Normal file
@@ -0,0 +1,288 @@
|
||||
# 1. Paperless-Instanz vorbereiten
|
||||
|
||||
Ziel ist eine **leere** Paperless-Instanz, in die importiert wird. Ob sie
|
||||
später produktiv läuft oder nur der Migration dient, ist zunächst
|
||||
gleichgültig — die Konfiguration sollte aber von Anfang an die
|
||||
Produktivfassung sein.
|
||||
|
||||
---
|
||||
|
||||
## Wo migrieren?
|
||||
|
||||
Der Import ist CPU-gebunden, nicht I/O-gebunden. Wenn die spätere
|
||||
Produktivumgebung ein schwacher Server ist, lohnt es, die Migration auf
|
||||
einer stärkeren Maschine zu fahren und den Bestand danach umzuziehen.
|
||||
|
||||
Ein Paperless-Bestand ist portabel: Postgres-Dump plus die Verzeichnisse
|
||||
`media` und `data`. Voraussetzung ist, dass Paperless- und
|
||||
Postgres-**Hauptversion** auf beiden Seiten übereinstimmen.
|
||||
|
||||
Zweiter Vorteil: Man kann beim Skriptbau beliebig oft von vorn anfangen,
|
||||
ohne die produktive Instanz anzufassen.
|
||||
|
||||
---
|
||||
|
||||
## Versionen festlegen
|
||||
|
||||
**Nie `:latest`.** Alle fünf Images pinnen — Paperless, Postgres, Redis,
|
||||
Gotenberg und Tika.
|
||||
|
||||
Bei **Tika** ist das keine Stilfrage: `apache/tika:latest` zeigt inzwischen
|
||||
auf Tika 4.0.0, und Paperless 3.1 quittiert das mit `HTTP 406 Not
|
||||
Acceptable` bei **jedem** Office-Dokument und **jeder** `.eml`. In einem
|
||||
Testlauf waren das 315 Fehlschläge in einer einzigen Tranche, ohne
|
||||
verwertbare Fehlermeldung.
|
||||
|
||||
---
|
||||
|
||||
## Verzeichnisse
|
||||
|
||||
```bash
|
||||
mkdir -p data/{data,media,export,consume,pgdata,redis}
|
||||
```
|
||||
|
||||
Auf **Btrfs** vor dem ersten Start das Copy-on-Write für die
|
||||
Datenbankverzeichnisse abschalten, sonst fragmentieren sie stark:
|
||||
|
||||
```bash
|
||||
chattr +C data/pgdata data/redis
|
||||
lsattr -d data/pgdata # muss ---------------C zeigen
|
||||
```
|
||||
|
||||
Wirkt nur auf leere Verzeichnisse. Nachträglich greift es bei bestehenden
|
||||
Dateien nicht.
|
||||
|
||||
Auf **ZFS** ist das nicht nötig. Dort lohnt getrenntes Anlegen der
|
||||
Datenbereiche, damit Snapshots und Sicherungen gezielt greifen.
|
||||
|
||||
---
|
||||
|
||||
## Benutzer im Container
|
||||
|
||||
Das Paperless-Image startet seit 3.x bereits als unprivilegierter Benutzer
|
||||
(1000:1000), **nicht als root**. Damit ist `USERMAP_UID`/`USERMAP_GID`
|
||||
wirkungslos — dieser Mechanismus setzt voraus, dass das Einstiegsskript als
|
||||
root beginnt und selbst herunterschaltet. Im Log erscheint dann:
|
||||
|
||||
```
|
||||
[init-user] WARNING: USERMAP_UID/USERMAP_GID are set but have no effect
|
||||
when the container is started as a non-root user
|
||||
```
|
||||
|
||||
Setz die UID stattdessen fest in der Compose und richte den Dateibesitz
|
||||
danach aus:
|
||||
|
||||
```yaml
|
||||
user: "1500:1500"
|
||||
```
|
||||
|
||||
### Zwei Container, bei denen das falsch wäre
|
||||
|
||||
**Postgres.** Sein Einstiegsskript korrigiert beim Start Besitzer und
|
||||
Rechte des Datenverzeichnisses und schaltet dann selbst auf UID 999
|
||||
herunter. Ein erzwungener Benutzer lässt das scheitern:
|
||||
|
||||
```
|
||||
chmod: changing permissions of '/var/lib/postgresql/data': Permission denied
|
||||
```
|
||||
|
||||
Stattdessen gehört das Verzeichnis auf dem Host der 999:
|
||||
|
||||
```bash
|
||||
chown 999:999 data/pgdata
|
||||
```
|
||||
|
||||
**Gotenberg.** Chromium legt beim Start ein Crashpad-Verzeichnis im Home
|
||||
des internen Benutzers an. Unter fremder UID scheitert das:
|
||||
|
||||
```
|
||||
chrome failed to start: chrome_crashpad_handler: --database is required
|
||||
```
|
||||
|
||||
Symptom ist ein HTTP 500 beim Rendern von `.eml`. Unkritisch ohne eigene
|
||||
UID, weil Gotenberg keine Bind Mounts hat und nur im internen Netz
|
||||
erreichbar ist.
|
||||
|
||||
---
|
||||
|
||||
## SELinux
|
||||
|
||||
Auf Systemen mit SELinux im Enforcing-Modus — etwa openSUSE Tumbleweed —
|
||||
brauchen Bind Mounts eine Kennzeichnung, sonst bekommen die Container
|
||||
`Permission denied`, obwohl Besitzer und Rechte stimmen.
|
||||
|
||||
Erkennbar am Punkt hinter den Rechten und am Kontext `unlabeled_t`:
|
||||
|
||||
```bash
|
||||
ls -ldZ data/pgdata
|
||||
# drwxr-xr-x. 999 999 unconfined_u:object_r:unlabeled_t:s0
|
||||
```
|
||||
|
||||
`unlabeled_t` tritt typischerweise bei frisch angelegten
|
||||
Btrfs-Subvolumes auf. Dauerhafte Lösung:
|
||||
|
||||
```bash
|
||||
zypper in policycoreutils-python-utils
|
||||
semanage fcontext -a -t container_file_t "/pfad/zu/data(/.*)?"
|
||||
restorecon -Rv /pfad/zu/data
|
||||
```
|
||||
|
||||
Die `:z`-Option an den Bind Mounts wirkt nur, wenn der Docker-Daemon mit
|
||||
`"selinux-enabled": true` läuft. Der `semanage`-Eintrag ist unabhängig
|
||||
davon und überlebt Neustarts.
|
||||
|
||||
**Setz das Label, bevor große Datenmengen hineinkopiert werden** — sonst
|
||||
läuft `restorecon` später über den gesamten Bestand.
|
||||
|
||||
---
|
||||
|
||||
## Konfiguration
|
||||
|
||||
`.env.example` nach `.env` kopieren und anpassen. Geheimnisse erzeugen mit
|
||||
`openssl rand -hex 32` — hexadezimal statt base64, dann enthält der Wert
|
||||
garantiert keine Zeichen, die die Env-Datei durcheinanderbringen.
|
||||
|
||||
### Was besonders zu beachten ist
|
||||
|
||||
**`PAPERLESS_DBPASS` ausschreiben.** Docker Compose ersetzt Variablen nur
|
||||
in der `docker-compose.yml`, nicht innerhalb einer über `env_file`
|
||||
eingebundenen Datei. Ein `${POSTGRES_PASSWORD}` dort wird wörtlich
|
||||
durchgereicht.
|
||||
|
||||
**`PAPERLESS_FILENAME_FORMAT` einmal festlegen.** Eine spätere Änderung
|
||||
benennt den gesamten Bestand um. Seit 3.x doppelte geschweifte Klammern:
|
||||
|
||||
```
|
||||
PAPERLESS_FILENAME_FORMAT={{ created_year }}/{{ document_type }}/{{ title }}
|
||||
```
|
||||
|
||||
**OCR-Einstellungen in 3.x-Syntax.** Die alten Werte `OCR_MODE=skip` und
|
||||
`OCR_SKIP_ARCHIVE_FILE` sind entfallen:
|
||||
|
||||
```
|
||||
PAPERLESS_OCR_MODE=auto
|
||||
PAPERLESS_ARCHIVE_FILE_GENERATION=auto
|
||||
```
|
||||
|
||||
`auto` überspringt OCR, wenn bereits ein Textlayer vorhanden ist — bei
|
||||
einem Bestand aus einem anderen DMS der Regelfall. Der Unterschied liegt
|
||||
bei Stunden gegen Tage.
|
||||
|
||||
**`PAPERLESS_CONSUMER_IGNORE_PATTERNS` nicht setzen.** In 3.x ist das eine
|
||||
Liste von **regulären Ausdrücken**, nicht von Glob-Mustern. Ein Eintrag wie
|
||||
`*.part` lässt den Consumer beim Start abstürzen:
|
||||
|
||||
```
|
||||
re.PatternError: nothing to repeat at position 0
|
||||
```
|
||||
|
||||
Die eingebauten Vorgaben decken `.DS_Store`, `Thumbs.db` und Ähnliches
|
||||
bereits ab.
|
||||
|
||||
**Consume-Ordner abschalten.** Während der Migration:
|
||||
|
||||
```
|
||||
PAPERLESS_CONSUMER_POLLING=0
|
||||
```
|
||||
|
||||
**Worker hochdrehen**, aber mit Bedacht. Die Migration ist seriell je
|
||||
Dokument, Parallelität nutzt vor allem den Sidecar-Diensten.
|
||||
|
||||
---
|
||||
|
||||
## Starten
|
||||
|
||||
```bash
|
||||
docker compose up -d --build
|
||||
docker compose logs -f webserver
|
||||
```
|
||||
|
||||
Das `--build` ist nötig, wenn ein eigenes Image mit Plugins verwendet
|
||||
wird. Ohne baut Compose nicht neu, sobald der Tag existiert.
|
||||
|
||||
Erwartet wird eine saubere Startmeldung ohne Warnungen. Jede Warnung im
|
||||
Log ist eine spätere Fehlerquelle — die drei häufigsten betreffen
|
||||
OCR-Einstellungen, das Dateinamensformat und die Benutzerabbildung.
|
||||
|
||||
---
|
||||
|
||||
## Benutzer und Gruppen anlegen
|
||||
|
||||
**Vor dem ersten Import**, damit die Rechteübersetzung sie zuordnen kann.
|
||||
|
||||
```bash
|
||||
docker compose exec webserver python manage.py shell
|
||||
```
|
||||
|
||||
```python
|
||||
from django.contrib.auth.models import User, Group
|
||||
for n in ["benutzer_a", "benutzer_b"]:
|
||||
User.objects.get_or_create(username=n)
|
||||
for g in ["Gruppe_A", "Gruppe_B"]:
|
||||
Group.objects.get_or_create(name=g)
|
||||
|
||||
U = lambda n: User.objects.get(username=n)
|
||||
Group.objects.get(name="Gruppe_A").user_set.set([U("benutzer_a"), U("benutzer_b")])
|
||||
|
||||
for g in Group.objects.all():
|
||||
print(g.id, g.name, [u.username for u in g.user_set.all()])
|
||||
for u in User.objects.all():
|
||||
print(u.id, u.username)
|
||||
```
|
||||
|
||||
**Notier die IDs.** Sie werden in `rollen.yml` und in
|
||||
`PAPERLESS_DEFAULT_PERMISSIONS_OWNER` gebraucht — und dort erwartet
|
||||
Paperless eine **numerische ID**, keinen Benutzernamen. Eine falsche ID
|
||||
lässt jeden Konsumvorgang mit `Error while queuing document` scheitern.
|
||||
|
||||
### Löschrechte
|
||||
|
||||
Keiner Gruppe das Recht zum endgültigen Löschen geben. Zusammen mit
|
||||
Audit-Log, Versionierung und Papierkorb-Frist ist das der vierte Baustein
|
||||
der Nachvollziehbarkeit.
|
||||
|
||||
---
|
||||
|
||||
## API-Token
|
||||
|
||||
Im Benutzerprofil erzeugen, nicht in der allgemeinen Verwaltung.
|
||||
|
||||
```bash
|
||||
export PT=dein_token_40_zeichen
|
||||
curl -s -H "Authorization: Token $PT" http://localhost:8000/api/documents/
|
||||
```
|
||||
|
||||
Erwartet wird `{"count":0,...}`. Kommt
|
||||
`{"detail":"Authentication credentials were not provided."}`, fehlt der
|
||||
Header-Name — die Variable enthält nur den Token, nicht `Authorization:`.
|
||||
|
||||
Alternativ per Shell:
|
||||
|
||||
```bash
|
||||
docker compose exec webserver python manage.py shell -c \
|
||||
"from django.contrib.auth.models import User
|
||||
from rest_framework.authtoken.models import Token
|
||||
t,_=Token.objects.get_or_create(user=User.objects.get(username='DEIN_BENUTZER'))
|
||||
print(t.key)"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Snapshot
|
||||
|
||||
Wenn Instanz, Benutzer, Gruppen und Token stehen: einen Snapshot des
|
||||
Datenverzeichnisses anlegen. Das ist der Zustand, zu dem man beim Bau des
|
||||
Migrationsskripts immer wieder zurückwill.
|
||||
|
||||
```bash
|
||||
docker compose down
|
||||
btrfs subvolume snapshot -r data ../snapshots/paperless-vorbereitet
|
||||
docker compose up -d
|
||||
```
|
||||
|
||||
Auf ZFS entsprechend `zfs snapshot`. Container vorher stoppen, sonst ist
|
||||
der Datenbankstand nicht konsistent zu den Dateien.
|
||||
|
||||
---
|
||||
|
||||
Weiter mit [Export aus ecoDMS](02-export-aus-ecodms.md).
|
||||
Reference in New Issue
Block a user