# 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).