8.5    Beispiel: wget-Script plus tmpfs-Konfiguration

In diesem Abschnitt präsentieren wir Ihnen ein etwas längeres Beispiel aus unserer beruflichen Praxis, das Script-Programmierung und Systemadministration miteinander kombiniert.

Ein auf einem Linux-Server eingesetztes Tool sollte aus einer HTML-Datei ein PDF-Dokument machen. Allerdings war die recht alte Version des Tools inkompatibel mit modernen HTTPS-Verschlüsselungsprotokollen. Aus diversen Gründen kam ein Update auf eine neue Version des Tools nicht infrage.

So entstand die folgende Idee: Es müsste doch möglich sein, mit wget die HTML-Datei samt allen dort referenzierten Zusatzdateien (CSS, Bilder usw.) in ein temporäres Verzeichnis herunterzuladen. Die lokalen Dateien könnten dann ohne Netzwerkprobleme in das PDF-Format umgewandelt werden.

wget

wget ist ein unter Linux gebräuchliches Kommando, um eine Datei per FTP oder HTTP herunterzuladen. wget verfügt darüber hinaus über unzählige Spezialfunktionen, die durch Optionen gesteuert werden. Unter anderem kann das Kommando ganze Websites herunterladen. Dabei passt es die Links innerhalb der HTML-Seiten so an, dass die gespiegelte Website lokal im Webbrowser betrachtet werden kann. Die Kunst beim Einsatz von wget besteht darin, die richtige Kombination von Optionen zu übergeben.

wget-Script entwickeln

Das Ziel bestand also darin, ein Bash-Script zu entwickeln, das alle für das Tool gedachten Parameter entgegennimmt, ein temporäres Verzeichnis erzeugt, die im ersten Parameter übergebene HTML-Datei herunterlädt, den HTML-PDF-Konverter aufruft und schließlich aufräumt. Die Entwicklung des Scripts erfolgte in VS Code, wobei im Editor eine SSH-Verbindung zum Server bestand.

Prompt: I want to write a small Bash script which I pass an URL (first parameter) and some options. The script creates a temporary directory, downloads the URL plus all referenced files (wget relink), calls another command to process these local files and finally deletes the temporary directory. Can you help? (Short, concise code preferred.)

Certainly! Below is a concise Bash script to achieve what you described:

#!/bin/bash
URL=$1
shift # shift the arguments to remove the URL
# Create a temporary directory
TMPDIR=$(mktemp -d)
# Ensure the temporary directory is deleted on script exit
trap 'rm -rf "$TMPDIR"' EXIT
# Download the URL and all referenced files into the
# temporary directory
wget --mirror --convert-links --adjust-extension \
--page-requisites --no-parent -P "$TMPDIR" "$URL"
# Call another command to process these local files
your_command "$TMPDIR" "$@"

Explanation

  • Trap: Ensures that the temporary directory is deleted when the script exits, regardless of success or failure.

  • Wget Command: Uses wget to download the URL and all referenced files into the temporary directory. The options used are:

    • --mirror: Enables options suitable for mirroring.

    • --convert-links: Converts the links to make them suitable for offline viewing.

    • --adjust-extension: Adds the appropriate extensions to files.

    • --page-requisites: Downloads all necessary files for displaying the page.

    • --no-parent: Prevents wget from following links outside the given URL.

    • -P "$TMPDIR": Specifies the directory to save the files.

  • Processing Command: Replace your_command with the actual command you want to run on the downloaded files. The script passes the temporary directory and any additional options to this command.

Die erste Version des Scripts sieht schon recht vielversprechend aus. Die Erläuterungen zum Code haben wir gekürzt, damit das Beispiel übersichtlich bleibt. Zum Ausprobieren haben wir den Vorschlag nahezu unverändert übernommen und lediglich your_command durch den absoluten Pfad zum HTML-PDF-Konverter ersetzt.

Beim Testen sind allerdings einige Probleme aufgetaucht, die wir zum Teil selbst und zum Teil mit KI-Unterstützung behoben haben. Die folgenden Prompts dokumentieren diesen iterativen Prozess. Auf die Wiedergabe der recht langatmigen Antworten haben wir verzichtet.

Prompt: I need to pass $URL to your_command. However, $URL needs to be modified to refer to the local start point generated by wget.

Prompt: $URL can contain all sort of special characters. Can I get wget to use index.html as a starting point to the local file tree?

ChatGPT findet keine geeignete Option (soweit wir wissen, gibt es auch keine) und schlägt vor, im temporären Verzeichnis mit find nach *.html-Dateien zu suchen und die erste gefundene Datei zu verwenden. Das erscheint uns aber fehleranfällig:

Prompt: Is it safe to simply take the first result? Perhaps there are other *.html files within in the directory. Would it make sense to extract the base name from $URL and use it as a search pattern with find?

ChatGPT liefert nun Code, der unseren Wünschen entspricht. Im weiteren Verlauf stellt sich aber heraus, dass wget die HTML-Datei zu umfassend verändert. wget --mirror lädt nicht nur die zur Darstellung der HTML-Seite erforderlichen Dateien herunter, sondern verarbeitet alle in der HTML-Seite enthaltenen Links. Das führt dazu, dass externe Links im später weitergegebenen PDF-Dokument nicht wie beabsichtigt funktionieren. Wir versuchen, noch einmal zu erklären, was wir eigentlich wollen:

Prompt: I want wget to locally download all files needed to correctly display the document (i.e. CSS + images) and to change these links within the document (one document only). But I do not want wget to change links to everything else. Links to external files not directly displayed within the document should stay as is. Can you help?

