Normale Ansicht

Es gibt neue verfügbare Artikel. Klicken Sie, um die Seite zu aktualisieren.
Ältere BeiträgeHaupt-Feeds

Mein Paperless-NGX-Mini-Konzept

31. Juli 2023 um 05:00

Paperless-NGX ist ein bekanntes und beliebtes Open Source Dokumenten-Management-System (DMS). Auch ich möchte zukünftig meinen „Papierkram“ 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üfen, ob ich auch an alles Wichtige gedacht habe. Falls euch das Konzept gefällt, dürft ihr es selbstverständlich gerne nachahmen. Und wenn ihr schwerwiegende Fehler darin entdeckt, freue ich mich über einen Hinweis.

Es ist kein Tutorial und keine Schritt-für-Schritt-Anleitung. Zwar mag dieser Text dazu dienen, sich eine eigene Umgebung aufzubauen, Mitdenken ist dabei jedoch zwingend erforderlich.

Ziele

  • Betrieb von Paperless-NGX als rootless-Podman-Container
  • Consumption-Ordner als Samba-Share freigegeben, um via Netzwerk Dateien hineinkopieren zu können
  • Getrennte Benutzerkonten für meine Frau und mich
  • Freigabe gewisser Dokumente für weitere Benutzer(gruppen)
  • Erfolgreicher Restore-Test

Infrastruktur

In meinem Heimnetzwerk betreibe ich einen Desktop-/Server-PC. Auf diesem läuft aktuell RHEL 9 als KVM/QEMU-Hypervisor. Er dient mir ebenfalls als Ansible Control Node. Hierauf betreibe ich eine RHEL-9-VM mit einer rootless-Podman-Installation. Diese VM wird auch meine Paperless-NGX-Instanz hosten.

In der VM wird das EPEL-Repository aktiviert, um daraus die Pakete podman-compose und python3-pexpect installieren zu können.

Falls ich mal nicht mehr weiß, wie dieses Setup aufgebaut wird, finden sich dazu Hinweise in folgenden Links:

Installation mit Ansible

Für die Installation der Anwendung wird die Container Route verwendet. Die Installation wird dabei unter Nutzung der Ansible-Rolle tronde.paperless_ngx_with_rootless_podman automatisiert.

Das Playbook deploy_paperless_ngx.yml, welches auf meiner Synology Diskstation abgelegt ist, führt die Installation und Konfiguration der Anwendung durch. Es installiert und konfiguriert zudem Samba und die Datei-Freigabe des Consumption-Verzeichnisses.

In dem Playbook werden folgende Rollen verwendet:

Die Rollen sind mit dem Playbook in meinem Ansible-Projekt-Verzeichnis auf meiner Synology Diskstation installiert.

Alle Playbooks und Rollen werden mit Git versioniert. Die Repositories werden auf entfernte Rechner synchronisiert.

Vorbereitung

Die Dateien docker-compose.postgres-tika.yml, docker-compose.env und .env werden aus dem Projekt-Repository in das Rollen-Verzeichnis files meiner Ansible-Rolle heruntergeladen. Die Datei docker-compose.postgres-tika.yml wird dabei zu docker-compose.yml umbenannt und bearbeitet.

Um Datenverlust vorzubeugen, wird die Ansible-Rolle mit den angepassten Dateien in die regelmäßige Datensicherung aufgenommen.

Folgende Variablen werden in meinem Ansible-Vault hinterlegt:

# Paperless-ngx with podman-compose
pnwrp_podman_user: alice
pnwrp_podman_group: alice
pnwrp_compose_dir: /home/{{ pnwrp_podman_user }}/paperless-ngx
pnwrp_paperless_superuser: alice
pnwrp_paperless_superuser_email: alice@example.com
pnwrp_paperless_superuser_password: ImWunderland
## Username and password for document scanner
brother_scanner_user: scanner
brother_scanner_pass: ImWunderland

Die Werte der Variablen werden selbstverständlich angepasst.

Das Playbook

Folgender Code-Block zeigt das fertige Playbook mit Beispielwerten:

---
- hosts: host.example.com
  remote_user: alice
  debugger: never
  vars_files:
    - files/alice.vault
  tasks:
    - name: Setup Paperless-NGX with podman-compose in rootless Podman
      include_role:
        name: ansible_role_paperless-ngx_with_rootless_podman

    - name: Enable Port 8000/tcp in host firewall
      ansible.posix.firewalld:
        port: 8000/tcp
        immediate: true
        permanent: true
        state: enabled
      become: true

    - name: >-
        Create and add {{ brother_scanner_user }} to
        {{ pnwrp_podman_group }}
      ansible.builtin.user:
        name: "{{ brother_scanner_user }}"
        comment: "Brother Dokumenten-Scanner"
        create_home: false
        groups: "{{ pnwrp_podman_group }}"
        append: true
        shell: /usr/sbin/nologin
        state: present
        system: true
      become: true

    - name: Include role vladgh.samba.server
      include_role:
        name: vladgh.samba.server
      vars:
        ansible_become: true
        samba_users:
          - name: "{{ pnwrp_podman_user }}"
            password: "{{ alice_password }}"
          - name: "{{ brother_scanner_user }}"
            password: "{{ brother_scanner_pass }}"
        samba_shares:
          - name: consumption
            comment: 'paperless consumption directory'
            path: "{{ pnwrp_compose_dir }}/consume"
            read_only: false
            guest_ok: false
            browseable: true
            owner: "{{ pnwrp_podman_user }}"
            group: "{{ pnwrp_podman_group }}"
            write_list: +{{ pnwrp_podman_group }}

    - name: Enable samba in host firewall
      ansible.posix.firewalld:
        service: samba
        immediate: true
        permanent: true
        state: enabled
      become: true

Das Playbook gewinnt sicher keinen Schönheitswettbewerb, doch arbeitet es bisher robust und tut, was es soll. In Tests habe ich meine Wunschumgebung damit mehrmals provisioniert.

Backup & Restore

Wie es sich gehört, wird erst ein Backup erstellt, dann die Anwendung inkl. aller Daten gelöscht und wiederhergestellt.

Backup

Für das Backup verwende ich ein Ansible-Playbook, welches sich zum Podman-Host verbindet und dort folgende Aufgaben ausführt:

  1. Stelle sicher, dass das Backup-Verzeichnis auf dem Remote-Host existiert
  2. Stoppe alle Paperless-NGX-Container
  3. Exportiere Podman-Volumes in TAR-Archive; hänge das aktuelle Datum an die Dateinamen an
  4. Archiviere und komprimiere paperless-ngx-Verzeichnis auf Remote-Host; hänge das aktuelle Datum an die Dateinamen an
  5. Starte alle Paperless-NGX-Container
  6. Komprimiere die Exporte der Podman-Volumes
  7. Synchronisiere das Backup-Verzeichnis auf dem Remote-Host mit einem Verzeichnis auf meiner Diskstation
  8. Synchronisiere das Diskstation-Verzeichnis mit einem verschlüsselten S3-Bucket

Es fehlt die Aufgabe, alte Backups aufzuräumen. Darum werde ich mich kümmern, wenn 70% des verfügbaren Speicherplatzes belegt sind.

Der folgende Code-Block zeigt ein Muster des verwendeten Playbooks:

