Der DAMPF-Stack

LAMP auf Debian (mit FastCGI)

Wer mit Linux unterwegs ist und sich schon etwas mit Web-Entwicklung befasst hat, dem dürfte der LAMP-Stack bestehend aus Linux, Apache, MySQL und PHP bereits begegnet sein. Wer mit Debian GNU/Linux arbeitet, kann das L mit einem D konkretisieren. Und wer PHP via FastCGI statt mit Apaches mod_php einbindet, dem dürfte PHP-FPM ein Begriff sein, wodurch das P zu PF ergänzt wird. So soll hier die Rede vom DAMPF-Stack sein: Debian, Apache, MariaDB (anstelle von MySQL) und PHP-FPM.

In diesem Beitrag wird erklärt, wie dieser Stack aufgesetzt und anhand einer minimalen Beispielanwendung in Betrieb genommen wird.

D wie Debian

Die Ausgangslage ist eine Grundinstallation von Debian 12 Bookworm. Grundlegende Kenntnisse von systemd, HTML, SQL und PHP sind dem Verständnis dienlich; die einzelnen Schritte sollten aber auch ohne dieses Vorwissen nachvollziehbar sein.

Als Konvention wird Befehlen, welche Superuser-Berechtigungen erfordern, das Rautezeichen # vorangestellt. (Diese Befehle lassen sich per sudo ausführen.) Befehlen, die man auch ohne Superuser-Berechtigungen ausführen kann, wird das Dollarzeichen $ vorangestellt.

A wie Apache

Zunächst soll der Apache-Webserver installiert werden:

# apt install -y apache2

Dieser sollte nach der Installation bereits gestartet sein:

$ systemctl is-active apache2.service
active

Unsere DAMPF-Webseite soll in einem neuen Verzeichnis zu liegen kommen:

# mkdir /var/www/dampf

Zu Testzwecken soll eine statische HTML-Seite unter /var/www/dampf/index.html mit folgendem Inhalt angelegt werden:

<h1>Hallo DAMPF!</h1>

Eine minimale Virtualhost-Konfiguration unter /etc/apache2/sites-available/dampf.conf mit folgendem Inhalt wird ebenfalls benötigt:

<VirtualHost *:80>
    DocumentRoot /var/www/dampf
    ServerName localhost
</VirtualHost>

Die Seite wird aktiviert, indem man einen symbolischen Link innerhalb von /etc/apache2/sites-available zur jeweiligen Konfiguration unter /etc/apache2/sites-enabled erstellt. Dies lässt sich man manuell oder komfortabler mit dem Befehl a2ensite bewerkstelligen:

# a2ensite dampf.conf

Die Standardseite von Debian 000-default.conf soll hingegen deaktiviert werden:

# a2dissite 000-default.conf

Via systemd soll der Apache-Service zum Neuladen der Konfiguration veranlasst werden:

# systemctl reload apache2.service

Anschliessend sollte die Seite unter localhost verfügbar sein.

M wie MariaDB

Weiter geht es mit der Installation von MariaDB, welches ein (für unsere Zwecke vollständig kompatibler) Fork von MySQL ist:

# apt install -y mariadb-server

Für ein produktives Setup lohnt sich das Härten der Datenbankinstallation:

# mariadb-secure-installation

Da je nach Umgebung und Bedürfnissen andere Optionen sinnvoll sind, soll hier nicht weiter auf diese eingegangen werden. Stattdessen sollen ein neuer Benutzer namens dampfer und eine neue Datenbank namens dampf angelegt werden:

# mariadb
> CREATE USER dampfer@localhost IDENTIFIED BY 'topsecret';
> CREATE DATABASE dampf CHARACTER SET 'utf8' COLLATE 'utf8_general_ci';
> GRANT ALL PRIVILEGES ON dampf.* TO dampfer@localhost;
> FLUSH PRIVILEGES;
> EXIT

Die Befehle machen folgendes:

  1. Es wird ein neuer Benutzer namens dampf mit dem Passwort topsecret erstellt. (Mit @localhost wird angegeben, dass der Benutzer nicht von einem anderen System aus auf die Datenbank zugreifen kann.)
  2. Eine neue Datenbank namens dampf mit UTF-8-Kodierung ‒ nein: UTF-8 ist kein character set sondern ein encoding; Unicode wäre ein character set ‒ und entsprechender Collation (zwecks Sortierung) wird erstellt.
  3. Der Benutzer dampfer erhält sämtliche Rechte für die Datenbank dampf.
  4. Die alten Berechtigunge werden “gespült” (und anschliessend neu geladen).
  5. Die MariaDB-Konsole wird verlassen.

