# Plugins BunkerWeb verfügt über ein Pluginsystem, das es ermöglicht, neue Funktionen einfach hinzuzufügen. Sobald ein Plugin installiert ist, können Sie es über zusätzliche, vom Plugin definierte Einstellungen verwalten. ## Offizielle Plugins Hier ist die Liste der "offiziellen" Plugins, die wir pflegen (weitere Informationen finden Sie im Repository [bunkerweb-plugins](https://github.com/bunkerity/bunkerweb-plugins)): | Name | Version | Beschreibung | Link | | :------------: | :-----: | :------------------------------------------------------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------: | | **ClamAV** | 1.10 | Scannt hochgeladene Dateien automatisch mit der ClamAV-Antiviren-Engine und lehnt die Anfrage ab, wenn eine Datei als bösartig erkannt wird. | [bunkerweb-plugins/clamav](https://github.com/bunkerity/bunkerweb-plugins/tree/main/clamav) | | **Coraza** | 1.10 | Überprüft Anfragen mit der Coraza WAF (Alternative zu ModSecurity). | [bunkerweb-plugins/coraza](https://github.com/bunkerity/bunkerweb-plugins/tree/main/coraza) | | **Discord** | 1.10 | Sendet Sicherheitsbenachrichtigungen über einen Webhook an einen Discord-Kanal. | [bunkerweb-plugins/discord](https://github.com/bunkerity/bunkerweb-plugins/tree/main/discord) | | **Slack** | 1.10 | Sendet Sicherheitsbenachrichtigungen über einen Webhook an einen Slack-Kanal. | [bunkerweb-plugins/slack](https://github.com/bunkerity/bunkerweb-plugins/tree/main/slack) | | **VirusTotal** | 1.10 | Scannt hochgeladene Dateien automatisch mit der VirusTotal-API und lehnt die Anfrage ab, wenn eine Datei als bösartig erkannt wird. | [bunkerweb-plugins/virustotal](https://github.com/bunkerity/bunkerweb-plugins/tree/main/virustotal) | | **WebHook** | 1.10 | Sendet Sicherheitsbenachrichtigungen über einen Webhook an einen benutzerdefinierten HTTP-Endpunkt. | [bunkerweb-plugins/webhook](https://github.com/bunkerity/bunkerweb-plugins/tree/main/webhook) | ## Wie man ein Plugin verwendet ### Automatisch Wenn Sie externe Plugins schnell installieren möchten, können Sie die Einstellung `EXTERNAL_PLUGIN_URLS` verwenden. Sie akzeptiert eine durch Leerzeichen getrennte Liste von URLs, die jeweils auf ein komprimiertes (zip-Format) Archiv mit einem oder mehreren Plugins verweisen. Sie können den folgenden Wert verwenden, wenn Sie die offiziellen Plugins automatisch installieren möchten: `EXTERNAL_PLUGIN_URLS=https://github.com/bunkerity/bunkerweb-plugins/archive/refs/tags/v1.10.zip` ### Manuell Der erste Schritt besteht darin, das Plugin zu installieren, indem Sie seine Dateien in den entsprechenden Datenordner `plugins` legen. Das Verfahren hängt von Ihrer Integration ab: === "Docker" Bei Verwendung der [Docker-Integration](integrations.md#docker) müssen Plugins in das Volume gemountet werden, das auf `/data/plugins` im Scheduler-Container verweist. Als Erstes müssen Sie den Plugins-Ordner erstellen: ```shell mkdir -p ./bw-data/plugins ``` Dann können Sie die Plugins Ihrer Wahl in diesen Ordner legen: ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* ./bw-data/plugins ``` ??? warning "Verwendung eines lokalen Ordners für persistente Daten" Der Scheduler läuft als **unprivilegierter Benutzer mit UID 101 und GID 101** im Container. Der Grund dafür ist die Sicherheit: Im Falle einer ausgenutzten Schwachstelle hat der Angreifer keine vollen Root-Rechte (UID/GID 0). Aber es gibt einen Nachteil: Wenn Sie einen **lokalen Ordner für die persistenten Daten** verwenden, müssen Sie **die richtigen Berechtigungen festlegen**, damit der unprivilegierte Benutzer Daten darin schreiben kann. Etwas wie das Folgende sollte ausreichen: ```shell mkdir bw-data && \ chown root:101 bw-data && \ chmod 770 bw-data ``` Alternativ, wenn der Ordner bereits existiert: ```shell chown -R root:101 bw-data && \ chmod -R 770 bw-data ``` Wenn Sie [Docker im rootless-Modus](https://docs.docker.com/engine/security/rootless) oder [podman](https://podman.io/) verwenden, werden UIDs und GIDs im Container auf andere auf dem Host abgebildet. Sie müssen zuerst Ihre anfängliche subuid und subgid überprüfen: ```shell grep ^$(whoami): /etc/subuid && \ grep ^$(whoami): /etc/subgid ``` Wenn Sie beispielsweise einen Wert von **100000** haben, ist die abgebildete UID/GID **100100** (100000 + 100): ```shell mkdir bw-data && \ sudo chgrp 100100 bw-data && \ chmod 770 bw-data ``` Oder wenn der Ordner bereits existiert: ```shell sudo chgrp -R 100100 bw-data && \ chmod -R 770 bw-data ``` Dann können Sie das Volume beim Starten Ihres Docker-Stacks mounten: ```yaml services: ... bw-scheduler: image: bunkerity/bunkerweb-scheduler:1.6.11-rc1 volumes: - ./bw-data:/data ... ``` === "Docker autoconf" Bei Verwendung der [Docker-Autoconf-Integration](integrations.md#docker-autoconf) müssen Plugins in das Volume gemountet werden, das auf `/data/plugins` im Scheduler-Container verweist. Als Erstes müssen Sie den Plugins-Ordner erstellen: ```shell mkdir -p ./bw-data/plugins ``` Dann können Sie die Plugins Ihrer Wahl in diesen Ordner legen: ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* ./bw-data/plugins ``` Da der Scheduler als unprivilegierter Benutzer mit UID und GID 101 läuft, müssen Sie die Berechtigungen bearbeiten: ```shell chown -R 101:101 ./bw-data ``` Dann können Sie das Volume beim Starten Ihres Docker-Stacks mounten: ```yaml services: ... bw-scheduler: image: bunkerity/bunkerweb-scheduler:1.6.11-rc1 volumes: - ./bw-data:/data ... ``` === "Swarm" !!! warning "Veraltet" Die Swarm-Integration ist veraltet und wird in einer zukünftigen Version entfernt. Bitte erwägen Sie stattdessen die Verwendung der [Kubernetes-Integration](integrations.md#kubernetes). **Weitere Informationen finden Sie in der [Swarm-Integrationsdokumentation](integrations.md#swarm).** Bei Verwendung der [Swarm-Integration](integrations.md#swarm) müssen Plugins in das Volume gemountet werden, das auf `/data/plugins` im Scheduler-Container verweist. !!! info "Swarm-Volume" Die Konfiguration eines Swarm-Volumes, das bestehen bleibt, wenn der Scheduler-Dienst auf verschiedenen Knoten ausgeführt wird, wird in dieser Dokumentation nicht behandelt. Wir gehen davon aus, dass Sie einen freigegebenen Ordner auf `/shared` auf allen Knoten gemountet haben. Als Erstes müssen Sie den Plugins-Ordner erstellen: ```shell mkdir -p /shared/bw-plugins ``` Dann können Sie die Plugins Ihrer Wahl in diesen Ordner legen: ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* /shared/bw-plugins ``` Da der Scheduler als unprivilegierter Benutzer mit UID und GID 101 läuft, müssen Sie die Berechtigungen bearbeiten: ```shell chown -R 101:101 /shared/bw-plugins ``` Dann können Sie das Volume beim Starten Ihres Swarm-Stacks mounten: ```yaml services: ... bw-scheduler: image: bunkerity/bunkerweb-scheduler:1.6.11-rc1 volumes: - /shared/bw-plugins:/data/plugins ... ``` === "Kubernetes" Bei Verwendung der [Kubernetes-Integration](integrations.md#kubernetes) müssen Plugins in das Volume gemountet werden, das auf `/data/plugins` im Scheduler-Container verweist. Als Erstes müssen Sie einen [PersistentVolumeClaim](https://kubernetes.io/docs/concepts/storage/persistent-volumes/) deklarieren, der unsere Plugin-Daten enthalten wird: ```yaml apiVersion: v1 kind: PersistentVolumeClaim metadata: name: pvc-bunkerweb-plugins spec: accessModes: - ReadWriteOnce resources: requests: storage: 5Gi ``` Sie können nun den Volume-Mount und einen Init-Container hinzufügen, um das Volume automatisch bereitzustellen: ```yaml apiVersion: apps/v1 kind: Deployment metadata: name: bunkerweb-scheduler spec: replicas: 1 strategy: type: Recreate selector: matchLabels: app: bunkerweb-scheduler template: metadata: labels: app: bunkerweb-scheduler spec: serviceAccountName: sa-bunkerweb containers: - name: bunkerweb-scheduler image: bunkerity/bunkerweb-scheduler:1.6.11-rc1 imagePullPolicy: Always env: - name: KUBERNETES_MODE value: "yes" - name: "DATABASE_URI" value: "mariadb+pymysql://bunkerweb:changeme@svc-bunkerweb-db:3306/db" volumeMounts: - mountPath: "/data/plugins" name: vol-plugins initContainers: - name: bunkerweb-scheduler-init image: alpine/git command: ["/bin/sh", "-c"] args: ["git clone https://github.com/bunkerity/bunkerweb-plugins /data/plugins && chown -R 101:101 /data/plugins"] volumeMounts: - mountPath: "/data/plugins" name: vol-plugins volumes: - name: vol-plugins persistentVolumeClaim: claimName: pvc-bunkerweb-plugins ``` === "Linux" Bei Verwendung der [Linux-Integration](integrations.md#linux) müssen Plugins in den Ordner `/etc/bunkerweb/plugins` geschrieben werden: ```shell git clone https://github.com/bunkerity/bunkerweb-plugins && \ cp -rp ./bunkerweb-plugins/* /etc/bunkerweb/plugins && \ chown -R nginx:nginx /etc/bunkerweb/plugins ``` ## Ein Plugin schreiben ### Struktur !!! tip "Bestehende Plugins" Wenn die Dokumentation nicht ausreicht, können Sie sich den bestehenden Quellcode der [offiziellen Plugins](https://github.com/bunkerity/bunkerweb-plugins) und der [Kern-Plugins](https://github.com/bunkerity/bunkerweb/tree/v1.6.11-rc1/src/common/core) ansehen (bereits in BunkerWeb enthalten, aber technisch gesehen sind es Plugins). Wie eine Plugin-Struktur aussieht: ``` plugin / confs / conf_type / conf_name.conf ui / actions.py hooks.py template.html blueprints / templates / jobs / my-job.py templates / my-template.json my-template / configs / conf_type / conf_name.conf plugin.lua plugin.json ``` - **conf_name.conf** : Fügen Sie [benutzerdefinierte NGINX-Konfigurationen](advanced.md#custom-configurations) (als Jinja2-Vorlagen) hinzu. - **actions.py** : Skript, das auf dem Flask-Server ausgeführt wird. Dieses Skript wird in einem Flask-Kontext ausgeführt und gibt Ihnen Zugriff auf Bibliotheken und Dienstprogramme wie `jinja2` und `requests`. - **hooks.py** : Benutzerdefinierte Python-Datei, die Flask-Hooks enthält und beim Laden des Plugins ausgeführt wird. - **template.html** : Benutzerdefinierte Plugin-Seite, die über die Benutzeroberfläche aufgerufen wird. - **blueprints-Ordner (innerhalb von ui)**: Dieser Ordner wird verwendet, um bestehende Flask-Blueprints zu überschreiben oder neue zu erstellen. Darin können Sie Blueprint-Dateien und einen optionalen **templates**-Unterordner für Blueprint-spezifische Vorlagen einschließen. - **jobs py-Datei** : Benutzerdefinierte Python-Dateien, die als Jobs vom Scheduler ausgeführt werden. - **my-template.json** : Fügen Sie [benutzerdefinierte Vorlagen](concepts.md#templates) hinzu, um die Standardwerte von Einstellungen zu überschreiben und benutzerdefinierte Konfigurationen einfach anzuwenden. - **plugin.lua** : Code, der auf NGINX mit dem [NGINX LUA-Modul](https://github.com/openresty/lua-nginx-module) ausgeführt wird. - **plugin.json** : Metadaten, Einstellungen und Jobdefinitionen für Ihr Plugin. ### Erste Schritte Der erste Schritt ist die Erstellung eines Ordners, der das Plugin enthalten wird: ```shell mkdir myplugin && \ cd myplugin ``` ### Metadaten Eine Datei namens **plugin.json**, die im Stammverzeichnis des Plugin-Ordners geschrieben wird, muss Metadaten über das Plugin enthalten. Hier ist ein Beispiel: ```json { "id": "myplugin", "name": "My Plugin", "description": "Just an example plugin.", "version": "1.0", "stream": "partial", "settings": { "DUMMY_SETTING": { "context": "multisite", "default": "1234", "help": "Here is the help of the setting.", "id": "dummy-id", "label": "Dummy setting", "regex": "^.*$", "type": "text" } }, "jobs": [ { "name": "my-job", "file": "my-job.py", "every": "hour" } ] } ``` Hier sind die Details der Felder: | Feld | Obligatorisch | Typ | Beschreibung | | :-----------: | :-----------: | :----- | :------------------------------------------------------------------------------------------------------------------------------------------- | | `id` | ja | string | Interne ID für das Plugin: muss unter anderen Plugins (einschließlich "Kern"-Plugins) eindeutig sein und darf nur Kleinbuchstaben enthalten. | | `name` | ja | string | Name Ihres Plugins. | | `description` | ja | string | Beschreibung Ihres Plugins. | | `version` | ja | string | Version Ihres Plugins. | | `stream` | ja | string | Informationen zur Stream-Unterstützung: `no`, `yes` oder `partial`. | | `settings` | ja | dict | Liste der Einstellungen Ihres Plugins. | | `jobs` | nein | list | Liste der Jobs Ihres Plugins. | | `bwcli` | nein | dict | Ordnet CLI-Befehlsnamen den in dem 'bwcli'-Verzeichnis des Plugins gespeicherten Dateien zu, um CLI-Plugins verfügbar zu machen. | Jede Einstellung hat die folgenden Felder (der Schlüssel ist die ID der in einer Konfiguration verwendeten Einstellungen): | Feld | Obligatorisch | Typ | Beschreibung | | :--------: | :-----------: | :----- | :-------------------------------------------------------------------------------- | | `context` | ja | string | Kontext der Einstellung: `multisite` oder `global`. | | `default` | ja | string | Der Standardwert der Einstellung. | | `help` | ja | string | Hilfetext zum Plugin (wird in der Web-UI angezeigt). | | `id` | ja | string | Interne ID, die von der Web-UI für HTML-Elemente verwendet wird. | | `label` | ja | string | Label, das von der Web-UI angezeigt wird. | | `regex` | ja | string | Der Regex, der zur Validierung des vom Benutzer angegebenen Werts verwendet wird. | | `type` | ja | string | Der Typ des Feldes: `text`, `check`, `select` oder `password`. | | `multiple` | nein | string | Eindeutige ID zur Gruppierung mehrerer Einstellungen mit Zahlen als Suffix. | | `select` | nein | list | Liste der möglichen Zeichenfolgenwerte, wenn `type` `select` ist. | Jeder Job hat die folgenden Felder: | Feld | Obligatorisch | Typ | Beschreibung | | :-----: | :-----------: | :----- | :-------------------------------------------------------------------------------------------------------------------------------------------------- | | `name` | ja | string | Name des Jobs. | | `file` | ja | string | Name der Datei im Jobs-Ordner. | | `every` | ja | string | Häufigkeit der Job-Planung: `minute`, `hour`, `day`, `week` oder `once` (keine Häufigkeit, nur einmal vor der (Neu-)Generierung der Konfiguration). | ### CLI-Befehle Plugins können das 'bwcli'-Tool mit benutzerdefinierten Befehlen erweitern, die unter 'bwcli plugin ...' ausgeführt werden: 1. Fügen Sie ein 'bwcli'-Verzeichnis in Ihrem Plugin hinzu und legen Sie eine Datei pro Befehl ab (zum Beispiel 'bwcli/list.py'). Die CLI fügt den Plugin-Pfad zu 'sys.path' hinzu, bevor die Datei ausgeführt wird. 2. Deklarieren Sie die Befehle im optionalen 'bwcli'-Abschnitt von 'plugin.json', indem Sie jeden Befehlsnamen seinem ausführbaren Dateinamen zuordnen. ```json { "bwcli": { "list": "list.py", "save": "save.py" } } ``` Der Scheduler stellt die deklarierten Befehle automatisch bereit, sobald das Plugin installiert ist. Core-Plugins wie 'backup' in 'src/common/core/backup' folgen demselben Muster. ### Konfigurationen Sie können benutzerdefinierte NGINX-Konfigurationen hinzufügen, indem Sie einen Ordner namens **confs** mit Inhalt ähnlich den [benutzerdefinierten Konfigurationen](advanced.md#custom-configurations) hinzufügen. Jeder Unterordner in **confs** enthält [jinja2](https://jinja.palletsprojects.com)-Vorlagen, die generiert und im entsprechenden Kontext (`http`, `server-http`, `default-server-http`, `stream`, `server-stream`, `modsec`, `modsec-crs`, `crs-plugins-before` und `crs-plugins-after`) geladen werden. Hier ist ein Beispiel für eine Konfigurationsvorlagendatei im Ordner **confs/server-http** namens **example.conf**: ```nginx location /setting { default_type 'text/plain'; content_by_lua_block { ngx.say('{{ DUMMY_SETTING }}') } } ``` `{{ DUMMY_SETTING }}` wird durch den Wert von `DUMMY_SETTING` ersetzt, der vom Benutzer des Plugins ausgewählt wurde. ### Vorlagen Weitere Informationen finden Sie in der [Vorlagendokumentation](concepts.md#templates). ### LUA #### Hauptskript Im Hintergrund verwendet BunkerWeb das [NGINX LUA-Modul](https://github.com/openresty/lua-nginx-module), um Code innerhalb von NGINX auszuführen. Plugins, die Code ausführen müssen, müssen eine Lua-Datei im Stammverzeichnis des Plugin-Ordners bereitstellen, die den `id`-Wert von **plugin.json** als Namen verwendet. Hier ist ein Beispiel namens **myplugin.lua**: ```lua local class = require "middleclass" local plugin = require "bunkerweb.plugin" local utils = require "bunkerweb.utils" local myplugin = class("myplugin", plugin) function myplugin:initialize(ctx) plugin.initialize(self, "myplugin", ctx) self.dummy = "dummy" end function myplugin:init() self.logger:log(ngx.NOTICE, "init called") return self:ret(true, "success") end function myplugin:set() self.logger:log(ngx.NOTICE, "set called") return self:ret(true, "success") end function myplugin:access() self.logger:log(ngx.NOTICE, "access called") return self:ret(true, "success") end function myplugin:log() self.logger:log(ngx.NOTICE, "log called") return self:ret(true, "success") end function myplugin:log_default() self.logger:log(ngx.NOTICE, "log_default called") return self:ret(true, "success") end function myplugin:preread() self.logger:log(ngx.NOTICE, "preread called") return self:ret(true, "success") end function myplugin:log_stream() self.logger:log(ngx.NOTICE, "log_stream called") return self:ret(true, "success") end return myplugin ``` Die deklarierten Funktionen werden automatisch in bestimmten Kontexten aufgerufen. Hier sind die Details zu jeder Funktion: | Funktion | Kontext | Beschreibung | Rückgabewert | | :-----------: | :-----------------------------------------------------------------------------------------: | :---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | :------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `init` | [init_by_lua](https://github.com/openresty/lua-nginx-module#init_by_lua) | Wird aufgerufen, wenn NGINX gerade gestartet wurde oder einen Neuladebefehl erhalten hat. Der typische Anwendungsfall besteht darin, Daten vorzubereiten, die von Ihrem Plugin verwendet werden. | `ret`, `msg`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
| | `set` | [set_by_lua](https://github.com/openresty/lua-nginx-module#set_by_lua) | Wird vor jeder vom Server empfangenen Anfrage aufgerufen. Der typische Anwendungsfall ist die Berechnung vor der Zugriffsphase. | `ret`, `msg`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
| | `access` | [access_by_lua](https://github.com/openresty/lua-nginx-module#access_by_lua) | Wird bei jeder vom Server empfangenen Anfrage aufgerufen. Der typische Anwendungsfall besteht darin, hier die Sicherheitsüberprüfungen durchzuführen und die Anfrage bei Bedarf abzulehnen. | `ret`, `msg`,`status`,`redirect`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
  • `status` (number): unterbricht den aktuellen Prozess und gibt den [HTTP-Status](https://github.com/openresty/lua-nginx-module#http-status-constants) zurück
  • `redirect` (URL): wenn gesetzt, wird auf die angegebene URL umgeleitet
| | `log` | [log_by_lua](https://github.com/openresty/lua-nginx-module#log_by_lua) | Wird aufgerufen, wenn eine Anfrage abgeschlossen ist (und bevor sie in die Zugriffsprotokolle geschrieben wird). Der typische Anwendungsfall besteht darin, beispielsweise Statistiken zu erstellen oder Zähler zu berechnen. | `ret`, `msg`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
| | `log_default` | [log_by_lua](https://github.com/openresty/lua-nginx-module#log_by_lua) | Dasselbe wie `log`, wird aber nur auf dem Standardserver aufgerufen. | `ret`, `msg`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
| | `preread` | [preread_by_lua](https://github.com/openresty/stream-lua-nginx-module#preread_by_lua_block) | Ähnlich der `access`-Funktion, aber für den Stream-Modus. | `ret`, `msg`,`status`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
  • `status` (number): unterbricht den aktuellen Prozess und gibt den [Status](https://github.com/openresty/lua-nginx-module#http-status-constants) zurück
| | `log_stream` | [log_by_lua](https://github.com/openresty/stream-lua-nginx-module#log_by_lua_block) | Ähnlich der `log`-Funktion, aber für den Stream-Modus. | `ret`, `msg`
  • `ret` (boolean): true, wenn kein Fehler, sonst false
  • `msg` (string): Erfolgs- oder Fehlermeldung
| #### Bibliotheken Alle Direktiven aus dem [NGINX LUA-Modul](https://github.com/openresty/lua-nginx-module) und dem [NGINX Stream LUA-Modul](https://github.com/openresty/stream-lua-nginx-module) sind verfügbar. Darüber hinaus können Sie die in BunkerWeb enthaltenen LUA-Bibliotheken verwenden: siehe [dieses Skript](https://github.com/bunkerity/bunkerweb/blob/v1.6.11-rc1/src/deps/clone.sh) für die vollständige Liste. Wenn Sie zusätzliche Bibliotheken benötigen, können Sie diese in den Stammordner des Plugins legen und darauf zugreifen, indem Sie ihnen Ihre Plugin-ID voranstellen. Hier ist ein Beispiel für eine Datei namens **mylibrary.lua**: ```lua local _M = {} _M.dummy = function () return "dummy" end return _M ``` Und hier ist, wie Sie sie aus der Datei **myplugin.lua** verwenden können: ```lua local mylibrary = require "myplugin.mylibrary" ... mylibrary.dummy() ... ``` #### Helfer Einige Helfermodule bieten allgemeine nützliche Helfer: - `self.variables`: ermöglicht den Zugriff auf und die Speicherung von Plugin-Attributen - `self.logger`: druckt Protokolle - `bunkerweb.utils`: verschiedene nützliche Funktionen - `bunkerweb.datastore`: greift auf die globalen gemeinsamen Daten auf einer Instanz zu (Schlüssel-Wert-Speicher) - `bunkerweb.clusterstore`: greift auf einen Redis-Datenspeicher zu, der zwischen BunkerWeb-Instanzen geteilt wird (Schlüssel-Wert-Speicher) Um auf die Funktionen zuzugreifen, müssen Sie zuerst die Module **erfordern**: ```lua local utils = require "bunkerweb.utils" local datastore = require "bunkerweb.datastore" local clustestore = require "bunkerweb.clustertore" ``` Einen Einstellungswert abrufen: ```lua local myvar = self.variables["DUMMY_SETTING"] if not myvar then self.logger:log(ngx.ERR, "kann Einstellung DUMMY_SETTING nicht abrufen") else self.logger:log(ngx.NOTICE, "DUMMY_SETTING = " .. value) end ``` Etwas im lokalen Cache speichern: ```lua local ok, err = self.datastore:set("plugin_myplugin_something", "somevalue") if not ok then self.logger:log(ngx.ERR, "kann plugin_myplugin_something nicht im Datenspeicher speichern: " .. err) else self.logger:log(ngx.NOTICE, "plugin_myplugin_something erfolgreich im Datenspeicher gespeichert") end ``` Überprüfen, ob eine IP-Adresse global ist: ```lua local ret, err = utils.ip_is_global(ngx.ctx.bw.remote_addr) if ret == nil then self.logger:log(ngx.ERR, "Fehler beim Überprüfen, ob die IP " .. ngx.ctx.bw.remote_addr .. " global ist oder nicht: " .. err) elseif not ret then self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " ist nicht global") else self.logger:log(ngx.NOTICE, "IP " .. ngx.ctx.bw.remote_addr .. " ist global") end ``` !!! tip "Weitere Beispiele" Wenn Sie die vollständige Liste der verfügbaren Funktionen sehen möchten, können Sie sich die Dateien im [lua-Verzeichnis](https://github.com/bunkerity/bunkerweb/tree/v1.6.11-rc1/src/bw/lua/bunkerweb) des Repositorys ansehen. ### Jobs BunkerWeb verwendet einen internen Job-Scheduler für periodische Aufgaben wie die Erneuerung von Zertifikaten mit Certbot, das Herunterladen von Blacklists, das Herunterladen von MMDB-Dateien usw. Sie können Aufgaben Ihrer Wahl hinzufügen, indem Sie sie in einen Unterordner namens **jobs** legen und sie in der Metadatendatei **plugin.json** auflisten. Vergessen Sie nicht, die Ausführungsberechtigungen für alle hinzuzufügen, um Probleme zu vermeiden, wenn ein Benutzer Ihr Plugin klont und installiert. ### Plugin-Seite Alles, was mit der Web-UI zu tun hat, befindet sich im Unterordner **ui**, wie wir im [vorherigen Strukturabschnitt](#struktur) gesehen haben. #### Voraussetzungen Wenn Sie eine Plugin-Seite erstellen möchten, benötigen Sie zwei Dateien: - **template.html**, das mit einem **GET /plugins/<*plugin_id*>** erreichbar ist. - **actions.py**, wo Sie Skripte und Logik mit einem **POST /plugins/<*plugin_id*>** hinzufügen können. Beachten Sie, dass diese Datei **eine Funktion mit demselben Namen wie das Plugin benötigt**, um zu funktionieren. Diese Datei wird auch dann benötigt, wenn die Funktion leer ist. #### Grundlegendes Beispiel !!! info "Jinja 2-Vorlage" Die Datei **template.html** ist eine Jinja2-Vorlage. Weitere Informationen finden Sie in der [Jinja2-Dokumentation](https://jinja.palletsprojects.com). Wir können die Datei **actions.py** beiseite legen und **nur die Vorlage in einer GET-Situation verwenden**. Die Vorlage kann auf den App-Kontext und Bibliotheken zugreifen, sodass Sie Jinja-, Request- oder Flask-Dienstprogramme verwenden können. Sie können beispielsweise die Anforderungsargumente in Ihrer Vorlage wie folgt abrufen: ```html

Anforderungsargumente: {{ request.args.get() }}.

``` #### Actions.py !!! warning "CSRF-Token" Bitte beachten Sie, dass jede Formularübermittlung durch ein CSRF-Token geschützt ist. Sie müssen das folgende Snippet in Ihre Formulare einfügen: ```html ``` Sie können Ihre Plugin-Seite mit zusätzlichen Skripten mit der Datei **actions.py** erweitern, wenn Sie einen **POST /plugins/<*plugin_id*>** senden. Sie haben standardmäßig zwei Funktionen in **actions.py**: **pre_render-Funktion** Dies ermöglicht es Ihnen, Daten abzurufen, wenn Sie die Vorlage **GET**en, und die Daten mit der in Jinja verfügbaren `pre_render`-Variable zu verwenden, um Inhalte dynamischer anzuzeigen. ```python def pre_render(**kwargs) return ``` BunkerWeb sendet Ihnen diese Art von Antwort: ```python return jsonify({"status": "ok|ko", "code" : XXX, "data": }), 200 ``` **<*plugin_id*>-Funktion** Dies ermöglicht es Ihnen, Daten abzurufen, wenn Sie einen **POST** vom Vorlagenendpunkt ausführen, der in AJAX verwendet werden muss. ```python def myplugin(**kwargs) return ``` BunkerWeb sendet Ihnen diese Art von Antwort: ```python return jsonify({"message": "ok", "data": }), 200 ``` **Was Sie von action.py aus zugreifen können** Hier sind die Argumente, die an die Funktionen von `action.py` übergeben und darauf zugegriffen werden: ```python function(app=app, args=request.args.to_dict() or request.json or None) ``` !!! info "Verfügbare Python-Bibliotheken" Die Web-UI von BunkerWeb enthält eine Reihe vorinstallierter Python-Bibliotheken, die Sie in der `actions.py`-Datei Ihres Plugins oder anderen UI-bezogenen Skripten verwenden können. Diese sind sofort verfügbar und erfordern keine zusätzlichen Installationen. Hier ist die vollständige Liste der enthaltenen Bibliotheken: - **bcrypt** - Passwort-Hashing-Bibliothek - **biscuit-python** - Biscuit-Authentifizierungstoken - **certbot** - ACME-Client für Let's Encrypt - **Flask** - Web-Framework - **Flask-Login** - Benutzersitzungsverwaltung - **Flask-Session[cachelib]** - Serverseitige Sitzungsspeicherung - **Flask-WTF** - Formularbehandlung und CSRF-Schutz - **gunicorn[gthread]** - WSGI-HTTP-Server - **pillow** - Bildverarbeitung - **psutil** - System- und Prozess-Dienstprogramme - **python_dateutil** - Datums- und Uhrzeit-Dienstprogramme - **qrcode** - QR-Code-Generierung - **regex** - Erweiterte reguläre Ausdrücke - **urllib3** - HTTP-Client - **user_agents** - User-Agent-Parsing !!! tip "Verwendung von Bibliotheken in Ihrem Plugin" Um diese Bibliotheken in Ihrer `actions.py`-Datei zu importieren und zu verwenden, verwenden Sie einfach die standardmäßige Python-`import`-Anweisung. Zum Beispiel: ```python from flask import request import bcrypt ``` ??? warning "Externe Bibliotheken" Wenn Sie Bibliotheken benötigen, die nicht oben aufgeführt sind, installieren Sie sie im `ui`-Ordner Ihres Plugins und importieren Sie sie mit der klassischen `import`-Anweisung. Stellen Sie die Kompatibilität mit der vorhandenen Umgebung sicher, um Konflikte zu vermeiden. **Einige Beispiele** - Abrufen von übermittelten Formulardaten ```python from flask import request def myplugin(**kwargs) : my_form_value = request.form["my_form_input"] return my_form_value ``` - Zugriff auf die App-Konfiguration **action.py** ```python from flask import request def pre_render(**kwargs) : config = kwargs['app'].config["CONFIG"].get_config(methods=False) return config ``` **Vorlage** ```html
{{ pre_render }}
``` ### Hooks.py Diese Dokumentation beschreibt die Lebenszyklus-Hooks, die zur Verwaltung verschiedener Phasen einer Anfrage innerhalb der Anwendung verwendet werden. Jeder Hook ist mit einer bestimmten Phase verbunden. === "before_request" Diese Hooks werden **vor** der Verarbeitung einer eingehenden Anfrage ausgeführt. Sie werden normalerweise für Vorverarbeitungsaufgaben wie Authentifizierung, Validierung oder Protokollierung verwendet. Wenn der Hook ein Antwortobjekt zurückgibt, überspringt Flask die Anforderungsbehandlung und gibt die Antwort direkt zurück. Dies kann nützlich sein, um die Anforderungsverarbeitungspipeline kurzzuschließen. **Beispiel:** ```python from flask import request, Response def before_request(): print("Before-request: Validating request...", flush=True) # Hier Authentifizierung, Validierung oder Protokollierung durchführen if not is_valid_request(request): # Wir befinden uns im App-Kontext return Response("Invalid request!", status=400) def is_valid_request(request): # Dummy-Validierungslogik return "user" in request ``` === "after_request" Diese Hooks werden **nach** der Verarbeitung der Anfrage ausgeführt. Sie sind ideal für Nachverarbeitungsaufgaben wie Bereinigung, zusätzliche Protokollierung oder das Ändern der Antwort, bevor sie zurückgesendet wird. Sie erhalten das Antwortobjekt als Argument und können es ändern, bevor sie es zurückgeben. Der erste `after_request`-Hook, der eine Antwort zurückgibt, wird als endgültige Antwort verwendet. **Beispiel:** ```python from flask import request def after_request(response): print("After-request: Logging response...", flush=True) # Hier Protokollierung, Bereinigung oder Antwortänderungen durchführen log_response(response) return response def log_response(response): # Dummy-Protokollierungslogik print("Response logged:", response, flush=True) ``` === "teardown_request" Diese Hooks werden aufgerufen, wenn der Anforderungskontext abgebaut wird. Diese Hooks werden verwendet, um Ressourcen freizugeben oder Fehler zu behandeln, die während des Lebenszyklus der Anfrage aufgetreten sind. **Beispiel:** ```python def teardown_request(error=None): print("Teardown-request: Cleaning up resources...", flush=True) # Hier Bereinigung, Ressourcenfreigabe oder Fehlerbehandlung durchführen if error: handle_error(error) cleanup_resources() def handle_error(error): # Dummy-Fehlerbehandlungslogik print("Error encountered:", error, flush=True) def cleanup_resources(): # Dummy-Ressourcenbereinigungslogik print("Resources have been cleaned up.", flush=True) ``` === "context_processor" Diese Hooks werden verwendet, um zusätzlichen Kontext in Vorlagen oder Ansichten einzufügen. Sie bereichern den Laufzeitkontext, indem sie allgemeine Daten (wie Benutzerinformationen oder Konfigurationseinstellungen) an die Vorlagen übergeben. Wenn ein Kontextprozessor ein Wörterbuch zurückgibt, werden die Schlüssel und Werte dem Kontext für alle Vorlagen hinzugefügt. Dies ermöglicht es Ihnen, Daten über mehrere Ansichten oder Vorlagen hinweg zu teilen. **Beispiel:** ```python def context_processor() -> dict: print("Context-processor: Injecting context data...", flush=True) # Ein Wörterbuch mit Kontextdaten für Vorlagen/Ansichten zurückgeben return { "current_user": "John Doe", "app_version": "1.0.0", "feature_flags": {"new_ui": True} } ``` Dieses Lebenszyklus-Hook-Design bietet einen modularen und systematischen Ansatz zur Verwaltung verschiedener Aspekte des Lebenszyklus einer Anfrage: - **Modularität:** Jeder Hook ist für eine bestimmte Phase verantwortlich, wodurch sichergestellt wird, dass die Belange getrennt sind. - **Wartbarkeit:** Entwickler können Hook-Implementierungen einfach hinzufügen, ändern oder entfernen, ohne andere Teile des Lebenszyklus der Anfrage zu beeinträchtigen. - **Erweiterbarkeit:** Das Framework ist flexibel und ermöglicht zusätzliche Hooks oder Erweiterungen, wenn sich die Anwendungsanforderungen ändern. Durch die klare Definition der Verantwortlichkeiten jedes Hooks und der zugehörigen Protokollierungspräfixe stellt das System sicher, dass jede Phase der Anforderungsverarbeitung transparent und verwaltbar ist. ### Blueprints In Flask dienen **Blueprints** als modularer Weg, um verwandte Komponenten – wie Ansichten, Vorlagen und statische Dateien – innerhalb Ihrer Anwendung zu organisieren. Sie ermöglichen es Ihnen, Funktionen logisch zu gruppieren und können verwendet werden, um neue Abschnitte Ihrer App zu erstellen oder bestehende zu überschreiben. #### Erstellen eines Blueprints Um einen Blueprint zu definieren, erstellen Sie eine Instanz der `Blueprint`-Klasse und geben dabei dessen Namen und Importpfad an. Anschließend definieren Sie Routen und Ansichten, die mit diesem Blueprint verbunden sind. **Beispiel: Definieren eines neuen Blueprints** ```python from os.path import dirname from flask import Blueprint, render_template # Den Blueprint definieren my_blueprint = Blueprint('my_blueprint', __name__, template_folder=dirname(__file__) + '/templates') # Der template_folder wird gesetzt, um Konflikte mit dem ursprünglichen Blueprint zu vermeiden # Eine Route innerhalb des Blueprints definieren @my_blueprint.route('/my_blueprint') def my_blueprint_page(): return render_template('my_blueprint.html') ``` In diesem Beispiel wird ein Blueprint namens `my_blueprint` erstellt und eine Route `/my_blueprint` darin definiert. #### Überschreiben eines bestehenden Blueprints Blueprints können auch bestehende überschreiben, um Funktionalität zu ändern oder zu erweitern. Stellen Sie dazu sicher, dass der neue Blueprint denselben Namen hat wie der, den Sie überschreiben, und registrieren Sie ihn nach dem Original. **Beispiel: Überschreiben eines bestehenden Blueprints** ```python from os.path import dirname from flask import Flask, Blueprint # Ursprünglicher Blueprint instances = Blueprint('instances', __name__, template_folder=dirname(__file__) + '/templates') # Der template_folder wird gesetzt, um Konflikte mit dem ursprünglichen Blueprint zu vermeiden @instances.route('/instances') def override_instances(): return "Meine neue Instanzen-Seite" ``` In diesem Szenario wird beim Aufrufen der URL `/instances` "Meine neue Instanzen-Seite" angezeigt, da der zuletzt registrierte `instances`-Blueprint den ursprünglichen `instances`-Blueprint überschreibt. !!! warning "Zum Überschreiben" Seien Sie vorsichtig beim Überschreiben bestehender Blueprints, da dies das Verhalten der Anwendung beeinträchtigen kann. Stellen Sie sicher, dass die Änderungen den Anforderungen der Anwendung entsprechen und keine unerwarteten Nebenwirkungen verursachen. Alle bestehenden Routen werden aus dem ursprünglichen Blueprint entfernt, sodass Sie sie bei Bedarf neu implementieren müssen. #### Namenskonventionen !!! danger "Wichtig" Stellen Sie sicher, dass der Name des Blueprints mit dem Namen der Blueprint-Variable übereinstimmt, andernfalls wird er nicht als gültiger Blueprint betrachtet und nicht registriert. Für Konsistenz und Klarheit ist es ratsam, die folgenden Namenskonventionen zu befolgen: - **Blueprint-Namen**: Verwenden Sie kurze, nur aus Kleinbuchstaben bestehende Namen. Unterstriche können zur Lesbarkeit verwendet werden, z. B. `user_auth`. - **Dateinamen**: Passen Sie den Dateinamen an den Blueprint-Namen an und stellen Sie sicher, dass er nur aus Kleinbuchstaben mit Unterstrichen besteht, z. B. `user_auth.py`. Diese Praxis steht im Einklang mit den Namenskonventionen für Python-Module und hilft, eine klare Projektstruktur beizubehalten. **Beispiel: Blueprint- und Dateibenennung** ``` plugin / ui / blueprints / user_auth.py templates / user_auth.html ``` In dieser Struktur enthält `user_auth.py` den `user_auth`-Blueprint und `user_auth.html` ist die zugehörige Vorlage, die den empfohlenen Namenskonventionen entspricht.