---
- name: Backup podman volumes
  hosts: host.example.com
  gather_facts: true
  vars:
    paperless_ngx_dir: /home/alice/paperless-ngx
    docker_compose_file: docker-compose.yml
    remote_backup_dir: /home/alice/backups
    diskstation_backup_dir: /home/alice/diskstation/home/backups/host.example.com

  tasks:
    - name: Ensure backup directory exists
      ansible.builtin.file:
        path: "{{ remote_backup_dir }}"
        state: directory
        owner: alice
        group: alice
        mode: 0777

    - name: Stop paperless-ngx containers
      ansible.builtin.command: >
        podman-compose -f {{ paperless_ngx_dir }}/{{ docker_compose_file }} stop

    - name: List podman volumes
      ansible.builtin.command: podman volume ls --quiet
      register: __podman_volumes
      tags:
        - volumes

    - name: Output __podman_volumes
      ansible.builtin.debug:
        msg: "{{ item }}"
      loop: "{{ __podman_volumes['stdout_lines'] }}"
      tags:
        - volumes

    - name: Export podman volumes
      ansible.builtin.command: >
        podman volume export {{ item }} --output {{ remote_backup_dir }}/{{ item }}_{{ ansible_facts['date_time']['date'] }}.tar
      loop: "{{ __podman_volumes['stdout_lines'] }}"

    - name: Compact {{ paperless_ngx_dir }}
      community.general.archive:
        path: "{{ paperless_ngx_dir }}"
        dest: "{{ remote_backup_dir }}/paperless-ngx_{{ ansible_facts['date_time']['date'] }}.tar.gz"
        format: gz

    - name: Start paperless-ngx containers
      ansible.builtin.command: >
        podman-compose -f {{ paperless_ngx_dir }}/{{ docker_compose_file }} start

    - name: Compress volume exports
      community.general.archive:
        path: "{{ remote_backup_dir }}/{{ item }}_{{ ansible_facts['date_time']['date'] }}.tar"
        format: gz
        remove: true
      loop: "{{ __podman_volumes['stdout_lines'] }}"
      tags:
        - compress

    - name: Sync backups to diskstation
      ansible.posix.synchronize:
        archive: true
        compress: false
        delete: false
        dest: "{{ diskstation_backup_dir }}"
        mode: pull
        private_key: /home/alice/.ssh/ansible_id_rsa
        src: "{{ remote_backup_dir }}/"
      delegate_to: localhost
      tags:
        - rsync

    - name: Sync backups from diskstation to contabo S3
      ansible.builtin.command: rclone sync -P ../backups/ secure:backups
      delegate_to: localhost
      tags:
        - rsync

Das Playbook wird einmal wöchentlich ausgeführt. Ob ich für die geplante Ausführung cron oder systemd timer units verwende, habe ich noch nicht entschieden. Ich tendiere aus Neugier zu letzterem.

Um ein Offsite-Backup zu haben, werden die Dateien von der Diskstation einmal wöchentlich verschlüsselt und in einen S3-Speicher synchronisiert. Ich nutze dafür das Programm rclone und S3-kompatiblen Speicher von Contabo. Die folgenden Links bieten weiterführende Informationen dazu:

Restore

Der Ablaufplan für die Wiederherstellung der Anwendung mit ihren Daten sieht wie folgt aus:

  1. Eine rootless-Podman-Umgebung bereitstellen
  2. podman-compose bereitstellen
  3. TAR-Archive auf Zielsystem übertragen
  4. Paperless-NGX mit Playbook installieren
  5. Alle Container stoppen
  6. Inhalt der TAR-Archive in die Podman-Volumes importieren (siehe podman-volume-import(1)): gunzip -c hello.tar.gz | podman volume import myvol -
  7. Alle Container starten

Fazit

Bereitstellung, Sicherung und Wiederherstellung funktionieren wie beschrieben. Damit kann ich nun beginnen und die Anwendung konfigurieren und mit meinem Papierkram füttern.

Die Samba-Freigabe habe ich ebenfalls getestet. Sie funktioniert wie erwartet. PDF-Dateien mit dem Befehl find Documents -type f -iname "*.pdf" -exec cp {} /consume \; hineinzukopieren ist übrigens besonders dann eine gute Idee, wenn man sehen möchte, welche Dateien so in den Tiefen des eigenen Dateisystems schlummern.

Ansible: Seafile Professional Edition in Rootless-Podman-Umgebung bereitstellen

20. März 2023 um 06:00

Wer diesen Blog regelmäßig liest, kann den Eindruck gewinnen, es sei mein Hobby, Ansible-Rollen zu schreiben, mit denen von mir genutzte Web-Anwendungen auf Servern bereitgestellt werden können. Dieses Mal habe ich es mit Seafile getan und möchte in diesem Beitrag darüber berichten.

Was ist Seafile?

Seafile ist eine Sync&Share- bzw. Private-Cloud-Lösung ähnlich wie Nextcloud, ownCloud oder TeamDrive. Auf mich erweckt Seafile den Eindruck, als wenn der Schwerpunkt jedoch auf der Synchronisation und dem Teilen von Dateien liegt und damit genau meinem Suchmuster entspricht.

Seafile gibt es in einer Community und einer Professional Edition. Die Professional Edition darf mit bis zu drei Benutzern kostenlos verwendet werden.

Für weiterführende Informationen wird auf die Seiten des Herstellers und den Wikipedia-Artikel verwiesen.

Was ist das Ziel?

Nun, es gibt nicht das eine Ziel. Ich habe mit diesem kleinen Wochenendprojekt folgende Ziele verfolgt:

  • Beschäftige dich mit Ansible.
  • Beschäftige dich mit der Collection Containers.Podman.
  • Beschäftige dich mit rootless Podman.
  • Deploye Seafile Professional Edition in einer rootless-Podman-Umgebung, um es als Sync&Share-Lösung nutzen zu können.

Die Vorgehensweise

Zuerst habe ich mich informiert, ob es Container-Images für Seafile gibt und ob entsprechende Installationswege in der Dokumentation beschrieben sind. Meine Recherche förderte folgende Treffer zutage:

Ansible-Rollen haben eine einheitliche Struktur. Mit dem Befehl ansible-galaxy role init ansible_role_deploy_seafile_with_rootless_podman habe ich das Grundgerüst für meine Rolle erstellt. Anschließend habe ich die notwendigen Dateien {defaults,meta,tasks,vars}/main.yml mit Inhalt gefüllt und nicht benötigte Verzeichnisse wie handlers gelöscht. Mir ist dabei wichtig, dass alle notwendigen Parameter über Variablen definiert werden, die in defaults/main.yml zu finden sind. In vars/main.yml befinden sich hingegen nur Variablen, welche intern von der Rolle verwendet werden und vom Benutzer nicht explizit gesetzt werden sollen. So lässt sich die Rolle leicht wiederverwenden, um verschiedene Seafile-Instanzen auf dem gleichen Host oder in unterschiedlichen Umgebungen zu deployen.

Bevor ich die Rolle zum ersten Mal auf meinen Server angewendet habe, habe ich sie mit yamllint und ansible-lint geprüft und vorhandene Warnungen und Fehler behoben. Allerdings lassen sich mit den Lint-Werkzeugen und der Option --syntax-check nicht alle Fehler im Vorfeld finden. Da mir ein zweites Augenpaar fehlte, habe ich die letzten Tippfehler erst durch die Verwendung des Ansible Playbook Debugger gefunden.

Das Ergebnis

Das Ergebnis findet ihr auf:

Unter allen drei URLs könnt ihr meine Rolle herunterladen. Es ist damit möglich, eine lauffähige Seafile Pro Instanz bereitzustellen. Ein Test auf Idempotenz und ob diese Rolle auch zur Aktualisierung einer bestehenden Umgebung genutzt werden kann, steht noch aus.

Ihr seid herzlich eingeladen, die Rolle bei Interesse zu testen. Ich freue mich über Rückmeldungen zur Rolle und Dokumentation (Readme.md).

Ich habe das Deployment bisher nur auf Debian Buster getestet. Daher freue ich mich besonders über Rückmeldungen, wenn ihr die Rolle erfolgreich auf anderen Plattformen angewendet habt. Dann kann ich die entsprechenden Angaben für Ansible Galaxy ergänzen.

Eure Rückmeldungen nehme ich in den Kommentaren zu diesem Beitrag, per E-Mail oder in meinem neuen Matrix-Kanal #my-it-brain:matrix.org entgegen.

Fragen an meine Leser*innen

Ich interessiere mich für Themen rund um Ansible und Podman und frage mich, wie dies bei euch aussieht. Daher freue ich mich, wenn ihr in den Kommentaren oder gern auch per E-Mail und Chat folgende Fragen beantworten mögt:

  • Verwendet ihr Ansible für Software-Deployments?
  • Kennt ihr Podman und nutzt ihr es im privaten und/oder beruflichen Umfeld?
  • Findet ihr die Nutzung von Ansible zur Bereitstellung von Software auf (rootless) Podman sinnvoll? Oder bevorzugt ihr andere Bereitstellungsverfahren?

Ich freue mich auf eure Antworten.

Nextcloud im Container – Teil 6: Updates

