CloudPanel Image Optimizer: WebP für WordPress & Apps

1. Einleitung

Bilder sind oft der grösste Speicherfresser auf Webservern und verlangsamen die Ladezeiten. Während es viele WordPress-Plugins gibt, arbeiten diese oft ressourcenintensiv oder kosten Geld. Für Server-Administratoren (speziell unter CloudPanel) ist ein serverseitiges Bash-Skript oft die effizientere Lösung.

Dieses Tutorial stellt ein umfassendes «Universal Image Optimizer»-Skript vor. Es konvertiert Bilder vollautomatisch in das WebP-Format, aktualisiert Datenbankpfade und bereinigt den Speicherplatz – alles über ein interaktives Terminal-Menü.

  • Zielgruppe: Server-Admins, Entwickler, CloudPanel-Nutzer
  • Voraussetzungen: Root-Zugriff, ImageMagick, WP-CLI
  • Funktionen: WordPress, Statische Galerien, NodeJS-Apps

[BILD 1: Schematische Darstellung des Skript-Ablaufs: Input JPG -> Konverter -> Output WebP + DB Update]


2. Überblick

Am Ende dieses Tutorials haben Sie ein einsatzbereites Bash-Tool auf Ihrem Server installiert, das folgende Aufgaben übernimmt:

  • Massenaustausch von JPG/PNG gegen WebP (Qualität 82).
  • Automatisches Suchen & Ersetzen in der WordPress-Datenbank.
  • Erstellung von Thumbnails für statische Galerien.
  • Pfadanpassung in Custom-Apps (via .env und MySQL).

3. Schritt-für-Schritt Anleitung

Schritt 1: Abhängigkeiten installieren

Damit das Skript funktioniert, müssen ImageMagick (für die Bildbearbeitung) und ein MySQL-Client installiert sein. Auf den meisten CloudPanel-Instanzen ist dies Standard, zur Sicherheit prüfen wir es:

apt update
apt install imagemagick mariadb-client -y

Schritt 2: Das Skript erstellen

Erstellen Sie eine neue Datei auf Ihrem Server, idealerweise im Root-Verzeichnis.

Datei: /root/optimize_cloudpanel.sh
Aktion: Neu erstellen

#!/bin/bash
# ---------------------------------------------------------
# UNIVERSAL IMAGE OPTIMIZER (CloudPanel / Server)
# Version 9: WordPress + Galerie + NodeJS 
# ---------------------------------------------------------
set -e

# --- KONFIGURATION ALLGEMEIN ---
WP_BIN="/usr/bin/wp"
QUALITY=82

# --- KONFIGURATION GALERIE (Fester Pfad) ---
GALLERY_PATH="/home/web1/htdocs/web1/wp-content/uploads/galerien"
GALLERY_MAX_EDGE=1600
THUMB_SIZE="150x150"

# --- KONFIGURATION NODEJS (r) ---
NODE_APP_PATH="/home/web2/htdocs/web2"
NODE_UPLOAD_DIR="$NODE_APP_PATH/uploads"
NODE_ENV_FILE="$NODE_APP_PATH/.env"

# --- KONFIGURATION WORDPRESS DOMAINS ---
declare -A WP_LABELS
declare -A WP_PATHS

# --- DOMAIN 1 ---
WP_LABELS[1]="web3"
WP_PATHS[1]="/home/web3/htdocs/web3"

# --- DOMAIN 2 ---
WP_LABELS[2]="web4"
WP_PATHS[2]="/home/web4/htdocs/web4"

# --- Weitere Domains hier einfach anfügen ... ---


# ---------------------------------------------------------
# HILFSFUNKTIONEN
# ---------------------------------------------------------
show_header() {
    clear
    echo "================================================="
    echo "   SERVER BILD-OPTIMIERER (2-in-1 + NodeJS)"
    echo "================================================="
}

get_owner() {
    stat -c '%U:%G' "$1"
}

press_enter_to_continue() {
    echo ""
    echo "-------------------------------------------------"
    read -p "Drücke [ENTER], um zum Hauptmenü zurückzukehren..." DUMMY
}

