🇬🇧 Read this article in English.
Vom Linux-Projekt zum nativen Port, eine Entwicklungschronik
Manchmal beginnt alles mit einer simplen Frage: Welche WAF kann ich auf FreeBSD produktiv einsetzen?
Die Antwort, die ich fand: Keine, die mir wirklich gefiel. Also habe ich das Problem selbst gelöst, und BunkerWeb in die FreeBSD Ports Collection gebracht. Dieser Artikel beschreibt den Weg dorthin: Architekturentscheidungen, Rückschläge, Lösungen und alles, was zwischen dem ersten make und dem finalen Commit lag.
1. Motivation: Warum BunkerWeb?
Auf der Suche nach einer modernen Open-Source-WAF für FreeBSD hatte ich eine klare Anforderungsliste:
- Reverse Proxy
- OWASP Core Rule Set mit ModSecurity
- Automatisches Let’s Encrypt
- Security Header
- Rate Limiting & Bot Protection
- Zentrale, übersichtliche Konfiguration, idealerweise mit Web-UI
BunkerWeb erfüllte all das, auf Linux. Für FreeBSD existierte kein offizieller Port. Die Entscheidung war schnell getroffen: Ich erstelle den Port selbst und pflege ihn langfristig.
2. Analyse des Upstream-Projekts: Was erwartet einen?
Bevor man einen Port erstellt, muss man verstehen, womit man es zu tun hat. Ich habe mir angeschaut:
BUILD.mdund Dockerfiles- Linux-Pakete und deren Inhalte
- Das Buildsystem
- Python-Komponenten und deren Abhängigkeiten
- Lua-Komponenten
- Die gesamte Verzeichnisstruktur
Das Ergebnis war ernüchternd, und gleichzeitig eine klare Aufgabenliste:
- Sehr Linux-lastig: viele Annahmen über das Dateisystem
- Fest kodierte Pfade wie
/usr/share/bunkerwebund/etc/bunkerwebüberall - Zahlreiche Python-Abhängigkeiten, von denen nicht alle als FreeBSD-Port existierten
- Mehrere eigenständige Dienste: OpenResty, Scheduler, API, UI, jeder mit eigenen Anforderungen
Klar war: Das wird kein schnelles Wochenendprojekt.
3. Die erste Architekturentscheidung: nginx oder OpenResty?
Mein erster Ansatz war naheliegend, BunkerWeb basiert auf NGINX, also baut man auf www/nginx auf.
Das funktionierte nicht.
BunkerWeb nutzt umfangreiche Lua-Komponenten für seine Sicherheitslogik. Standard-NGINX bringt diese Lua-Umgebung nicht mit. Man könnte Lua-Module einzeln nachziehen, aber das wäre eine fragile Konstruktion mit viel Pflegeaufwand.
Die Lösung: Wechsel auf OpenResty.
OpenResty ist im Grunde NGINX mit vollständig integrierter LuaJIT-Umgebung und allen nötigen lua-nginx-module-Erweiterungen. Damit bringt BunkerWeb seine gesamte Lua-Logik sauber zum Laufen, ohne Sonderlösungen. Die Entscheidung hat den Port langfristig wartbarer gemacht, auch wenn sie bedeutete, dass ich von vorn anfangen musste.
4. Fehlende Ports: Baustellen vor der eigentlichen Baustelle
Beim Portieren stellte sich schnell heraus: Nicht alle Abhängigkeiten existierten bereits im FreeBSD Ports Tree.
Bevor www/bunkerweb überhaupt fertig werden konnte, mussten weitere Ports erstellt oder angepasst werden, unter anderem:
- Anpassungen rund um nginx/OpenResty-Integration
- Diverse Python-Abhängigkeiten als eigene Ports
py-defusedcsv: aus dem BunkerWeb-Umfeld heraus entstanden, aber inzwischen eigenständig nützlich
Das ist eine typische Situation beim Portieren komplexer Software: Man trägt nebenbei zum Ports Tree bei, ohne dass das ursprünglich geplant war.
5. Der eigentliche Port: Was alles dazugehört
Ein vollständiger FreeBSD-Port ist mehr als ein Makefile. Für www/bunkerweb entstanden:
www/bunkerweb/ ├── Makefile # Build-Logik, Abhängigkeiten, Optionen ├── distinfo # Checksummen der Quelldateien ├── pkg-plist # Alle installierten Dateien ├── pkg-message # Post-Install-Hinweise └── files/ # rc.d-Skript, Patches (patch-*), Konfig-Templates
Jede dieser Dateien hat ihre eigene Geschichte.
6. Linux-Pfade: Das größte Dauerärgernis
BunkerWeb erwartete seine Dateien genau dort, wo Linux sie erwartet:
/usr/share/bunkerweb/ → Core, Plugins, UI-Dateien /etc/bunkerweb/ → Konfiguration /var/lib/bunkerweb/ → Runtime-Daten
Auf FreeBSD ist die Konvention eindeutig: Drittanbieter-Software gehört unter /usr/local/: also ${PREFIX} im Ports-Jargon. Praktisch jede Pfadreferenz im Code musste ersetzt werden:
- /usr/share/bunkerweb/core
+ ${PREFIX}/share/bunkerweb/core
- /etc/bunkerweb/variables.env
+ ${PREFIX}/etc/bunkerweb/variables.env
Das klingt mechanisch, war aber aufwendig, weil diese Pfade nicht nur in Konfigurationsdateien, sondern tief im Python-Code, in Lua-Skripten und in Startskripten steckten. Für jeden Fundort: Patch schreiben, testen, wiederholen.
7. Python: Mehrere Baustellen auf einmal
Die Python-Seite des Projekts war ein eigenes Kapitel:
Abhängigkeiten: Alle Runtime-Abhängigkeiten mussten als py3xx-*-Ports eingebunden werden, kein pip install im Port.
pkg_resources und setuptools: BunkerWeb nutzt intern pkg_resources zur Pfaderkennung. Auf einem sauber installierten FreeBSD-Port verhält sich das anders als in einem Linux-Virtualenv. Das erforderte Anpassungen.
Keine virtuelle Umgebung: In Docker-Deployments läuft BunkerWeb typischerweise in einem isolierten Container mit eigenem Python. Für einen nativen Port gilt: System-Python, Ports-Module, keine venv. Das erforderte sorgfältige Abstimmung der Abhängigkeiten.
Dateirechte: Einige durch den Build generierte Python-Dateien hatten kein Execute-Bit gesetzt. Scheduler und Konfigurations-Generator liefen deshalb nicht, ein Fehler, der sich erst zur Laufzeit zeigte, nicht beim Build.
8. rc.d statt systemd
BunkerWeb kommt mit systemd-Units für Linux:
bunkerweb.service bunkerweb-scheduler.service bunkerweb-ui.service
FreeBSD kennt kein systemd. Stattdessen: rc(8). Für jeden Dienst entstand ein eigenes rc.d-Skript, das die FreeBSD-Konventionen für PID-Dateien, Runtime-Verzeichnisse und Logging berücksichtigt:
#!/bin/sh
# PROVIDE: bunkerweb
# REQUIRE: NETWORKING DAEMON
# KEYWORD: shutdown
. /etc/rc.subr
name="bunkerweb"
rcvar="bunkerweb_enable"
pidfile="/var/run/${name}/${name}.pid"
logfile="/var/log/${name}/${name}.log"
command="/usr/local/bin/bunkerweb"
command_args="--pid ${pidfile}"
load_rc_config $name
run_rc_command "$1"
Aktiviert werden die Dienste dann mit sysrc:
sysrc bunkerweb_enable="YES" sysrc bunkerweb_scheduler_enable="YES" sysrc bunkerweb_ui_enable="YES" # optional
9. OpenResty: Mehr als nur NGINX umbenennen
Der Wechsel auf OpenResty war die richtige Architekturentscheidung, aber auch OpenResty hatte seine eigenen Tücken:
nginx.confund Includes: BunkerWeb generiert seine NGINX-Konfiguration dynamisch. Die generierten Pfade für Includes, Log-Dateien und den Socket mussten auf FreeBSD-Konventionen angepasst werden.- HTTP/2: Erforderte spezifische OpenResty-Kompilierungsoptionen.
- Prefix und Startparameter: OpenResty muss mit dem korrekten
--prefixgestartet werden, damit es seine Konfiguration und Module findet.
10. Die Chronik der Probleme
Hier eine ehrliche Übersicht der wichtigsten Hürden, von Anfang bis Ende:
| Problem | Ursache | Lösung |
|---|---|---|
| Architektur unklar | Upstream nur auf Linux ausgelegt | FreeBSD-Port-Konzept erarbeitet |
| nginx reicht nicht | Fehlende Lua-Integration | Wechsel auf OpenResty |
| Fehlende Ports | Abhängigkeiten nicht im Ports Tree | Neue Ports erstellt und committed |
| Runtime-Pfade falsch | Linux-Pfade fest kodiert | ${PREFIX}-Patches für alle Komponenten |
| Scheduler startet nicht | Fehlende Execute-Bits auf generierten Dateien | Rechte im Port-Install korrigiert |
| API startet nicht | Port 8888 bereits durch OpenResty belegt | Konfiguration angepasst |
| Logs/PIDs fehlen | Verzeichnisse wurden nicht angelegt | Im rc.d-Skript automatisch erstellt |
Der API-Port-Konflikt war dabei besonders tricky: Es sah aus wie ein Absturz der API, war aber keiner. OpenResty hatte USE_API=yes und API_HTTP_PORT=8888 gesetzt und belegte damit denselben Port, den die BunkerWeb-API ebenfalls verwenden wollte. Kein Crash, sondern eine Konfigurationsfrage.
11. Tests: Kein Merge ohne frische Jail
Ein erfolgreicher Build ist noch kein funktionierender Port. Das hat sich durch das gesamte Projekt gezogen.
Der Testprozess war entsprechend gründlich:
# Iterativer Build-Zyklus make clean make extract make patch make stage make package # Validierung poudriere testport www/bunkerweb
Entscheidend waren vor allem Tests in frischen Jails: nur dort zeigt sich, ob wirklich alle Abhängigkeiten deklariert sind und der Port ohne vorhandene Reste aus früheren Installationen funktioniert. Runtime-Tests (Scheduler läuft? API erreichbar? UI zeigt sich?) kamen immer als letzter Schritt.
12. Installation: So nutzt ihr den Port
Via pkg (empfohlen)
pkg install bunkerweb
Aus dem Ports Tree
cd /usr/ports/www/bunkerweb make install clean
Dienste aktivieren und starten
sysrc bunkerweb_enable="YES" sysrc bunkerweb_scheduler_enable="YES" service bunkerweb start service bunkerweb-scheduler start
Erste Konfiguration
Die zentrale Konfiguration liegt in /usr/local/etc/bunkerweb/variables.env:
SERVER_NAME=www.example.com USE_REVERSE_PROXY=yes REVERSE_PROXY_URL=/ REVERSE_PROXY_HOST=http://127.0.0.1:3000 USE_LETS_ENCRYPT=yes EMAIL_LETS_ENCRYPT=admin@example.com
Web-UI aufrufen
Das UI ist standardmäßig unter http://127.0.0.1:7000 erreichbar. Für Remote-Zugriff empfiehlt sich ein SSH-Tunnel:
ssh -L 7000:127.0.0.1:7000 user@mein-server
13. Lessons Learned
Das ist das Kapitel, das ich jedem empfehle, der selbst einen komplexen Port angehen will:
Nicht jedes Linux-Projekt lässt sich 1:1 portieren. BunkerWeb trägt Linux als Grundannahme tief im Code. Das ist kein Vorwurf ans Upstream-Projekt, aber man muss es verstehen, bevor man anfängt.
Architekturentscheidungen dürfen sich ändern. Der Wechsel von nginx zu OpenResty kam nicht am ersten Tag. Manchmal braucht man Zeit und Scheitern, um die richtige Lösung zu erkennen. Das ist kein Rückschritt.
Ein erfolgreicher Build bedeutet noch keinen funktionierenden Port. Die härtesten Probleme, falsche Pfade, fehlende Rechte, Port-Konflikte, zeigen sich erst zur Laufzeit. Build und Runtime sind zwei verschiedene Welten.
Poudriere und frische Jails sind unverzichtbar. Kein Port sollte ohne Tests in einer sauberen Umgebung in den Tree. Punkt.
Fazit
Monate Arbeit, einige Architekturwechsel, dutzende Patches und ein paar neue Ports später: BunkerWeb ist jetzt ein nativer FreeBSD-Port. Kein Docker-Wrapper, kein Linux-Compatibility-Layer, sondern www/bunkerweb, sauber integriert ins Ökosystem.
Der Port ist frisch. Feedback ist ausdrücklich willkommen, ob Bug Reports, Erfolgsberichte oder Verbesserungsvorschläge. Am besten direkt im FreeBSD-Forum-Thread oder über bugs.freebsd.org.
Wer meine Arbeit an diesem und über 40 weiteren Ports unterstützen möchte, findet in der Seitenleiste die Möglichkeit dazu.
Habt ihr BunkerWeb auf FreeBSD bereits getestet? Schreibt es in die Kommentare!