18. April 2022 um 05:00

Herzlich willkommen zu Teil 6 meiner Reihe Nextcloud im Container. Dieser Teil behandelt das Thema Updates. Zum Verständnis empfehle ich, zuerst Teil 1 und Teil 2 zu lesen.

Nun wünsche ich euch viel Spaß beim Lesen und gute Unterhaltung.

Gedanken zum Update

Meine Nextcloud-Instanz läuft in einem Podman-Pod. Das sieht im Terminal wie folgt aus:

$ podman pod ps
POD ID        NAME    STATUS   CREATED       INFRA ID      # OF CONTAINERS
e84bec6108d1  nc_pod  Running  2 months ago  5e52555c5060  3

Dieser Pod besteht aus den folgenden drei Container-Instanzen:

$ podman ps
CONTAINER ID  IMAGE                                  COMMAND               CREATED       STATUS         PORTS                    NAMES
5e52555c5060  k8s.gcr.io/pause:3.2                                         2 months ago  Up 7 days ago  127.0.0.1:40671->80/tcp  e84bec6108d1-infra
c6571aa338ce  docker.io/library/mariadb:10.5.7       mysqld                2 months ago  Up 7 days ago  127.0.0.1:40671->80/tcp  nc_mariadb
21739d36eef1  docker.io/library/nextcloud:23-apache  apache2-foregroun...  2 months ago  Up 7 days ago  127.0.0.1:40671->80/tcp  nextcloud

Diese Container-Instanzen sind zustandslos und ephemeral (engl. für kurzlebig, vergänglich oder flüchtig). Persistent zu speichernde Daten werden außerhalb der Container-Instanzen gespeichert. Diese Eigenschaften erlauben es, Container einfach entfernen und durch neue Instanzen ersetzen zu können.

Um die Nextcloud zu aktualisieren, wird in dieser Umgebung also nicht die Anwendung innerhalb des Containers aktualisiert. Stattdessen werden die Container-Instanzen entfernt und Container-Images mit aktuelleren Versionen der Anwendung und Datenbank instanziiert.

Der Update-Prozess

Die aktuell laufenden Versionen von Nextcloud und MariaDB sind obigen Codeblock zu entnehmen. Diese Images wurden durch die beiden folgenden Zeilen in der Datei {role_path}/defaults/main.yml definiert:

MARIADB_IMAGE: docker.io/library/mariadb:10.5.7
NC_IMAGE: docker.io/library/nextcloud:23-apache

Hier kann man nun die gewünschten Versionen der zu verwendenden Container-Images eintragen. Alternativ kann man die Default-Werte auch durch entsprechende Einträge in {role_path}/vars/main.yml überschreiben. Die Einträge sehen dann bspw. wie folgt aus:

MARIADB_IMAGE: docker.io/library/mariadb:10.5.9
NC_IMAGE: docker.io/library/nextcloud:23.0.3-apache

Nun kann das Playbook mit der Ansible-Rolle aus Teil 2 dieser Reihe erneut ausgeführt werden:

$ ansible-playbook -i hosts deploy_nextcloud.yml --ask-vault-pass                                                 
Vault password:                                                                                                   
                                                                                                                  
PLAY [localhost] **************************************************************************************************
                                                                                                                  
TASK [Gathering Facts] *******************************************************************************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Main folder, needed for updating] *************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for installed/modified apps] ***********************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for local configuration] ***************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for the actual data of Nextcloud] ******************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Volume for the MySQL data files] **************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Create the podman-pod(1)] *********************************
changed: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Create MariaDB container] *********************************
changed: [localhost]                                                                                               
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Wait for DB to initilize] *********************************
ok: [localhost]                                                                                                    
                                                                                                                  
TASK [ansible_role_deploy_nextcloud_with_mariadb_pod : Create Nextcloud container] *******************************
changed: [localhost]                                                                                               
                                                                                                                  
PLAY RECAP *******************************************************************************************************
localhost                   : ok=10   changed=3    unreachable=0    failed=0    skipped=0    rescued=0    ignored=0

Nun kann man sich auf dem Zielsystem davon überzeugen, dass die neuen Container-Images zur Instanziierung verwendet wurden:

$ podman ps
CONTAINER ID  IMAGE                                      COMMAND               CREATED         STATUS             PORTS                    NAMES
5e52555c5060  k8s.gcr.io/pause:3.2                                             2 months ago    Up 7 days ago      127.0.0.1:40671->80/tcp  e84bec6108d1-infra
248f87e1135b  docker.io/library/mariadb:10.5.9           mysqld                35 seconds ago  Up 36 seconds ago  127.0.0.1:40671->80/tcp  nc_mariadb
59ac1aad168c  docker.io/library/nextcloud:23.0.3-apache  apache2-foregroun...  10 seconds ago  Up 11 seconds ago  127.0.0.1:40671->80/tcp  nextcloud

Fertig. Schon kann Nextcloud in Version 23.0.3 mit einer MariaDB 10.5.9 genutzt werden.

Fazit

Mit diesem Artikel habe ich das letzte noch offene Ziel Nr. 5 „Konfiguration und Test automatischer durch Ansible gesteuerter Updates“ erreicht. Der Update-Prozess folgte dem Container-Paradigma, die Container komplett zu ersetzen und nicht die Anwendung innerhalb der Container zu aktualisieren.

Es handelte sich im dokumentierten Fall um Updates auf ein neues Patch-Release, bei denen das Risiko für Fehler ohnehin sehr gering ist. Ob das Update auch bei Minor- bzw. Major-Releases so gut funktioniert, muss sich noch zeigen. Ich werde diesen Artikel aktualisieren, wenn es Erkenntnisse dazu gibt.

Mit diesem Artikel endet meine Reihe „Nextcloud im Container“. Ich hoffe, ich habe euch damit ein wenig unterhalten und konnte euer Wissen durch die ein oder andere Information erweitern.

Quellen und weiterführende Links

  1. Nextcloud im Container – Teil 1: Der Plan
  2. Nextcloud im Container – Teil 2: Die Ansible-Rolle
  3. Nextcloud im Container — Teil 3: Mit Reverse-Proxy
  4. Nextcloud im Container — Teil 4: Hier und da klemmt es
  5. Nextcloud im Container – Teil 5: Backup und Restore
  6. ansible_role_deploy_nextcloud_with_mariadb_pod auf GitHub
  7. Semantic Versioning 2.0.0

Nextcloud im Container – Teil 5: Backup und Restore

21. März 2022 um 06:00

Hallo und herzlich willkommen zu Teil 5 von Nextcloud im Container. In diesem Artikel geht es um Backup und Restore. Die vorhergehenden Teile dieser Serie sind weiter unten verlinkt.

Backup

Mit dem Thema Backup und Restore im Containerland habe ich mich bereits in einem früheren Artikel beschäftigt. Da ich mit dem Ansatz von dort nicht ganz zufrieden bin, gehe ich in diesem Wochenend-Projekt einen anderen Weg.

Für Nextcloud existiert die App Backup, welche im Folgenden zum Einsatz kommt. Diese wird über die App-Verwaltung in den Admin-Einstellungen der Nextcloud aktiviert.

Die englischsprachige Dokumentation (siehe [5]) macht einen soliden und verständlichen Eindruck. Ich habe mich von dieser durch die Konfiguration führen lassen.

Einstellungen der Nextcloud-Sicherung
Standard-Einstellungen der Nextcloud-Sicherungs-App

Ich habe die im obigen Bild dargestellten Einstellungen wie folgt geändert:

  1. Zeitintervall zwischen zwei vollständigen Wiederherstellungspunkten: 7 Tage
  2. Die Verschlüsselung für Wiederherstellungspunkte habe ich deaktiviert
  3. Richtlinie für die lokalen App-Daten: 4

Nun kann man über die entsprechende Schaltfläche einen vollständigen Wiederherstellungspunkt erstellen. Alternativ kann man auch warten, bis das Zeitfenster für den ersten vollständigen Wiederherstellungspunkt erreicht ist.

Ich habe die Sicherung einige Tage mitlaufen lassen. Wie der folgende Screenshot zeigt, wurden ein Full-Backup und zwei darauf folgende differenzielle Backups erzeugt, bevor ein neues Full-Backup erstellt wurde.