Aus der Antwort geht hervor, dass die ursprünglich von ChatGPT vorgeschlagene wget-Option --mirror ungeeignet war. Es ist müßig, darüber zu streiten, ob wir anfänglich die Aufgabenstellung zu wenig präzise beschrieben habe oder ob die Schuld bei ChatGPT zu suchen ist. Auf jeden Fall war ein relativ langer interaktiver Prozess erforderlich, bis das in Zusammenarbeit mit ChatGPT produzierte Script wie beabsichtigt funktionierte. Das finale Script sieht so aus:

#!/bin/bash
URL=$1
shift # shift the arguments to remove the URL
TMP_DIR=$(mktemp -d /tmp/html2pdf-tmp.XXXXXXXX)
trap 'rm -rf "$TMPDIR"' EXIT
cd "$TMP_DIR"
wget --page-requisites --convert-links --no-parent "$URL"
# if $URL contains a long query string, wget shortens the file
# name; therefore, find the newest file in the result list
# with the same base name; this should be reasonably safe
URL_NO_QUERY=${URL%%\?*}
BASE_NAME=$(basename "$URL_NO_QUERY")
MAIN_FILE=$(find . -type f -name "${BASE_NAME}*" -print0 | \
xargs -0 ls -t | head -n 1)
# run html2pdf
/usr/local/bin/html2pdf "$MAIN_FILE" "$@"

Linux-Server-Konfiguration mit tmpfs

Das Script läuft mittlerweile sehr robust auf einem Linux-Server. Um die Effizienz zu verbessern, haben wir den Server so konfiguriert, dass das /tmp-Verzeichnis als tmpfs-Dateisystem im Arbeitsspeicher abgebildet wird. Sie wissen nicht, wie das geht? Fragen Sie ein KI-Tool!

Prompt: On an Ubuntu server I want to save the contents of /tmp in memory to make I/O faster. /tmp must not use more then 4 GB RAM. Please show me a systemd compatible setup.

To create a temporary filesystem in memory for /tmp using tmpfs and limit it to 4 GB of RAM, you can configure a systemd unit for this purpose. Here’s how you can set it up:

  • First, create a new systemd unit file to mount /tmp as a tmpfs with a 4 GB size limit.

    sudo nano /etc/systemd/system/tmp.mount

Im Weiteren erläutert ChatGPT den Aufbau der Datei tmp.mount (12 Zeilen Code) sowie deren Aktivierung mit systemctl daemon-reload und systemctl start tmp.mount. Grundsätzlich ist das eine gute Lösung – aber es ginge noch besser: Bei modernen Linux-Distributionen existiert /lib/systemd/system/tmp.mount bereits. Es würde ausreichen, das vorhandene Set-up nur minimal zu ändern. Erst wenn Sie das KI-Tool auf die richtige Fährte bringen, beschreibt es diese Vorgehensweise:

Prompt: My systemd distribution already contains a pre-configured file tmp.mount. With the execption of the disk size, it’s perfect. Can’t I simply change the setup to use a maximum of GiB RAM?

Yes, if your system already includes a pre-configured tmp.mount unit file, you can simply modify the existing file to set the maximum size to 4 GB. Here are the steps:

  • Open the tmp.mount file for editing:

    sudo systemctl edit tmp.mount
  • In the override file, add the following content to set the size limit for the tmpfs mount:

    [Mount]
    Options=mode=1777,size=4G
  • Reload the systemd daemon to apply the changes:

    sudo systemctl daemon-reload
  • Restart the tmp.mount unit to apply the new size limit:

    sudo systemctl restart tmp.mount

Fazit

Dieses Beispiel war sehr nahe an unserem Administratoralltag. War ChatGPT nun eine Hilfe oder nicht?

Beim ersten Teil der Fragestellung haben ChatGPT bzw. GitHub Copilot auf Anhieb ein Grundgerüst für das Script erstellt. Dieser Teil der Aufgabe war zwar nicht schwierig, aber auf jeden Fall haben wir uns ein paar Minuten Tipparbeit erspart.

Das eigentliche Problem bestand darin, den richtigen Optionenmix für wget zu finden. Wir verwenden wget nur hin und wieder und kennen dessen Optionen nicht auswendig. ChatGPT hat uns zuerst in die Irre geführt, letztlich aber die richtigen Optionen geliefert. Die Alternative hätte darin bestanden, die Optionen im Handbuch zu suchen. Ganz einfach ist das auch nicht, weil man wget einen recht unübersichtlichen Text liefert, dessen Länge ca. 40 Seiten dieses Buchs entspricht. Schwer zu sagen, ob wir auf diesem Weg schneller zum Ziel gekommen wären.

Beim zweiten Teil der Fragestellung war die von ChatGPT vorgeschlagene Lösung grundsätzlich gut. Die zweite Variante mit systemctl edit ist bei modernen Distributionen aber vorzuziehen, weil dabei nur eine minimale Änderung am ohnedies vorgesehenen Set-up notwendig ist. Zu einer entsprechenden Anleitung gelangen Sie aber nur, wenn Sie das perfekte Set-up schon kennen und ChatGPT explizit darauf hinweisen. Nur: Unter dieser Voraussetzung brauchen Sie gar kein KI-Tool.

Wenn wir den ganzen Prozess mit einer Schulnote bewerten müssten: Die KI-Tools haben gut funktioniert, aber eben nicht sehr gut.