# ---------------------------------------------------------
# FUNKTION 1: WORDPRESS MODUS
# ---------------------------------------------------------
run_wordpress() {
    while true; do
        show_header
        echo "--- WORDPRESS MODUS ---"
        echo "Wähle die Installation:"
        echo ""
        
        for ID in "${!WP_LABELS[@]}"; do
            echo " [$ID] ${WP_LABELS[$ID]}"
        done | sort -n

        echo " [0] Zurück zum Hauptmenü"
        echo ""
        read -p "Nummer: " SEL

        if [[ "$SEL" == "0" ]]; then return; fi

        WP_ROOT="${WP_PATHS[$SEL]}"
        if [ -z "$WP_ROOT" ]; then 
            echo ">> Ungültige Auswahl."; sleep 1; continue
        fi
        
        UPLOAD_DIR="$WP_ROOT/wp-content/uploads"
        if [ ! -d "$UPLOAD_DIR" ]; then
            echo ">> Fehler: Ordner nicht gefunden: $UPLOAD_DIR"; press_enter_to_continue; return
        fi

        OWNER=$(get_owner "$WP_ROOT")

        echo "-------------------------------------------------"
        echo "Webseite: ${WP_LABELS[$SEL]}"
        echo "Pfad:     $UPLOAD_DIR"
        echo "-------------------------------------------------"
        echo "Aktion:   Konvertierung zu WebP (1:1 Größe)."
        echo "          Originale werden GELÖSCHT."
        echo "          Datenbank wird aktualisiert."
        echo "-------------------------------------------------"
        read -p "Starten? (j/n): " C
        if [[ "$C" != "j" && "$C" != "J" ]]; then continue; fi

        echo ""
        echo ">>> Verarbeite Bilder..."
        
        find "$UPLOAD_DIR" -type f \( -iname "*.webp" -o -iname "*.webp" -o -iname "*.webp" \) | while read FILE; do
            DIR=$(dirname "$FILE")
            NAME="${FILE%.*}"
            WEBP="$NAME.webp"

            if [ ! -f "$WEBP" ]; then
                if nice -n 10 convert "$FILE" -quality $QUALITY -define webp:method=6 "$WEBP"; then
                    echo "NEU: $(basename "$FILE") -> WebP. Lösche Original."
                    rm "$FILE"
                fi
            else
                echo "CLEANUP: Lösche altes Original $(basename "$FILE")"
                rm "$FILE"
            fi
        done

        echo ">>> Korrigiere Rechte..."
        chown -R "$OWNER" "$UPLOAD_DIR"

        echo ">>> Datenbank Update (WP-CLI)..."
        read -p "DB jetzt aktualisieren? (j/n): " D
        if [[ "$D" == "j" || "$D" == "J" ]]; then
            cd "$WP_ROOT"
            $WP_BIN search-replace '.webp' '.webp' --all-tables --precise --allow-root
            $WP_BIN search-replace '.webp' '.webp' --all-tables --precise --allow-root
            $WP_BIN search-replace '.webp' '.webp' --all-tables --precise --allow-root
            $WP_BIN cache flush --allow-root
            echo ">> Fertig."
        else
            echo ">> Übersprungen."
        fi
        
        press_enter_to_continue
        return 
    done
}