Backup-History on Sun 13th Mar
Backup-Historie am 13.03.2022

Hierbei ist zu beachten, dass die Backups in der gleichen Container-Instanz liegen wie die Live- bzw. Produktions-Daten. Damit ist es noch kein richtiges Backup. Denn wenn diese Instanz komplett zerstört wird, ist auch das Backup weg. Die Backups müssen daher unbedingt an einem anderen Ort gespeichert werden. Ich habe mich entschieden, diesem Thema einen eigenen Artikel zu widmen und es in Teil 6 Desaster-Recovery zu behandeln.

Wiederherstellung einer einzelnen Datei

An dieser Stelle möchte ich festhalten, wie eine einzelne Datei wiederhergestellt werden kann. Mit der Wiederherstellung einer kompletten Nextcloud-Instanz beschäftige ich mich in einem folgenden Artikel.

Wiederherstellungspunkte auflisten

Um vorhandene Wiederherstellungspunkte auflisten zu können, wechselt man auf dem Container-Host zuerst in die Container-Instanz mit der laufenden Nextcloud:

$ podman exec -u 33 -it nextcloud bash
www-data@nc_pod:~/html$

Durch ‚-u 33′ betritt man den Container mit der UID des Users www-data, welcher berechtigt ist, die folgenden Kommandos auszuführen. In obigem Beispiel heißt die Container-Instanz schlicht ’nextcloud‘. Für weitere Informationen zum Kommando siehe podman-exec(1). Die Restore-Points können nun wie folgt aufgelistet werden:

www-data@nc_pod:~/html$ ./occ backup:point:list
- retreiving data from local
 > found RestoringPoint 20220305001002-full-3ebDDoh2yinj55C
 > found RestoringPoint 20220307235028-differential-W558tuHGTnCbwgx
 > found RestoringPoint 20220310234002-differential-1NHETYudPSx6Yxc
 > found RestoringPoint 20220312001002-full-i1SY9VxDZRL8n1O
Ausgabe gekürzt.

Einen Wiederherstellungspunkt nach einer Datei durchsuchen

Angenommen, die Bilddatei Frog.jpg ist abhandengekommen. Nun kann ein Wiederherstellungspunkt wie folgt nach dem Dateinamen durchsucht werden:

www-data@nc_pod:~/html$ ./occ backup:file:search --point 20220305001002-full-3ebDDoh2yinj55C Frog.jpg

