Kurze Vorgeschichte

Einer der Hauptgründe für die Anschaffung meines neuen NAS Systems war die Möglichkeit den EcoDMS Server direkt als Dockercontainer auf dem NAS laufen zu lassen um stets eingescannte Dokumente direkt in dem von EcoDMS überwachten Scaninput Ordner zu speichern. Leider verfügt mein Scanner, ein HP Officejet Pro 8600, nicht über eine komfortable Möglichkeit mehrseitige Dokumente beidseitig einzuscannen, sodass ich mir etwas einfallen lassen musste...

Idee

Ich bin im Laufe der Zeit immer wieder mit dem Kommandozeilentool pdftk in Kontakt gekommen um Beispielsweise aus Vorlesungsscripten druckbare, vierseitige PDFs zu erzeugen und nach kurzer Suche fand ich heraus, dass folgender Befehl:

    pdftk A=ungerade.pdf B=gerade.pdf shuffle A Bend-1 output merged.pdf

die Seiten in korrekter Reihenfolge in der Zieldatei merged.pdf anordnet, wenn zuerst alle ungeraden Seiten in der Datei ungerade.pdf und dann alle geraden der Datei gerade.pdf eingescannt wurden. Praktisch bedeutet das:

  • lege den ganzen Stapel in den Dokumenteneinzug
  • scanne sie unter dem Namen ungerade.pdf
  • drehe den Stapel herum und scanne mit dem Dateinamen gerade.pdf
  • führe am Rechner oben genannten Befehl aus
  • kopiere die zusammengesetzte Datei in den scaninput Ordner (und lösche ggf. die Einzeldateien)

Schnell wird offensichtlich, dass sich dieser Prozess mit ein paar Handgriffen in der Shell automatisieren lässt.

Docker

Hauptproblem bei pdftk ist jedoch, das es nicht als fertiges Paket weder in den offiziellen QNAP Addon Repositorys noch in der unoffiziellen qnapware/entware-ng vorhanden ist und zusätzlich zum kompilieren gcj erforderlich ist, welches ebenfalls nicht vorhanden ist. Klar könnte man beides aus den Quellen direkt auf dem NAS kompilieren, doch ist das ganze ein wenig umständlich, wenn es doch einfacher geht: durch verwenden eines weiteren Docker Containers. Für pdftk gibt es bereits ein fertiges Dockerimage. Darauf aufbauend habe ich ein neues Dockerfile und ein kleines Shellscript geschrieben welches einen gegebenen Ordner auf neue PDF-Dateien überwacht, diese gegebenenfalls zusammenfügt und in einem bestimmten Zielverzeichnis abspeichert.

Ordner Überwachung

Um unter GNU/Linux/Unix einen Ordner zu überwachen gibt es verschiedene Methoden. In modernen Distributionen, die Systemd verwenden, ist es am einfachsten mit einem Systemd-Service/Path wie beispielsweise hier beschrieben. Innerhalb von Dockercontainern ist das jedoch nur bedingt geeignet, da Docker, wenn ich das richtig verstanden habe, standardmäßig gar keinen Init Dienst verwendet.
Daher habe ich mich für incron entschieden, welches ebenfalls Ordner überwachen kann und für meinen Geschmack einfacher zu bedienen ist, als beispielsweise eine eigene Überwachung via inotify.

Shellskript mergepdf.sh

Da incron bei einer neu erstellten Datei ein Shellscript aufruft muss dieses das zusammenfügen übernehmen und den entsprechenden pdftk Befehl ausführen. Damit das Ganze automatisch funktioniert, habe ich die Konvention eingeführt, dass

  • Dateien die mit *_o.pdf enden die ungeraden (o als Abkürzung für odd) und
  • Dateien die mit *_e.pdf enden die geraden Seiten enthalten (e Abkürzung für even)

Das erspart Tipparbeit beim eingeben der Dateinamen und vereinfacht das Shellskript. Das fertige Skript sieht wie folgt aus:

Hauptproblem des Skriptes bestand darin, das incron schon mit dem verschieben der Dateien angefangen hat, bevor die Datei überhaupt fertig vom Scanner an den Netzwerkordner übertragen wurde. Das habe ich jetzt durch die stat Warteschleife und das großzügige einsetzen von sleep Befehlen "gelöst". Da lsof innerhalb von Docker nicht nutzbar ist. Falls hier jemand eine bessere Lösung parat hat, wäre ich über einen Kommentar äußerst dankbar.

Docker Image

Nun muss das ganze nur noch in ein Dockerimage verpackt werden. Hier gab es neben der Installation von incron und dem Einrichten des incron Jobs wenig zu tun. Incron meckert jedoch wenn man versucht incronjobs als root User anzulegen, daher habe ich die hier beschriebene Methode verwendet, um einen neuen User innerhalb des Images anzulegen, welcher dann den Incronjob ausführt.
Das fertige Dockerfile sieht wie folgt aus:

FROM ruudk/pdftk

MAINTAINER tuxflo

RUN apt-get update & apt-get install -y incron

ADD ./mergepdf.sh /opt/mergepdf.sh  
RUN chmod a+x /opt/mergepdf.sh

RUN adduser --disabled-password --gecos '' r && adduser r sudo && echo '%sudo ALL=(ALL) NOPASSWD:ALL' >> /etc/sudoers  
RUN echo r >> /etc/incron.allow  
USER r

RUN cd /home/r && incrontab -l > mycron && echo '/srv/input IN_CREATE /opt/mergepdf.sh $#' >> mycron && incrontab mycron && rm mycron  
RUN sudo incrond  

Zum erstellen des Images sind dann nur folgende Schritte nötig:

  • mein Repo clonen via git clone https://github.com/tuxflo/docker-mergepdf.git
  • in das Verzeichnis wechseln cd docker-mergepdf
  • Docker Build via docker build . ausführen

Alternativ kann das ganze auch in einem Rutsch ausgeführt werden:

docker build -t tuxflo/mergepdf github.com/tuxflo/docker-mergepdf

Zum starten des eben erstellten Images führt man:

docker run -v /home/user/input:/srv/input -v /home/user/output/:/srv/output -d --name mergepdf <ContainerID>

aus. Nun wartet der Daemnon bis in den übergebenen Ordner /home/user/input eine PDF erstellt wird.

QNAP Container Station

In meinem konkreten Fall soll der Container die zusammengefügten PDF-Dateien direkt in den scaninput Ordner meiner ecoDMS Instanz verschieben. Dafür muss der Container über die Container Station des QNAP NAS hinzugefügt werden. Dazu sollte es reichen wenn im Suchfeld nach mergepdf oder tuxflo gesucht wird. Anschließend einfach in den Advanced settings die gewünschten Verzeichnisse eingeben und schon sollte der Container starten.

Verbesserungen

Neben der oben erwähnten sleep Methode um auf die Dateien zu warten, könnte ich mir auch noch vorstellen, dass der Container noch mehr Aufgaben übernehmen könnte.
Beispielsweise wären Bildoptimierungen der eigescannten Dokumente oder das Umwandeln von Bilddateien in pdf denkbar. Wenn jemand weitere Ideen hat bin ich über einen Kommentar oder ein github Issue dankbar.