Damit es etwas zum DAMPFen gibt, soll eine entsprechende Datenbank angelegt werden. Das Skript hierzu wird in der Datei dampf.sql angelegt:

CREATE TABLE dampfware (
    id INTEGER AUTO_INCREMENT PRIMARY KEY,
    substanz VARCHAR(255) UNIQUE NOT NULL,
    geruch VARCHAR(255) NULL,
    stinkend TINYINT(1) NOT NULL DEFAULT 0,
    toxisch TINYINT(1) NOT NULL DEFAULT 0
);
INSERT INTO dampfware (substanz, geruch, stinkend, toxisch)
    VALUES ("faule Eier", "Schwefel", 1, 0);
INSERT INTO dampfware (substanz, geruch, stinkend, toxisch)
    VALUES ("Surströmming", "Verwesung", 1, 0);
INSERT INTO dampfware (substanz, geruch, stinkend, toxisch)
    VALUES ("Trifluormethylhypofluorit", NULL, 0, 1);
INSERT INTO dampfware (substanz, geruch, stinkend, toxisch)
    VALUES ("Limburger", "faules Gemüse", 1, 0);
INSERT INTO dampfware (substanz, geruch, stinkend, toxisch)
    VALUES ("Weihrauch", "Messdienergewand", 0, 0);
INSERT INTO dampfware (substanz, geruch, stinkend, toxisch)
    VALUES ("Buttersäure", "Mundgeruch", 1, 1);

Das Skript dampf.sql soll nun ausgeführt werden, wozu Datenbank, Benutzername und Passwort (letzteres interaktiv) eingegeben werden müssen. Das Skript wird via Standard Input eingelesen:

$ mariadb --database=dampf --user=dampfer --password <dampf.sql
Enter password: *********

Zur Kontrolle sollen die Daten noch (tabellarisch: -t) ausgegeben werden; zwecks Platzwersparnis mit den kurzen Varianten der Flags:

$ echo 'SELECT * FROM dampfware;' | mariadb -D dampf -u dampfer -p -t
Enter password: *********
+----+---------------------------+------------------+----------+---------+
| id | substanz                  | geruch           | stinkend | toxisch |
+----+---------------------------+------------------+----------+---------+
|  1 | faule Eier                | Schwefel         |        1 |       0 |
|  2 | Surströmming              | Verwesung        |        1 |       0 |
|  3 | Trifluormethylhypofluorit | NULL             |        0 |       1 |
|  4 | Limburger                 | faules Gemüse    |        1 |       0 |
|  5 | Weihrauch                 | Messdienergewand |        0 |       0 |
|  6 | Buttersäure               | Mundgeruch       |        1 |       1 |
+----+---------------------------+------------------+----------+---------+

P wie PHP

Um die Dampfwaren aus der Datenbank auf eine Webseite zu bringen wird PHP benötigt, welches in Version 8.2 installiert werden soll:

# apt install -y php8.2

Der Interpeter soll sogleich mit einem Einzeiler getestet werden:

$ php -r 'echo("PHP Version " . phpversion() . " läuft.\n");'
PHP Version 8.2.7 läuft

Da PHP offenbar funktionstüchtig ist, soll die statische HTML-Seite durch eine dynamische PHP-Seite mit folgendem Inhalt ersetzt werden (/var/www/dampf/index.php):

<?php
    echo("<h1>Hallo DAMPF!</h1>");
    echo("<p>" . "PHP Version " . phpversion() . " läuft.</p>");
?>

Die alte HTML-Seite kann entfernt werden:

# rm /var/www/dampf/index.html

Unter localhost sollte nun die von PHP dynamisch gerenderte Seite aufgerufen werden.

Um mehr über die Umgebung zu erfahren, soll zusätzlich die Datei /var/www/dampf/phpinfo.php mit folgendem Inhalt angelegt werden:

<?php
    phpinfo();
?>

Unter localhost/phpinfo.php erfährt man nun u.a. folgendes:

Server API: Apache 2.0 Handler

Das bedeutet, dass PHP von Apache mit mod_php ausgeführt wird. Ein DAMPF-Stack ist das noch nicht, es fehlt das F wie FPM.

F wie FPM

FPM steht für FastCGI Process Manager und ist eine Methode um PHP-Code auszuführen, die gegenüber mod_php bzw. herkömmlichen CGI einige Vorteile bietet:

Um PHP-FPM verwenden zu können, müssen weitere Pakete installiert werden:

# apt install -y php8.2-fpm libapache2-mod-fcgid

Das Paket php8.2-fpm ist der Dienst, welcher die PHP-Prozesse verwaltet. Das Apache-Modul libapache2-mod-fcgid delegiert die Anfragen, welche die Ausführung von PHP erfordern, an PHP-FPM weiter.

Das Apache-Modul mod_php muss deaktiviert; das Proxy-Modul für FastCGI muss aktiviert werden:

# a2dismod php8.2
# a2enmod proxy_fcgi

Weiter muss die Konfiguration von PHP-FPM aktiviert werden:

# a2enconf php8.2-fpm

Entsprechende symbolische Links werden in /etc/apache2/mods-enabled und in /etc/apache2/conf-enabled erstellt bzw. entfernt.

Damit die Änderungen wirksam werden, muss die Konfiguration von Apache neu geladen werden:

# systemctl reload apache2

Beim nächsten Aufruf von localhost/phpinfo.php sollte nun folgende Angabe erscheinen:

Server API: FPM/FastCGI

Damit wäre der DAMPF-Stack betriebsbereit!

Full Stack DAMPF

Da nun der ganze Stack lauffähig ist, sollen auch alle Komponenten davon eingesetzt werden.

Eine Datenbankverbindung lässt sich mit PHP Data Objects (PDO) erstellen. Die Verbindung soll dann sogleich verwendet werden um die ganzen DAMPF-Waren als HTML-Seite auszugeben (/var/www/dampf/dampfwaren.php):

<ol>
    <p>DAMPF-Waren:</p>
    <?php
        try {
            $db = new PDO("mysql:host=localhost;dbname=dampf", "dampfer", "topsecret");
            foreach ($db->query("SELECT * FROM dampfware;") as $dw) {
                echo("<li value=\"{$dw['id']}\">");
                echo("{$dw['substanz']}: {$dw['geruch']}");
                echo(" (stinkend: {$dw['stinkend']}, toxisch: {$dw['toxisch']})");
                echo("</li>");
            }
        } catch (PDOException $e) {
            die($e);
        }
    ?>
</ol>

Der Datenbanktreiber für MySQL (bzw. für MariaDB) muss noch über das entsprechende Paket installiert werden:

# apt install php-mysql

Anschliessend sollte ein Aufruf von localhost/dampfwaren.php nun folgende Ausgabe erzeugen:

DAMPF-Waren:

1. faule Eier: Schwefel (stinkend: 1, toxisch: 0)
2. Surstömming: Verwesung (stinkend: 1, toxisch: 0)
3. Trifluormethylhypofluorit: (stinkend: 0, toxisch: 1)
4. Limburger: faules Gemüse (stinkend: 1, toxisch: 0)
5. Weihrauch: Messdienergewand (stinkend: 0, toxisch: 0)
6. Buttersäure: Mundgeruch (stinkend: 1, toxisch: 1)

Übungen

Wer den hier beschriebenen DAMPF-Stack mit dieser Anleitung zum Laufen gebracht hat, der hat schon einiges geleistet. Wer noch nicht genug geDAMPFt hat, kann sich an folgenden Übungen versuchen:

  1. Die Datenbank-Verbindungsdaten sind in dampfwaren.php hartkodiert. Dies ist einerseits unflexibel, andererseits sicherheitstechnisch nicht optimal, da Quellcode schnell unwiderruflich in einem Versionierungssystem mit Änderungshistorie landet. Besser wäre es beispielsweise, die Verbindungsdaten in Umgebungsvariablen auszulagern. Hierzu nur einige wenige Stichworte:
    • Die Datei /etc/php/8.2/fpm/pool.d/www.conf mit den Direktiven env (Array) und clear_env (yes/no).
    • Das assoziative Array $_SERVER von PHP.
  2. Die Ausgabe der DAMPF-Waren als Liste weist noch einige Mängel optischer Natur auf, die verbessert werden könnten:
    • Die Darstellung von geruchlosen Einträgen (Trifluormethylhypofluorit) ist nicht optimal, da auf den Doppelpunkt keine Geruchsbezeichnung folgt, sondern direkt die geklammerten Eigenschaften aufgelistet werden.
    • Die TINYINT(1)-Felder “stinkend” und “toxisch” könnten besser dargestellt werden.
    • Eine Tabelle mit Spaltennamen wäre wohl übersichtlicher als eine Aufzählung.