- searching in 20220305001002-full-3ebDDoh2yinj55C (2022-03-05 00:10:02)
 1/1 [============================] 100%
   > found tronde/files/Photos/Frog.jpg (447.02 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Photos/Frog.jpg (447.02 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
 1/1 [============================] 100%
   > found core/skeleton/Photos/Frog.jpg (447.02 KB) in nextcloud/nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5
 1/1 [============================] 100%

In dem angegebenen Wiederherstellungspunkt existiert die Datei Frog.jpg insgesamt dreimal. Es wird jeweils der komplette Pfad angezeigt.

Wiederherstellung von bob/files/Photos/Frog.jpg

Nun soll die Datei Frog.jpg für den Benutzer Bob aus dem oben verwendeten Wiederherstellungspunkt wiederhergestellt werden. Da meine Wiederherstellungspunkte komprimiert gespeichert werden, dient der erste Befehl im folgenden Codeblock jedoch erstmal dazu, einen Wiederherstellungspunkt zu entpacken.

www-data@nc_pod:~/html$ ./occ backup:point:unpack 20220305001002-full-3ebDDoh2yinj55C
Unpacking Restoring Point 20220305001002-full-3ebDDoh2yinj55C
 > lock and set status to unpacking
 > Browsing RestoringData data
   > Unpacking RestoringChunk data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e: proceeding
     * copying parts to temp files
       - 00001-Ur86KyoXRnp8Evt: /tmp/phpkzVs4Y
     * merging parts into single file: /tmp/phpyLjmxZ
     * Extracting /tmp/phpyLjmxZ: /tmp/phpmJG1O1
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData nextcloud
   > Unpacking RestoringChunk nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5: proceeding
     * copying parts to temp files
       - 00001-EUEeVME26dbQyf6: /tmp/phpVfSAt3
     * merging parts into single file: /tmp/phpc3kVw0
     * Extracting /tmp/phpc3kVw0: /tmp/phpF1kt52
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData apps
   > Unpacking RestoringChunk apps-14864f48-87a9-4c61-82f5-33defc3de2d2: proceeding
     * copying parts to temp files
       - 00001-65lDdATRHTQde5b: /tmp/phpDngG62
     * merging parts into single file: /tmp/php9U5X7Y
     * Extracting /tmp/php9U5X7Y: /tmp/phpXGwXP0
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData config
   > Unpacking RestoringChunk config-a8809c3e-5259-49b4-b2cb-2dbeb755949f: proceeding
     * copying parts to temp files
       - 00001-HMfvJewXGLEckCH: /tmp/phpPePsK1
     * merging parts into single file: /tmp/phpVlxXA1
     * Extracting /tmp/phpVlxXA1: /tmp/phpBUIDm0
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData apps-custom_apps-84a585e6
   > Unpacking RestoringChunk apps-custom_apps-84a585e6-c4e2adaa-eb06-4318-8196-62aea73b89c8: proceeding
     * copying parts to temp files
       - 00001-pXttjhVm9cRU6DU: /tmp/phpATT8t2
     * merging parts into single file: /tmp/php8HMcR1
     * Extracting /tmp/php8HMcR1: /tmp/phpmHRKS1
     - storing chunk in appdata ok
     * removing old chunk parts
 > Browsing RestoringData sqldump
   > Unpacking RestoringChunk sqldump-d0ebed1c-81dd-418b-a8ba-cecca8d92c00: proceeding
     * copying parts to temp files
       - 00001-QYOIpn2zOj43TsL: /tmp/phpcEUqQZ
     * merging parts into single file: /tmp/phpOwn0g1
     * Extracting /tmp/phpOwn0g1: /tmp/phpiJxrh3
     - storing chunk in appdata ok
     * removing old chunk parts
 > removing status packed
 > unlocking restoring point

www-data@nc_pod:~/html$ ./occ backup:point:restore 20220305001002-full-3ebDDoh2yinj55C --file bob/files/Photos/Frog.jpg --data data                                           
   > restoring bob/files/Photos/Frog.jpg (447.02 KB) from /data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e (rewind: 8 days, 20 hours, 51 minutes and 20 seconds): ok

Die allgemeine Befehlsform für die Wiederherstellung lautet:

backup:point:restore [--force] [--do-not-ask-data] [--do-not-ask-sql] [--file FILE] [--chunk CHUNK] [--data DATA] [--] <pointId>

Es ist nicht gut dokumentiert, wie man den korrekten Wert für --data herausfindet. Ich habe diesen durch Ausprobieren gefunden. Dann war der Restore jedoch kein Problem mehr. Wie man die korrekten Werte für --data bzw. --chunk sicher identifiziert, muss ich noch herausfinden. Falls hier jemand einen Tipp für mich hat, freue ich mich über euren Kommentar oder eine E-Mail.

Um Speicherplatz zu sparen, komprimiere ich den genutzten Wiederherstellungspunkt wieder und verlasse den Container:

www-data@nc_pod:~/html$ ./occ backup:point:pack 20220305001002-full-3ebDDoh2yinj55C
Packing Restoring Point 20220305001002-full-3ebDDoh2yinj55C
 > lock and set status to processing
 > Browsing RestoringData data
   > Packing RestoringChunk data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e: proceeding
     * copying chunk to temp file /tmp/phpsyGIxw: ok
     * compressing /tmp/phpsyGIxw: /tmp/phpjuZY7z
     * spliting /tmp/phpjuZY7z in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e.zip
 > Browsing RestoringData nextcloud
   > Packing RestoringChunk nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5: proceeding
     * copying chunk to temp file /tmp/php6f6Kfx: ok
     * compressing /tmp/php6f6Kfx: /tmp/phpz3Ndtz
     * spliting /tmp/phpz3Ndtz in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file nextcloud-853e4f81-7f34-4a85-a428-1f569fe7efb5.zip
 > Browsing RestoringData apps
   > Packing RestoringChunk apps-14864f48-87a9-4c61-82f5-33defc3de2d2: proceeding
     * copying chunk to temp file /tmp/phpf19OHA: ok
     * compressing /tmp/phpf19OHA: /tmp/phpZMcpmw
     * spliting /tmp/phpZMcpmw in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file apps-14864f48-87a9-4c61-82f5-33defc3de2d2.zip
 > Browsing RestoringData config
   > Packing RestoringChunk config-a8809c3e-5259-49b4-b2cb-2dbeb755949f: proceeding
     * copying chunk to temp file /tmp/phpykt9Rw: ok
     * compressing /tmp/phpykt9Rw: /tmp/phpxik6bx
     * spliting /tmp/phpxik6bx in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file config-a8809c3e-5259-49b4-b2cb-2dbeb755949f.zip
 > Browsing RestoringData apps-custom_apps-84a585e6
   > Packing RestoringChunk apps-custom_apps-84a585e6-c4e2adaa-eb06-4318-8196-62aea73b89c8: proceeding
     * copying chunk to temp file /tmp/php1oAWAA: ok
     * compressing /tmp/php1oAWAA: /tmp/phpOtyrjA
     * spliting /tmp/phpOtyrjA in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file apps-custom_apps-84a585e6-c4e2adaa-eb06-4318-8196-62aea73b89c8.zip
 > Browsing RestoringData internal
   > Packing RestoringChunk app.zip: already packed
   > Packing RestoringChunk restore.php: already packed
 > Browsing RestoringData sqldump
   > Packing RestoringChunk sqldump-d0ebed1c-81dd-418b-a8ba-cecca8d92c00: proceeding
     * copying chunk to temp file /tmp/phpFfW2zy: ok
     * compressing /tmp/phpFfW2zy: /tmp/phpvXl8qy
     * spliting /tmp/phpvXl8qy in parts: 1 part(s) 
     - storing parts in appdata .ok
     * Removing old chunk file sqldump-d0ebed1c-81dd-418b-a8ba-cecca8d92c00.zip
 > removing status processing, adding status packed
 > unlocking restoring point
www-data@nc_pod:~/html$ exit
exit

Wiederherstellung eines Ordners inkl. Inhalt

Manchmal ist es erforderlich, statt einzelner Dateien ganze Ordner inkl. Inhalt wiederherzustellen. Wie dies funktioniert, demonstriere ich im Folgenden für den Ordner Documents des Benutzers Bob. Dazu wechsle ich zuerst wieder in den Container, wie in diesem Abschnitt bereits beschrieben wurde.

TL;DR: Die Wiederherstellung eines Ordners inkl. dessen Inhalts ist aktuell nicht möglich (siehe [6]). Wer es eilig hat, kann direkt zum Fazit springen.

Während ich im vorherigen Abschnitt einen ausgewählten Wiederherstellungspunkt nach einer Datei durchsucht habe, liste ich diesmal alle Wiederherstellungspunkte bis zum 15.03.2022 auf, welche den gesuchten Pfad enthalten:

www-data@nc_pod:~/html$ ./occ backup:file:search bob/files/Documents --until 2022-03-15

- searching in 20220305001002-full-3ebDDoh2yinj55C (2022-03-05 00:10:02)
 1/1 [============================] 100%
   > found bob/files/Documents/Example.md (1.07 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Documents/Nextcloud flyer.pdf (365.24 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Documents/Readme.md (136 B) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
   > found bob/files/Documents/Welcome to Nextcloud Hub.docx (24.56 KB) in data/data-baa61d4a-85a8-4cc7-b43b-5a6270b7143e
 1/1 [============================] 100%

- searching in 20220307235028-differential-W558tuHGTnCbwgx (2022-03-07 23:50:28)
 1/1 [============================] 100%   no result

- searching in 20220310234002-differential-1NHETYudPSx6Yxc (2022-03-10 23:40:02)
 1/1 [============================] 100%   no result

- searching in 20220312001002-full-i1SY9VxDZRL8n1O (2022-03-12 00:10:02)
 1/1 [============================] 100%
   > found bob/files/Documents/Example.md (1.07 KB) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
   > found bob/files/Documents/Nextcloud flyer.pdf (365.24 KB) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
   > found bob/files/Documents/Readme.md (136 B) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
   > found bob/files/Documents/Welcome to Nextcloud Hub.docx (24.56 KB) in data/data-f55df957-3368-4b84-b957-4dc3e819c0ce
 1/1 [============================] 100%

- searching in 20220314234502-differential-suwLdJJegRlO4XI (2022-03-14 23:45:02)
 1/1 [============================] 100%   no result

Es muss der vollständige Pfad zum gesuchten Verzeichnis angegeben werden. Wildcards wie z.B. */Documents oder "*/Documents" funktionieren genauso wenig wie reguläre Ausdrücke. Dies kann die Suche nach einem Ordner schonmal schwierig gestalten. Denn welcher Anwender kennt schon den vollständigen Pfad zu dem Ordner, der vermisst wird?

Im obigen Codeblock ist zu sehen, dass der Pfad zum Ordner lediglich als Bestandteil der Pfade zu den darin enthaltenen Dateien angezeigt wird. Dies und die Erkenntnis, keine Suchmuster verwenden zu können, lassen bei mir erste Zweifel aufkommen, ob das Verzeichnis inkl. Inhalt wiederhergestellt werden kann, oder ob ich jede enthaltene Datei einzeln wiederherstellen muss.

Doch bevor ich mich an der Wiederherstellung versuchen kann, muss ich zuerst den ausgewählten Wiederherstellungspunkt entpacken. Wie dies aussieht, kann dem Abschnitt über die Dateiwiederherstellung entnommen werden.

Der folgende Codeblock zeigt dann auch, dass der Ordner nicht wiederhergestellt werden kann:

www-data@nc_pod:~/html$ ./occ backup:point:restore 20220312001002-full-i1SY9VxDZRL8n1O --file bob/files/Documents --data data
 
In ChunkService.php line 925:
                                                        
  [OCA\Backup\Exceptions\ArchiveFileNotFoundException]  
                                                        

backup:point:restore [--force] [--do-not-ask-data] [--do-not-ask-sql] [--file FILE] [--chunk CHUNK] [--data DATA] [--] <pointId>

Eine Wiederherstellung ist offenbar nur auf Dateiebene möglich (siehe [6]). Das ist sehr schade, da dies die Wiederherstellung deutlich verkompliziert. Vor allem da Wildcards ebenfalls nicht funktionieren.

Fazit

In diesem Artikel habe ich das Thema Backup und Recovery aufgegriffen. Ich habe kurz beschrieben, wie eine Sicherung mit der Nextcloud-Backup-App konfiguriert werden kann. Nachdem einige Wiederherstellungspunkte erstellt wurden, habe ich exemplarisch eine Datei wiederhergestellt.

Da die Wiederherstellungspunkte komprimiert gespeichert werden, musste der ausgewählte Wiederherstellungspunkt vor dem Restore erst dekomprimiert werden, was einige Zeit gedauert hat. Hier muss jeder für sich selbst entscheiden, ob man sich die kürzere Zeit zur Wiederherstellung mit einem erhöhten Speicherbedarf durch unkomprimierte Wiederherstellungspunkte erkaufen möchte oder nicht.

Die zu Beginn des Artikels noch gelobte Dokumentation offenbarte schnell Lücken und ich muss mein Urteil revidieren. Sie ist im besten Fall ausreichend.

Der Versuch, einen Ordner inkl. Inhalt wiederherzustellen, endete hingegen in einer Enttäuschung. Dies ist aktuell offenbar nicht möglich, da nur der Datei-Restore oder die Wiederherstellung der gesamten Nextcloud-Instanz unterstützt wird. Diese Erfahrung lässt mich ein wenig enttäuscht und unzufrieden zurück.

Wie sichert ihr eure Nextcloud? Welche Restore-Fälle habt ihr damit bereits durchgespielt? Ich freue mich auf eure Kommentare und darauf euch zu Teil 6: Desaster-Recovery Updates wieder begrüßen zu dürfen.

Update 27.03.2022

Der obige Text wurde dahingehend angepasst, dass nun deutlich wird, dass die Wiederherstellung ganzer Verzeichnisse inkl. deren Inhalt aktuell nicht unterstützt wird (siehe [6]). Nicht mehr zutreffende Textstellen habe ich gestrichen.

Entgegen meiner ursprünglichen Planung wird es keinen Artikel zum Thema Desaster-Recovery geben, da es mir aktuell nicht möglich ist, die Restore-Points auf externem Speicher abzulegen (siehe [7] und [8]).

Damit ist die Backup-App in ihrem aktuellen Zustand (Version 1.0.6; Stand 27.03.2022) in meinen Augen unbrauchbar. Das ist schade, da mir die Idee, die Backup&Recovery-Funktion als App direkt zu integrieren, vom Grundsatz her gut gefällt.

Bis auf Weiteres verfahre ich daher so, wie analog im Artikel Backup und Restore im Kanboard-Container-Land beschrieben. Das dort beschriebene Verfahren lässt sich auch hier anwenden und genügt meinen Anforderungen.

Quellen und weiterführende Links

  1. Nextcloud im Container – Teil 1: Der Plan
  2. Nextcloud im Container – Teil 2: Die Ansible-Rolle
  3. Nextcloud im Container – Teil 3: Mit Reverse-Proxy
  4. Nextcloud im Container – Teil 4: Hier und da klemmt es
  5. Nextcloud im Container – Teil 6: Updates
  6. Nextcloud Backup App auf GitHub
  7. How to restore a folder with all files in it? #211
  8. S3 upload still failes with OOM for 1.0.6 #237
  9. Nothing being uploaded to external storage (WebDAV) #238

Nextcloud im Container — Teil 4: Hier und da klemmt es

07. März 2022 um 06:00

Herzlich Willkommen zu Teil 4 meines Wochenend-Projekts „Nextcloud im Container“. Diesem gingen die folgenden Teile voraus:

  1. Der Plan
  2. Die Ansible-Rolle
  3. NGINX als Reverse-Proxy

Nach Teil 3 habe ich die Zwei-Faktor-Authentisierung über TOTP für meine Nutzerkonten aktiviert, die Bookmark-, Calendar- und Contact-App installiert bzw. aktiviert, ein paar Kalendertermine erstellt und ein paar Dateien hochgeladen. Nichts Wichtiges. Lediglich ein paar Daten, die ich nach einem Backup zerstören kann, um anschließend den Restore-Prozess zu testen. Zuvor möchte ich aber noch ein paar Dinge festhalten, die mir bisher aufgefallen sind.

Background jobs: Cron does not run

In den Grundeinstellungen der Nextcloud werden Hintergrund-Aufgaben konfiguriert. Diese sind laut des dortigen Hinweises wichtig, um die optimale Geschwindigkeit zu erreichen:

Um die optimale Geschwindigkeit zu erreichen ist es wichtig, dass die Hintergrund-Aktivitäten richtig konfiguriert sind. Für größere Installationen ist ‚Cron‘ die empfohlene Einstellung. Weitere Informationen findest Du in der Dokumentation.

Grundeinstellungen in den Nextcloud-Einstellungen

Die Überschrift ist der Titel des GitHub-Issues #1695. Dieser beschäftigt sich damit, dass Cron in der Container-Instanz nicht läuft. Halt genau so, wie Cron dies bei mir auch nicht tut.

Der Benutzer beryl03, welcher den Issue eröffnet hat, beschreibt, dass Cron in der Container-Instanz nicht verfügbar ist und er in der Dokumentation keinen Hinweis darauf gefunden hat. Um das Problem zu mitigieren hat beryl03 einen Cronjob auf seinem Container-Host konfiguriert, welcher sich mit der Container-Instanz verbindet und darin die Datei cron.php ausführt. Welch elender Workaround. Aber immerhin gibt es einen. Denn die Hintergrund-Aufgaben mit AJAX auszuführen, scheitert leider ebenfalls. Schade, so habe ich mir das tatsächlich nicht vorgestellt.

Im Verlauf von Issue #1695 wird darauf hingewiesen, dass zur Verwendung von Cron ein weiterer Container benötigt wird (siehe [3]). Dies wird in den Beispielen zu den Compose-Dateien beschrieben (siehe [4]). Da ich Podman und Ansible statt Docker-Compose verwende, habe ich mir diese Beispiele natürlich nicht angesehen. Das ist dem Projekt nicht anzulasten, da ich mich ja bewusst für einen anderen Weg entschieden habe. Doch denke ich, dass man das Thema Hintergrund-Aufgaben innerhalb der Projekt-Dokumentation als auch in der Nextcloud-Dokumentation etwas ausführlicher behandeln könnte und sollte. Doch wie gehe ich nun mit dem Problem um, dass meine Hintergrund-Aufgaben nicht ausgeführt werden?

Docker-Compose mit Podman nutzen

Tatsächlich habe ich einen Artikel gefunden, welcher beschreibt, wie man Docker-Compose ab Podman 3.0 nutzen kann. Allerdings bietet dieser nur eine Lösung für den Fall, dass man Podman als User root bzw. mit Root-Rechten ausführt. Da Podman bei mir rootless läuft, kommt die Lösung für mich nicht in Frage.

Nach etwas weiterer Recherche habe ich einen RFE gefunden, welcher diese Funktionalität auch für rootless-Podman fordert. Die gute Nachricht lautet, dass diese Funktion mit Podman 3.2 veröffentlicht wurde. Pech für mich, dass unter Debian stable lediglich Podman 3.0.1 in den Quellen verfügbar ist.

Ein Workaround ist besser als gar keine Lösung

Tatsächlich erscheint mir aktuell der Workaround von beryl03 (siehe [1]) der beste Weg zu sein, um die Hintergrund-Aufgaben ausführen zu lassen. Dazu führe ich auf meinem Container-Host folgenden Befehl aus:

$ podman exec -u 33 -t nextcloud php -f /var/www/html/cron.php

Damit wird das Skript cron.php innerhalb der Container-Instanz mit der Nextcloud ausgeführt. Mit -u 33 wird die UID von www-data innerhalb der Container-Instanz angegeben. Für eine genaue Erklärung des Befehls und seiner Optionen siehe podman-exec(1).

Hintergrund-Aufgaben wurden erfolgreich ausgeführt.
Die Hintergrund-Aufgaben wurden nun erfolgreich ausgeführt

Da ich nicht gern lange Befehle in die Crontab schreibe, erstelle ich ein kurzes Skript namens nextcloud_cron.sh, welches obigen Befehl aufnimmt und welches ich alle 5 Minuten von Cron ausführen lasse. Damit werde ich sich noch sehr lange arbeiten, denn nicht umsonst sagen manche: „Nichts hält so lange, wie ein gutes Improvisorium.“

Fazit von Teil 4

Ich hoffe, die Artikelserie hat euch bis hierhin ein wenig unterhalten. Wer nach einer einfachen Lösung gesucht hat, bei der man ein bis zwei Container-Images aus dem Regal nimmt, ein paar Variablen mit Werten füllt, sie auf einen Container-Host provisioniert, ausführt und fertig ist, wird sicher gemerkt haben, dass er diese Lösung in dieser Artikelreihe nicht findet.

Auch ich habe mir zu Beginn nicht vorgestellt, dass es so hakelig werden würde. Schließlich soll mit Containern doch alles einfacher werden, nicht wahr? Warum mache ich also weiter und lasse das ganze Wochenend-Projekt nicht einfach fallen? Neugier, Sturheit, eine nutzbare Nextcloud-Instanz und auch ein bisschen Spaß bilden die Antwort auf vorstehende Frage. Und deshalb mache ich auch weiter. In Teil 5 wird es um Backup und Restore gehen.

Wie betreibt ihr eure Nextcloud? Mit Container oder ohne? Unter Docker, K3s, K8s, Podman, OpenShift oder einer noch ganz anderen Lösung? Lasst es mich gern in den Kommentaren wissen. Habt ihr über eure Erfahrungen in eurem eigenen Blog geschrieben, lasst mir gern einen Link hier. Macht es gut, bis nächste Woche.

Quellen und weiterführende Links

  1. Background jobs: Cron does not run #1695
  2. AJAX Background Jobs Fail After a Period of Inactivity #1442
  3. https://github.com/nextcloud/docker/issues/1695#issuecomment-1042602441
  4. https://github.com/nextcloud/docker/blob/master/.examples/docker-compose/insecure/mariadb/apache/docker-compose.yml
  5. Using Podman and Docker Compose. Podman 3.0 now supports Docker Compose to orchestrate containers. Enable Sysadmin. 2021-01-07.
  6. [RFE]Make docker-compose work with rootless podman #9169

Nextcloud im Container — Teil 3: Mit Reverse-Proxy

28. Februar 2022 um 06:00

In diesem Teil beschreibe ich die Konfiguration von NGINX als Reverse-Proxy für mein Wochenend-Projekt „Nextcloud im Container“. Und was so langweilig klingt, wie lediglich die Direktive proxy_pass in die NGINX-Konfiguration einzufügen, dauerte dann doch überraschend lange.

Darüber hinaus dokumentiere ich die Konfiguration von fail2ban, um die Anmeldemaske der Nextcloud vor Brute-Force-Angriffen zu schützen.

Falls ihr Teil 1 und Teil 2 dieser Reihe noch nicht kennt, empfehle ich euch, diese Teile zuerst zu lesen.

Die Fehlschläge

Bevor ich zur aktuellen Konfig komme, möchte ich kurz die Fehlschläge auf dem Weg dorthin festhalten.

Nextcloud im Unterverzeichnis nicht so einfach wie gedacht

Auf meinem Server läuft bereits ein Dienst, welcher unter der URL https://www.example.com/service1 erreichbar ist. Mir gefiel die Idee, die Nextcloud unter einer URL wie https://www.example.com/service2 erreichbar zu machen.

Die Dokumentation des Container-Repos [3] ist in meinen Augen zu kurz geraten und reicht für eine erfolgreiche Konfiguration längst nicht aus.

Nachdem ich einiges über die Generierung von Back-to-URLs und Redirects gelesen und im Nextcloud-Forum gestöbert hatte, ließ ich die Idee fallen. Ich warte mal ab, wie sich Issue #401 entwickelt. Vielleicht greife ich die Idee später nochmal auf.

Falsche Back-to-URLs und Protokoll-Probleme

Im nächsten Versuch, sollte Nextcloud unter einer eigenen Domain wie nextcloud.example.com erreichbar sein. Doch auch hier klemmte es zunächst. Zuerst stolperte ich in den bekannten Untrusted-Domain-Fehler.

access-through-untrusted-domain-error.png
Bekannte Fehlermeldung, wenn trusted_domains in config.php nicht korrekt gesetzt ist.

Diesen Fehler konnte ich schnell abstellen, da ich lediglich vergessen hatte die Variable NEXTCLOUD_TRUSTED_DOMAINS mit einem Wert zu belegen. Als Wert ist der FQDN einzutragen, unter dem die Nextcloud erreichbar sein soll. Dieser ist explizit anzugeben, da der Apache-Prozess innerhalb des Nextcloud-Containers den FQDN, auf den der NGINX-Reverse-Proxy lauscht und welcher im HTTP-Header übertragen wird, nicht kennt.

Des Weiteren musste ich noch die Variablen NEXTCLOUD_OVERWRITEPROTOCOL und NEXTCLOUD_OVERWRITECLIURL in vars/main.yml mit Werten belegen, um die entsprechenden Umgebungsvariablen für die Container-Instanz zu setzen (vgl. [3]). Dies ist notwendig, da die Anwendung im Nextcloud-Container andernfalls Back-to-URLs für das HTTP-Protokoll generiert. Versucht der Browser diese aufzurufen, erscheint ein Fehler oder man landet in einer endlosen Redirect-Schleife.

Nachdem ich die Nextcloud mit Hilfe der Ansible-Rolle [2] erneut deployt habe, war der Login-Screen erreichbar:

login-screen.png
Nextcloud-Login-Screen

Der Weg hierher war für meinen Geschmack aufwändiger als er hätte sein müssen. Nur durch Lektüre und Recherche der Quellen unter [4], [5] und [6] konnte ich mich von Problem zu Problem hangeln und diese lösen. Hier ist in meinen Augen noch Raum für Verbesserungen.

Die aktuell funktionierende Konfiguration

Meine NGINX-vHost-Konfiguration habe ich mir mithilfe von [4], [5] und [6] zusammengesucht und bin bei folgender Konfig gelandet (es werden nur relevante Abschnitte wiedergegeben):

server {
	listen 80;
	listen [::]:80;
	server_name nextcloud.example.com;
	root /var/www/nextcloud.example.com;
	index index.html;
[...]
	return 301 https://$server_name$request_uri;
	location ^~ /.well-known/acme-challenge/ {
	default_type "text/plain";
	root /var/www/nextcloud.example.com;
	}
	location = /.well-known/acme-challenge/ {
	return 404;
	}
}
server {
    # Listen on Port 443
    listen 443 ssl http2;
    listen [::]:443 ssl http2;
    server_name nextcloud.example.com;
[...]
    root /var/www/nextcloud.example.com;
[...]
	location ^~ /.well-known/acme-challenge/ {
	default_type "text/plain";
	root /var/www/nextcloud.example.com;
	}
	location = /.well-known/acme-challenge/ {
	return 404;
	}

        location / {
	proxy_set_header Host $host;
	proxy_set_header X-Forwarded-Proto $scheme;
	proxy_set_header X-Real-IP $remote_addr;
	proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
	add_header Front-End-Https on;
       	proxy_pass http://127.0.0.1:40231;
        }

	location /.well-known/carddav{
		return 301 $scheme://$host/remote.php/dav;
	}

	location /.well-known/caldav {
		return 301 $scheme://$host/remote.php/dav;
	}
}

Ob dies eine gute Konfiguration ist, mag ich nicht beurteilen. Sie funktioniert zumindest. Die Nextcloud ist erreichbar und über die Weboberfläche können weitere Einstellungen vorgenommen, Nutzerkonten erstellt und Apps installiert werden.

Fail2ban für Nextcloud

Nun steht die Nextcloud mit ihrer Login-Maske im Wind. Sie stellt damit ein schönes Ziel für Brute-Force-Angriffe dar. Um es den Angreifern nicht zu leicht zu machen, habe ich für den Nextcloud-Admin keinen einfach zu erratenen Namen wie Admin, Nextcloud-Admin oder meinen Namen verwendet. Um Angreifer weiter auszubremsen, konfiguriere ich fail2ban. Wie dies installiert wird, schlagt bitte in der Dokumentation eurer Distribution nach. Ich gehe hier nicht auf die Installation ein.

Damit fail2ban fehlgeschlagene Anmeldeversuche erkennt, habe ich die Datei /etc/fail2ban/filter.d/filter.d/jk_nextcloud.conf mit folgendem Inhalt erstellt:

[Definition]
_groupsre = (?:(?:,?\s*"\w+":(?:"[^"]+"|\w+))*)
failregex = ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Login failed:
            ^\{%(_groupsre)s,?\s*"remoteAddr":"<HOST>"%(_groupsre)s,?\s*"message":"Trusted domain error.
datepattern = ,?\s*"time"\s*:\s*"%%Y-%%m-%%d[T ]%%H:%%M:%%S(%%z)?"

Dieser Filter wird in der Datei /etc/fail2ban/jail.d/jk_nextcloud.local referenziert:

[jk_nextcloud]
backend = auto
enabled = true
port = 80,443
protocol = tcp
filter = jk_nextcloud
maxretry = 3
bantime = 86400
findtime = 43200
logpath = /home/tronde/.local/share/containers/storage/volumes/nc_data/_data/nextcloud.log

Wenn innerhalb der findtime von 43200 Sekunden von einer IP-Adresse mehr als drei fehlgeschlagene Anmeldeversuche (maxretry) registriert werden, wird die entsprechede IP-Adresse für 86400 Sekunden (bantime) gesperrt. Eine Statusabfrage ist wie folgt möglich:

$ sudo fail2ban-client status jk_nextcloud
[sudo] password for tronde: 
Status for the jail: jk_nextcloud
|- Filter
|  |- Currently failed:	1
|  |- Total failed:	5
|  `- File list:	/home/tronde/.local/share/containers/storage/volumes/nc_data/_data/nextcloud.log
`- Actions
   |- Currently banned:	0
   |- Total banned:	1
   `- Banned IP list:

Es ist zu erkennen, dass aktuell keine IP-Adresse gesperrt ist. In der Vergangenheit wurde jedoch bereits eine IP gebannt.

Für mich stellt fail2ban einen Baustein zur Sicherung der Nextcloud bereit. Zusätzlich werde ich eine Zwei-Faktor-Authentisierung konfigurieren, um die Sicherheit weiter zu steigern.

Zusammenfassung

An diesem Punkt meines Wochenend-Projekts kann ich eine Nextcloud-Instanz mit meiner Ansible-Rolle deployen und hinter einem Reverse-Proxy nutzen. Bisher schützt fail2ban vor Brute-Force-Angriffen. Zukünftig wird jedoch eine Zwei-Faktor-Authentisierung diesen Schutz verstärken.

Eine lauffähige Konfiguration zu erstellen, hat dabei länger gedauert, als ich mir vorgestellt habe. Dabei hat mir missfallen, dass dies nicht mit der Dokumentation allein gelang, sondern das Forum [5] und die Internetsuchmaschine meines geringsten Misstrauens zurate gezogen werden mussten. Damit ist dieses Projekt jedoch bei weitem kein Einzelfall.

307 offene Issues (Stand 05.02.2022) zeugen davon, dass hier noch längst nicht alles rund läuft. Von der Idee, dass es sich hierbei um ein Fire-and-Forget-Projekt handeln könnte, verabschiede ich mich lieber. So bin ich auch gleich in das unter [7] beschriebene Problem getreten. Erfreulicher Weise hat die dort beschriebene Lösung funktioniert, so dass meine Cloud mir jetzt E-Mails senden kann. Ich werde mir Gedanken machen, wie die entsprechenden Abschnitte in der Dokumentation verbessert werden können und dem Projekt einen Pull-Request senden. Mal schauen wie man darauf reagiert.

Damit sind die in Teil 1 formulierten Ziele 1-3 erreicht. In Teil 4 beschäftige ich mich mit einigen weiteren Steinen, über die ich gestolpert bin, bevor ich mich dann in Teil 5 dem Thema Backup & Recovery widme.

Quellen und weiterführende Links

  1. Nextcloud im Container — Teil 1: Der Plan
  2. Nextcloud im Container — Teil 2: Die Ansible-Rolle
  3. Using the apache image behind a reverse proxy and auto configure server host and protocol
  4. https://github.com/nextcloud/docker
  5. https://help.nextcloud.com/
  6. https://docs.nextcloud.com/server/latest/admin_manual/
  7. Email settings gets wrong senEmail settings gets wrong sender domain, fails due to not RFC compliant

Nextcloud im Container – Teil 1: Der Plan

14. Februar 2022 um 06:00

Dies ist der Beginn meines zweiten Container-Projekts. Nach Kanboard im Container möchte ich diesmal eine Nextcloud-Instanz als Container, zusammen mit einem Datenbank-Container, in einem Podman-Pod betreiben.

Da ein einzelner Artikel vermutlich zu lang wird, teile ich das Projekt in mehrere Artikel auf. Wie viele es genau werden, kann ich jetzt noch nicht sagen. Am Ende der Reihe werde ich hier eine Übersicht einführen und die einzelnen Teilen entsprechend miteinander verbinden.

In diesem ersten Teil geht es um meine Motivation, das eigentliche Ziel und den groben Plan.

Was Leser dieser Reihe erwartet

Ihr könnt mich durch diese Reihe begleiten und euch von meinen Erlebnissen und Erkenntnissen unterhalten lassen. Dabei dürft ihr nicht annehmen, dass es sich bei dem von mir beschriebenen Vorgehen um eine gute Praxis handelt. Hier gilt eher: Der Weg ist das Ziel.

Ihr seid herzlich eingeladen, die Artikel zu kommentieren und über das Vorgehen und Alternativen dazu zu diskutieren. Gern in der Kommentarsektion unter den jeweiligen Beiträgen oder als Artikel in euren eigenen Blogs.

Ich plane die Artikel im Wochenrhythmus, wenigstens monatlich, zu veröffentlichen. Bitte verzeiht, wenn es etwas unregelmäßig wird. Dies ist ein Hobby, dem nur begrenzt Zeit zur Verfügung steht.

Motivation

Bei Linux-Containern handelt es sich um eine Technologie, die gekommen ist, um zu bleiben. Sie hat bereits in vielen Branchen Fuß gefasst und immer mehr Projekte bieten ihre Anwendungen zusätzlich oder ausschließlich in Form von Containern an.

Als Sysadmin mittleren Alters werden mich Linux-Container sicher noch viele Jahre begleiten. Um praktische Erfahrungen mit dem Betrieb zu sammeln, möchte ich einige private Projekte in Containern betreiben.

Beruflich arbeite ich überwiegend mit RHEL. Red Hat engagiert sich stark in den Projekten Ansible und Podman, welche ich auch unter anderen Distributionen, wie z.B. Debian, einsetze. Ich möchte das Projekt als Chance nutzen, mein Wissen auch in diesen Werkzeugen zu festigen und auszubauen.

Ich spiele schon seit einiger Zeit mit dem Gedanken, wieder eine eigene Nextcloud-Instanz zu betreiben. Da auf dem zur Verfügung stehenden Server bereits eine Nextcloud-Instanz läuft und ich meine Anwendung von der bestehenden Instanz getrennt und möglichst losgelöst vom Betriebssystem betreiben möchte, habe ich mich entschieden, Nextcloud im Container zu betreiben.

Ziele

Ziel dieses Projekts sind das Deployment und der Betrieb einer Nextcloud-Instanz als Podman-Pod. Im Einzelnen sollen folgende Ziele erreicht werden:

  1. Entwicklung eines wiederverwendbaren Verfahrens zum Deployment einer Nextcloud im Container
  2. Persistente Speicherung von Konfigurations- und inhaltlichen Daten im Dateisystem des Hosts
  3. Konfiguration eines Reverse-Proxies (NGINX) für den Zugriff auf die Nextcloud-Instanz
  4. Konfiguration von Backup und Restore für Konfiguration und Inhalte der Nextcloud-Instanz
  5. Konfiguration und Test automatischer durch Ansible gesteuerter Updates

Umgebung

Für die Umsetzung des Projekts steht mir ein Virtual Private Server (VPS) mit genügend Ressourcen zur Verfügung. Dieser wird in einem Rechenzentrum in Deutschland betrieben. Auf diesem sind Debian Bullseye, NGINX, ein OpenSSH-Server, Podman 3.0.1 (rootless) und Python 3.9.2 installiert. Damit erfüllt dieses System die Voraussetzungen, um mit Ansible konfiguriert zu werden und Container ausführen zu können.

Ansible selbst läuft in meiner privaten Arbeitsumgebung auf meinem Debian-PC und einem Fedora-35-Notebook.

Methodik und verwendete Werkzeuge

Zu Beginn habe ich mich etwas in der Nextcloud-Dokumentation und den verfügbaren Nextcloud-Images belesen. Besagte Dokumentation sowie die der verwendeten Werkzeuge sind im folgenden Abschnitt verlinkt.

Um die oben formulierten Ziele zu erreichen, werde ich in einem Python Virtual Environment eine Ansible-Version installieren, mit der ich die Collection containers.podman nutzen kann. Hiermit werde ich eine Ansible-Rolle entwickeln, die ich wiederverwenden kann, um Nextcloud-Instanzen in einer rootless-Podman-Umgebung zu deployen. Die Ansible-Rolle wird anschließend auf meinem GitHub-Account veröffentlicht.

Die Konfiguration von NGINX und acme.sh für die TLS-Zertifikate erfolgt manuell.

Quellen und weiterführende Links

In diesem Abschnitt liste ich Links zu Artikeln und Dokumentationen auf, welche ich im Vorfeld gelesen habe und deren Kenntnis ich für die Umsetzung als nützlich erachte. Zur besseren Übersicht gliedere ich diese in die Unterabschnitte Hintergrundwissen, Dokumentation und Eigene Artikel.

Die weiteren Artikel dieser Reihe

Hintergrundwissen

Dokumentation

Eigene Artikel

❌
❌