{"id":3545,"date":"2023-07-31T07:00:00","date_gmt":"2023-07-31T05:00:00","guid":{"rendered":"https:\/\/www.my-it-brain.de\/wordpress\/?p=3545"},"modified":"2025-12-27T20:24:40","modified_gmt":"2025-12-27T19:24:40","slug":"mein-paperless-ngx-mini-konzept","status":"publish","type":"post","link":"https:\/\/www.my-it-brain.de\/wordpress\/mein-paperless-ngx-mini-konzept\/","title":{"rendered":"Mein Paperless-NGX-Mini-Konzept"},"content":{"rendered":"\n<p><a href=\"https:\/\/docs.paperless-ngx.com\/\">Paperless-NGX<\/a> ist ein bekanntes und beliebtes Open Source Dokumenten-Management-System (DMS). Auch ich m\u00f6chte zuk\u00fcnftig meinen \u201ePapierkram\u201c darin verwalten.<\/p>\n\n\n\n<p>In diesem Artikel halte ich meine Gedanken fest, wie ich plane, paperless-ngx in meiner Umgebung aufzusetzen und zu betreiben.<\/p>\n\n\n\n<p>Dies hilft mir, zu pr\u00fcfen, ob ich auch an alles Wichtige gedacht habe. Falls euch das Konzept gef\u00e4llt, d\u00fcrft ihr es selbstverst\u00e4ndlich gerne nachahmen. Und wenn ihr schwerwiegende Fehler darin entdeckt, freue ich mich \u00fcber einen Hinweis.<\/p>\n\n\n\n<p>Es ist kein Tutorial und keine Schritt-f\u00fcr-Schritt-Anleitung. Zwar mag dieser Text dazu dienen, sich eine eigene Umgebung aufzubauen, Mitdenken ist dabei jedoch zwingend erforderlich.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"ziele\">Ziele<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Betrieb von Paperless-NGX als rootless-Podman-Container<\/li>\n\n\n\n<li>Consumption-Ordner als Samba-Share freigegeben, um via Netzwerk Dateien hineinkopieren zu k\u00f6nnen<\/li>\n\n\n\n<li>Getrennte Benutzerkonten f\u00fcr meine Frau und mich<\/li>\n\n\n\n<li>Freigabe gewisser Dokumente f\u00fcr weitere Benutzer(gruppen)<\/li>\n\n\n\n<li>Erfolgreicher Restore-Test<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"infrastruktur\">Infrastruktur<\/h2>\n\n\n\n<p>In meinem Heimnetzwerk betreibe ich einen Desktop-\/Server-PC. Auf diesem l\u00e4uft aktuell RHEL 9 als KVM\/QEMU-Hypervisor. Er dient mir ebenfalls als <em>Ansible Control Node<\/em>. Hierauf betreibe ich eine RHEL-9-VM mit einer rootless-Podman-Installation. Diese VM wird auch meine Paperless-NGX-Instanz hosten.<\/p>\n\n\n\n<p>In der VM wird das <a href=\"https:\/\/docs.fedoraproject.org\/en-US\/epel\/#_el9\">EPEL-Repository<\/a> aktiviert, um daraus die Pakete <code>podman-compose<\/code> und <code>python3-pexpect<\/code> installieren zu k\u00f6nnen.<\/p>\n\n\n\n<p>Falls ich mal nicht mehr wei\u00df, wie dieses Setup aufgebaut wird, finden sich dazu Hinweise in folgenden Links:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/access.redhat.com\/documentation\/en-us\/red_hat_enterprise_linux\/9\/html\/configuring_and_managing_virtualization\/index\">RHEL 9 Setting up your host, creating and administering virtual machines, and understanding virtualization features<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/access.redhat.com\/documentation\/en-us\/red_hat_enterprise_linux\/9\/html\/building_running_and_managing_containers\/index\">Using Podman, Buildah, and Skopeo on Red Hat Enterprise Linux 9<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/podman.io\/docs\/installation\">Podman Installation Instructions<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/containers\/podman\/blob\/main\/docs\/tutorials\/rootless_tutorial.md\">Basic Setup and Use of Podman in a Rootless environment<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/github.com\/containers\/podman-compose\">GitHub: containers\/podman-compose<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/pkgs.org\/download\/python3-pexpect\">python3-pexpect<\/a><\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"installation_ansible\">Installation mit Ansible<\/h2>\n\n\n\n<p>F\u00fcr die Installation der Anwendung wird die <em><a href=\"https:\/\/docs.paperless-ngx.com\/setup\/#docker_hub\">Container Route<\/a><\/em> verwendet. Die Installation wird dabei unter Nutzung der Ansible-Rolle <code>tronde.paperless_ngx_with_rootless_podman<\/code> automatisiert.<\/p>\n\n\n\n<p>Das Playbook <code>deploy_paperless_ngx.yml<\/code>, welches auf meiner Synology Diskstation abgelegt ist, f\u00fchrt die Installation und Konfiguration der Anwendung durch. Es installiert und konfiguriert zudem Samba und die Datei-Freigabe des Consumption-Verzeichnisses.<\/p>\n\n\n\n<p>In dem Playbook werden folgende Rollen verwendet:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>tronde.<code>paperless_ngx_with_rootless_podman<\/code> (Eigenkreation)<\/li>\n\n\n\n<li><a href=\"https:\/\/galaxy.ansible.com\/vladgh\/samba\">vladgh.samba.server<\/a><\/li>\n<\/ul>\n\n\n\n<p>Die Rollen sind mit dem Playbook in meinem Ansible-Projekt-Verzeichnis auf meiner Synology Diskstation installiert.<\/p>\n\n\n\n<p>Alle Playbooks und Rollen werden mit Git versioniert. Die Repositories werden auf entfernte Rechner synchronisiert.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"vorbereitung\">Vorbereitung<\/h3>\n\n\n\n<p>Die Dateien <code>docker-compose.postgres-tika.yml<\/code>, <code>docker-compose.env<\/code> und <code>.env<\/code> werden aus dem <a href=\"https:\/\/github.com\/paperless-ngx\/paperless-ngx\/tree\/main\/docker\/compose\">Projekt-Repository<\/a> in das Rollen-Verzeichnis <code>files<\/code> meiner Ansible-Rolle heruntergeladen. Die Datei <code>docker-compose.postgres-tika.yml<\/code> wird dabei zu <code>docker-compose.yml<\/code> umbenannt und bearbeitet.<\/p>\n\n\n\n<p>Um Datenverlust vorzubeugen, wird die Ansible-Rolle mit den angepassten Dateien in die regelm\u00e4\u00dfige Datensicherung aufgenommen.<\/p>\n\n\n\n<p>Folgende Variablen werden in meinem Ansible-Vault hinterlegt:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Paperless-ngx with podman-compose\npnwrp_podman_user: alice\npnwrp_podman_group: alice\npnwrp_compose_dir: \/home\/{{ pnwrp_podman_user }}\/paperless-ngx\npnwrp_paperless_superuser: alice\npnwrp_paperless_superuser_email: alice@example.com\npnwrp_paperless_superuser_password: ImWunderland\n## Username and password for document scanner\nbrother_scanner_user: scanner\nbrother_scanner_pass: ImWunderland<\/code><\/pre>\n\n\n\n<p>Die Werte der Variablen werden selbstverst\u00e4ndlich angepasst.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"playbook\">Das Playbook<\/h3>\n\n\n\n<p>Folgender Code-Block zeigt das fertige Playbook mit Beispielwerten:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n- hosts: host.example.com\n  remote_user: alice\n  debugger: never\n  vars_files:\n    - files\/alice.vault\n  tasks:\n    - name: Setup Paperless-NGX with podman-compose in rootless Podman\n      include_role:\n        name: ansible_role_paperless-ngx_with_rootless_podman\n\n    - name: Enable Port 8000\/tcp in host firewall\n      ansible.posix.firewalld:\n        port: 8000\/tcp\n        immediate: true\n        permanent: true\n        state: enabled\n      become: true\n\n    - name: &gt;-\n        Create and add {{ brother_scanner_user }} to\n        {{ pnwrp_podman_group }}\n      ansible.builtin.user:\n        name: \"{{ brother_scanner_user }}\"\n        comment: \"Brother Dokumenten-Scanner\"\n        create_home: false\n        groups: \"{{ pnwrp_podman_group }}\"\n        append: true\n        shell: \/usr\/sbin\/nologin\n        state: present\n        system: true\n      become: true\n\n    - name: Include role vladgh.samba.server\n      include_role:\n        name: vladgh.samba.server\n      vars:\n        ansible_become: true\n        samba_users:\n          - name: \"{{ pnwrp_podman_user }}\"\n            password: \"{{ alice_password }}\"\n          - name: \"{{ brother_scanner_user }}\"\n            password: \"{{ brother_scanner_pass }}\"\n        samba_shares:\n          - name: consumption\n            comment: 'paperless consumption directory'\n            path: \"{{ pnwrp_compose_dir }}\/consume\"\n            read_only: false\n            guest_ok: false\n            browseable: true\n            owner: \"{{ pnwrp_podman_user }}\"\n            group: \"{{ pnwrp_podman_group }}\"\n            write_list: +{{ pnwrp_podman_group }}\n\n    - name: Enable samba in host firewall\n      ansible.posix.firewalld:\n        service: samba\n        immediate: true\n        permanent: true\n        state: enabled\n      become: true<\/code><\/pre>\n\n\n\n<p>Das Playbook gewinnt sicher keinen Sch\u00f6nheitswettbewerb, doch arbeitet es bisher robust und tut, was es soll. In Tests habe ich meine Wunschumgebung damit mehrmals provisioniert.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"backup_restore\">Backup &amp; Restore<\/h2>\n\n\n\n<p>Wie es sich geh\u00f6rt, wird erst ein Backup erstellt, dann die Anwendung inkl. aller Daten gel\u00f6scht und wiederhergestellt.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"backup\">Backup<\/h2>\n\n\n\n<p>F\u00fcr das Backup verwende ich ein Ansible-Playbook, welches sich zum Podman-Host verbindet und dort folgende Aufgaben ausf\u00fchrt:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Stelle sicher, dass das Backup-Verzeichnis auf dem Remote-Host existiert<\/li>\n\n\n\n<li>Stoppe alle Paperless-NGX-Container<\/li>\n\n\n\n<li>Exportiere Podman-Volumes in TAR-Archive; h\u00e4nge das aktuelle Datum an die Dateinamen an<\/li>\n\n\n\n<li>Archiviere und komprimiere paperless-ngx-Verzeichnis auf Remote-Host; h\u00e4nge das aktuelle Datum an die Dateinamen an<\/li>\n\n\n\n<li>Starte alle Paperless-NGX-Container<\/li>\n\n\n\n<li>Komprimiere die Exporte der Podman-Volumes<\/li>\n\n\n\n<li>Synchronisiere das Backup-Verzeichnis auf dem Remote-Host mit einem Verzeichnis auf meiner Diskstation<\/li>\n\n\n\n<li>Synchronisiere das Diskstation-Verzeichnis mit einem verschl\u00fcsselten S3-Bucket<\/li>\n<\/ol>\n\n\n\n<p>Es fehlt die Aufgabe, alte Backups aufzur\u00e4umen. Darum werde ich mich k\u00fcmmern, wenn 70% des verf\u00fcgbaren Speicherplatzes belegt sind.<\/p>\n\n\n\n<p>Der folgende Code-Block zeigt ein Muster des verwendeten Playbooks:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>---\n- name: Backup podman volumes\n  hosts: host.example.com\n  gather_facts: true\n  vars:\n    paperless_ngx_dir: \/home\/alice\/paperless-ngx\n    docker_compose_file: docker-compose.yml\n    remote_backup_dir: \/home\/alice\/backups\n    diskstation_backup_dir: \/home\/alice\/diskstation\/home\/backups\/host.example.com\n\n  tasks:\n    - name: Ensure backup directory exists\n      ansible.builtin.file:\n        path: \"{{ remote_backup_dir }}\"\n        state: directory\n        owner: alice\n        group: alice\n        mode: 0777\n\n    - name: Stop paperless-ngx containers\n      ansible.builtin.command: &gt;\n        podman-compose -f {{ paperless_ngx_dir }}\/{{ docker_compose_file }} stop\n\n    - name: List podman volumes\n      ansible.builtin.command: podman volume ls --quiet\n      register: __podman_volumes\n      tags:\n        - volumes\n\n    - name: Output __podman_volumes\n      ansible.builtin.debug:\n        msg: \"{{ item }}\"\n      loop: \"{{ __podman_volumes&#91;'stdout_lines'] }}\"\n      tags:\n        - volumes\n\n    - name: Export podman volumes\n      ansible.builtin.command: &gt;\n        podman volume export {{ item }} --output {{ remote_backup_dir }}\/{{ item }}_{{ ansible_facts&#91;'date_time']&#91;'date'] }}.tar\n      loop: \"{{ __podman_volumes&#91;'stdout_lines'] }}\"\n\n    - name: Compact {{ paperless_ngx_dir }}\n      community.general.archive:\n        path: \"{{ paperless_ngx_dir }}\"\n        dest: \"{{ remote_backup_dir }}\/paperless-ngx_{{ ansible_facts&#91;'date_time']&#91;'date'] }}.tar.gz\"\n        format: gz\n\n    - name: Start paperless-ngx containers\n      ansible.builtin.command: &gt;\n        podman-compose -f {{ paperless_ngx_dir }}\/{{ docker_compose_file }} start\n\n    - name: Compress volume exports\n      community.general.archive:\n        path: \"{{ remote_backup_dir }}\/{{ item }}_{{ ansible_facts&#91;'date_time']&#91;'date'] }}.tar\"\n        format: gz\n        remove: true\n      loop: \"{{ __podman_volumes&#91;'stdout_lines'] }}\"\n      tags:\n        - compress\n\n    - name: Sync backups to diskstation\n      ansible.posix.synchronize:\n        archive: true\n        compress: false\n        delete: false\n        dest: \"{{ diskstation_backup_dir }}\"\n        mode: pull\n        private_key: \/home\/alice\/.ssh\/ansible_id_rsa\n        src: \"{{ remote_backup_dir }}\/\"\n      delegate_to: localhost\n      tags:\n        - rsync\n\n    - name: Sync backups from diskstation to contabo S3\n      ansible.builtin.command: rclone sync -P ..\/backups\/ secure:backups\n      delegate_to: localhost\n      tags:\n        - rsync<\/code><\/pre>\n\n\n\n<p>Das Playbook wird einmal w\u00f6chentlich ausgef\u00fchrt. Ob ich f\u00fcr die geplante Ausf\u00fchrung <code>cron<\/code> oder <code>systemd timer units<\/code> verwende, habe ich noch nicht entschieden. Ich tendiere aus Neugier zu letzterem.<\/p>\n\n\n\n<p>Um ein Offsite-Backup zu haben, werden die Dateien von der Diskstation einmal w\u00f6chentlich verschl\u00fcsselt und in einen S3-Speicher synchronisiert. Ich nutze daf\u00fcr das Programm <code>rclone<\/code> und S3-kompatiblen Speicher von Contabo. Die folgenden Links bieten weiterf\u00fchrende Informationen dazu:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><a href=\"https:\/\/contabo.com\/de\/object-storage\/\" data-type=\"link\" data-id=\"https:\/\/contabo.com\/de\/object-storage\/\">Contabo: S3-kompatibler Object Storage<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/rclone.org\">https:\/\/rclone.org<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/contabo.com\/blog\/linux-server-backup-using-rclone\/\">Contabo: Linux Server Backup Using Rclone<\/a><\/li>\n\n\n\n<li><a href=\"https:\/\/rclone.org\/crypt\/\">Verschl\u00fcsselung mit rclone Crypt<\/a><\/li>\n<\/ul>\n\n\n\n<h3 class=\"wp-block-heading\" id=\"restore\">Restore<\/h3>\n\n\n\n<p>Um die Dateien aus dem verschl\u00fcsselten S3-Objekt-Speicher wiederherstellen zu k\u00f6nnen, wird die Datei <code>$HOME\/.config\/rclone\/rclone.conf<\/code> ben\u00f6tigt, welche die geheimen Zugriffsinformationen enth\u00e4lt. Ich halte diese Datei auf meinen verschiedenen Rechnern in einem Backup au\u00dferhalb des S3-Speichers vor.<\/p>\n\n\n\n<p>Der Ablaufplan f\u00fcr die Wiederherstellung der Anwendung mit ihren Daten sieht wie folgt aus:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Eine rootless-Podman-Umgebung bereitstellen<\/li>\n\n\n\n<li><code>podman-compose<\/code> bereitstellen<\/li>\n\n\n\n<li>TAR-Archive auf Zielsystem \u00fcbertragen<\/li>\n\n\n\n<li>Paperless-NGX mit Playbook installieren<\/li>\n\n\n\n<li>Alle Container stoppen<\/li>\n\n\n\n<li>Inhalt der TAR-Archive in die Podman-Volumes importieren (siehe podman-volume-import(1)): <code>gunzip -c hello.tar.gz | podman volume import myvol -<\/code><\/li>\n\n\n\n<li>Alle Container starten<\/li>\n<\/ol>\n\n\n\n<h2 class=\"wp-block-heading\" id=\"fazit\">Fazit<\/h2>\n\n\n\n<p>Bereitstellung, Sicherung und Wiederherstellung funktionieren wie beschrieben. Damit kann ich nun beginnen und die Anwendung konfigurieren und mit meinem Papierkram f\u00fcttern.<\/p>\n\n\n\n<p>Die Samba-Freigabe habe ich ebenfalls getestet. Sie funktioniert wie erwartet. PDF-Dateien mit dem Befehl <code>find Documents -type f -iname \"*.pdf\" -exec cp {} \/consume \\;<\/code> hineinzukopieren ist \u00fcbrigens besonders dann eine gute Idee, wenn man sehen m\u00f6chte, welche Dateien so in den Tiefen des eigenen Dateisystems schlummern.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Paperless-NGX ist ein bekanntes und beliebtes Open Source Dokumenten-Management-System (DMS). Auch ich m\u00f6chte zuk\u00fcnftig meinen \u201ePapierkram\u201c darin verwalten. In diesem Artikel halte ich meine Gedanken fest, wie ich plane, paperless-ngx in meiner Umgebung aufzusetzen und zu betreiben. Dies hilft mir, zu pr\u00fcfen, ob ich auch an alles Wichtige gedacht habe. Falls euch das Konzept gef\u00e4llt,<span class=\"continue-reading\"> <a href=\"https:\/\/www.my-it-brain.de\/wordpress\/mein-paperless-ngx-mini-konzept\/\">[Weiterlesen&#8230;]<\/a><\/span><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"_metis_text_type":"standard","_metis_text_length":10271,"_post_count":0,"footnotes":""},"categories":[57],"tags":[430,791,305,792],"class_list":["post-3545","post","type-post","status-publish","format-standard","hentry","category-wochenend-projekte","tag-osbn","tag-paperless-ngx","tag-planet","tag-rclone"],"public_identification_id":"786f8cde101247d59e52533753447b0f","private_identification_id":"908c8e5a4f9a4274a93c0ad0610e5eb1","_links":{"self":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts\/3545","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/comments?post=3545"}],"version-history":[{"count":11,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts\/3545\/revisions"}],"predecessor-version":[{"id":4270,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/posts\/3545\/revisions\/4270"}],"wp:attachment":[{"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/media?parent=3545"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/categories?post=3545"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.my-it-brain.de\/wordpress\/wp-json\/wp\/v2\/tags?post=3545"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}