# ---------------------------------------------------------
# FUNKTION 2: GALERIE MODUS
# ---------------------------------------------------------
run_gallery() {
    show_header
    echo "--- GALERIE MODUS ---"
    
    if [ ! -d "$GALLERY_PATH" ]; then
        echo "FEHLER: $GALLERY_PATH nicht gefunden."; press_enter_to_continue; return
    fi

    OWNER=$(get_owner "$GALLERY_PATH")
    echo "Ziel: $GALLERY_PATH"
    echo "User: $OWNER"
    echo "Aktion: Resize (${GALLERY_MAX_EDGE}px) + Thumbnails. Originale LÖSCHEN."
    read -p "Starten? (j/n): " C
    if [[ "$C" != "j" && "$C" != "J" ]]; then return; fi

    echo ">>> Suche Bilder..."
    find "$GALLERY_PATH" -type f \( -iname "*.webp" -o -iname "*.webp" -o -iname "*.webp" -o -iname "*.heic" \) \
        -not -path '*/Thumbnail/*' | while read FILE; do
        
        DIR=$(dirname "$FILE")
        FILENAME=$(basename "$FILE")
        NAME="${FILENAME%.*}"
        
        OUT_MAIN="$DIR/$NAME.webp"
        THUMB_DIR="$DIR/Thumbnail"
        OUT_THUMB="$THUMB_DIR/$NAME.webp"
        mkdir -p "$THUMB_DIR"

        if [ ! -f "$OUT_MAIN" ]; then
            nice -n 10 convert "$FILE" -resize "${GALLERY_MAX_EDGE}x${GALLERY_MAX_EDGE}>" \
                -quality $QUALITY -define webp:method=6 "$OUT_MAIN"
            nice -n 10 convert "$OUT_MAIN" -thumbnail "${THUMB_SIZE}^" -gravity center -extent "$THUMB_SIZE" \
                -quality $QUALITY "$OUT_THUMB"
            echo "Galerie: $FILENAME verarbeitet. Lösche Original."
            rm "$FILE"
        else
            echo "Überspringe (existiert schon): $FILENAME"
        fi
    done

    echo ">>> Korrigiere Rechte..."
    chown -R "$OWNER" "$GALLERY_PATH"
    echo "Fertig."
    press_enter_to_continue
}

# ---------------------------------------------------------
# FUNKTION 3: NODEJS MODUS (ictlager)
# ---------------------------------------------------------
run_nodejs_ict() {
    show_header
    echo "--- NODEJS MODUS (ictlager) ---"
    
    if [ ! -f "$NODE_ENV_FILE" ]; then
        echo "FEHLER: .env Datei nicht gefunden in:"
        echo "$NODE_ENV_FILE"
        press_enter_to_continue; return
    fi

    OWNER=$(get_owner "$NODE_UPLOAD_DIR")
    echo "App Pfad: $NODE_APP_PATH"
    echo "Uploads:  $NODE_UPLOAD_DIR"
    echo "User:     $OWNER"
    echo "-------------------------------------------------"
    echo "ACHTUNG: Dies ist keine WordPress-Installation!"
    echo "Das Script liest die MySQL-Zugangsdaten aus der .env-Datei,"
    echo "um in der Tabelle 'Product' die Pfade (.webp -> .webp) zu ändern."
    echo "-------------------------------------------------"
    read -p "Bist du sicher? (j/n): " C
    if [[ "$C" != "j" && "$C" != "J" ]]; then return; fi

    # 1. Credentials aus .env parsen
    DB_URL=$(grep "DATABASE_URL" "$NODE_ENV_FILE" | cut -d'=' -f2- | tr -d '"' | tr -d "'")
    
    if [[ $DB_URL =~ mysql://([^:]+):([^@]+)@([^:]+):([0-9]+)/(.+) ]]; then
        DB_USER="${BASH_REMATCH[1]}"
        DB_PASS="${BASH_REMATCH[2]}"
        DB_HOST="${BASH_REMATCH[3]}"
        DB_PORT="${BASH_REMATCH[4]}"
        DB_NAME="${BASH_REMATCH[5]}"
        DB_NAME=$(echo "$DB_NAME" | cut -d'?' -f1)
    else
        echo "FEHLER: Konnte DATABASE_URL nicht parsen."
        press_enter_to_continue; return
    fi

    echo ">> DB Credentials gefunden: $DB_USER @ $DB_NAME"

    # 2. Bilder konvertieren
    echo ">>> Konvertiere Bilder..."
    find "$NODE_UPLOAD_DIR" -type f \( -iname "*.webp" -o -iname "*.webp" -o -iname "*.webp" \) | while read FILE; do
        DIR=$(dirname "$FILE")
        NAME="${FILE%.*}"
        WEBP="$NAME.webp"

        if [ ! -f "$WEBP" ]; then
            if nice -n 10 convert "$FILE" -quality $QUALITY -define webp:method=6 "$WEBP"; then
                echo "NEU: $(basename "$FILE") -> WebP. Lösche Original."
                rm "$FILE"
            fi
        else
             echo "CLEANUP: Lösche altes Original $(basename "$FILE")"
             rm "$FILE"
        fi
    done

    # 3. Rechte
    echo ">>> Korrigiere Rechte..."
    chown -R "$OWNER" "$NODE_UPLOAD_DIR"

    # 4. SQL Update
    echo ">>> Aktualisiere Datenbank (Tabelle 'Product')..."
    
    SQL_CMD_JPG="UPDATE Product SET imagePath = REPLACE(imagePath, '.webp', '.webp') WHERE imagePath LIKE '%.webp';"
    SQL_CMD_JPEG="UPDATE Product SET imagePath = REPLACE(imagePath, '.webp', '.webp') WHERE imagePath LIKE '%.webp';"
    SQL_CMD_PNG="UPDATE Product SET imagePath = REPLACE(imagePath, '.webp', '.webp') WHERE imagePath LIKE '%.webp';"
    
    mysql -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" -P"$DB_PORT" "$DB_NAME" -e "$SQL_CMD_JPG"
    mysql -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" -P"$DB_PORT" "$DB_NAME" -e "$SQL_CMD_JPEG"
    mysql -u"$DB_USER" -p"$DB_PASS" -h"$DB_HOST" -P"$DB_PORT" "$DB_NAME" -e "$SQL_CMD_PNG"

    echo ">> Datenbank-Pfade aktualisiert."
    echo "Fertig."
    press_enter_to_continue
}

# ---------------------------------------------------------
# HAUPTMENÜ LOOP
# ---------------------------------------------------------
while true; do
    show_header
    echo "Was möchtest du tun?"
    echo "1) WordPress optimieren"
    echo "2) Fotogalerie optimieren"
    echo "3) NodeJS App optimieren (ictlager)"
    echo "4) Beenden"
    echo ""
    read -p "Auswahl [1-4]: " CHOICE

    case "$CHOICE" in
        1) run_wordpress ;;
        2) run_gallery ;;
        3) run_nodejs_ict ;;
        4) echo "Tschüss!"; exit 0 ;;
        *) echo "Ungültig."; sleep 1 ;;
    esac
done

Schritt 3: Konfiguration anpassen

Bevor Sie das Skript starten, müssen Sie die Pfade zu Ihren Webseiten im oberen Bereich des Skripts (unter --- KONFIGURATION ---) anpassen:

      • WordPress: Passen Sie die Arrays WP_LABELS (Name) und WP_PATHS (Systempfad) an.
      • Galerie: Setzen Sie GALLERY_PATH auf Ihren statischen Bilderordner.
      • NodeJS: Falls verwendet, prüfen Sie NODE_APP_PATH und den Tabellennamen im SQL-Abschnitt (standardmässig ‚Product‘).

Schritt 4: Ausführbar machen und Starten

Geben Sie der Datei die nötigen Ausführungsrechte und starten Sie das Menü:

chmod +x /root/optimize_cloudpanel.sh
./optimize_cloudpanel.sh

[BILD 2: Screenshot des interaktiven Terminal-Menüs bei der Ausführung]


4. Wichtige Hinweise & Stolperfallen

Backup Pflicht: Dieses Skript arbeitet destruktiv und löscht Originaldateien, um Speicherplatz zu sparen. Erstellen Sie vor der Ausführung zwingend ein Backup der Webseite oder einen Server-Snapshot.

WordPress Update: Bestätigen Sie die Frage nach dem Datenbank-Update immer mit «J». Andernfalls sind die Bilder physisch WebP, aber WordPress sucht weiterhin nach JPG, was zu fehlerhaften Bildern auf der Webseite führt.


5. Ergebnis & Fazit

Mit diesem Skript haben Sie eine leistungsfähige Lösung, um Ihre Server-Assets zu modernisieren. Durch die Konvertierung zu WebP sparen Sie Speicherplatz und beschleunigen die Auslieferung der Webseiten signifikant. Die Integration von WordPress, statischen Galerien und Custom-Apps in ein einziges Menü erleichtert die Wartung enorm.

Ein logischer nächster Schritt wäre die Einrichtung eines Cronjobs, falls Sie diese Aufgaben regelmässig automatisiert ausführen möchten.