mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Merge branch 'dev' of github.com:bunkerity/bunkerweb into dev
This commit is contained in:
commit
4e02e0a467
49 changed files with 380 additions and 581 deletions
|
|
@ -75,9 +75,9 @@ BunkerWeb has a pro version to further improve the security of your applications
|
|||
|
||||
We have centralised version management and support within the panel for greater ease of use.
|
||||
|
||||
More detailed information about this version can be found [by visiting our website](https://www.bunkerweb.io/#services?utm_campaign=self&utm_source=doc).
|
||||
More detailed information about this version can be found [by visiting our website.](https://www.bunkerweb.io/#services?utm_campaign=self&utm_source=doc)
|
||||
|
||||
If you're interested, upgrade to the pro version in just a few clicks, [following the steps here.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc).
|
||||
If you're interested, upgrade to the pro version in just a few clicks, [following the steps here.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc)
|
||||
|
||||
Note that in the documentation, it will be mentioned when a feature is for the pro version.
|
||||
|
||||
|
|
|
|||
|
|
@ -581,7 +581,7 @@ For example, you can get the request arguments in your template like this :
|
|||
```
|
||||
|
||||
|
||||
You can power-up your plugin page with additionnal scripting with the **actions.py** file when sending a **POST /plugins/<*plugin_id*>**.
|
||||
You can power-up your plugin page with additional scripting with the **actions.py** file when sending a **POST /plugins/<*plugin_id*>**.
|
||||
|
||||
Here is what is send to the function :
|
||||
|
||||
|
|
|
|||
|
|
@ -2431,6 +2431,12 @@ By default, BunkerWeb will only listen on IPv4 addresses and won't use IPv6 for
|
|||
|
||||
This quickstart is just a tiny fraction of the functionality that BunkerWeb has to offer. Be curious and see what else BunkerWeb has to offer.
|
||||
|
||||
The documentation shares free and professional features. The latter will be marked accordingly to avoid confusion.
|
||||
You can take a detailed look at the many [plugins and settings available](settings.md) in this solution.
|
||||
|
||||
If you want to make the most of the solution, don't hesitate to [take a detailed look at the features from the panel.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc)
|
||||
Details on the use of certain plugins can also be found on the [security tuning page.](security-tuning.md)
|
||||
|
||||
For simplified use of the solution, don't hesitate to [try out the web UI.](web-ui.md)
|
||||
|
||||
Please note that **BunkerWeb comes in both free and pro versions**, with pro features indicated to avoid confusion.
|
||||
|
||||
If you'd like to find out more about the pro version, and use the solution to the full, [visit the panel.](https://panel.bunkerweb.io/?utm_campaign=self&utm_source=doc)
|
||||
|
|
|
|||
|
|
@ -1729,7 +1729,7 @@ After a successful login/password combination, you will be prompted to enter you
|
|||
systemctl restart bunkerweb
|
||||
```
|
||||
|
||||
## Upgrade pro version
|
||||
## Upgrade to PRO
|
||||
|
||||
In case you have buy a pro version and you already have a BunkerWeb setup with an UI, you can update **pro settings** inside **global config page**.
|
||||
|
||||
|
|
|
|||
|
|
@ -67,8 +67,8 @@ RUN apk add --no-cache pcre bash python3 yajl && \
|
|||
mkdir -p /var/www/html && \
|
||||
mkdir -p /etc/bunkerweb && \
|
||||
mkdir -p /data/cache && ln -s /data/cache /var/cache/bunkerweb && \
|
||||
for dir in $(echo "configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
|
||||
for dir in $(echo "pro configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
|
||||
chown -R root:nginx /data /etc/nginx /var/cache/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /usr/bin/bwcli && \
|
||||
chmod -R 770 /data /etc/nginx /var/cache/bunkerweb /var/tmp/bunkerweb /var/log/bunkerweb /var/run/bunkerweb && \
|
||||
chmod 750 cli/main.py gen/main.py helpers/*.sh entrypoint.sh /usr/bin/bwcli deps/python/bin/* && \
|
||||
|
|
|
|||
|
|
@ -128,6 +128,8 @@ api.global.POST["^/confs$"] = function(self)
|
|||
destination = "/etc/bunkerweb/configs"
|
||||
elseif self.ctx.bw.uri == "/plugins" then
|
||||
destination = "/etc/bunkerweb/plugins"
|
||||
elseif self.ctx.bw.uri == "/pro_plugins" then
|
||||
destination = "/etc/bunkerweb/pro/plugins"
|
||||
end
|
||||
local form, err = upload:new(4096)
|
||||
if not form then
|
||||
|
|
@ -175,6 +177,8 @@ api.global.POST["^/custom_configs$"] = api.global.POST["^/confs$"]
|
|||
|
||||
api.global.POST["^/plugins$"] = api.global.POST["^/confs$"]
|
||||
|
||||
api.global.POST["^/pro_plugins$"] = api.global.POST["^/confs$"]
|
||||
|
||||
api.global.POST["^/unban$"] = function(self)
|
||||
read_body()
|
||||
local data = get_body_data()
|
||||
|
|
|
|||
|
|
@ -40,7 +40,7 @@ resolver {{ DNS_RESOLVERS }} {% if USE_IPV6 == "no" %}ipv6=off{% endif %};
|
|||
port_in_redirect off;
|
||||
|
||||
# lua configs
|
||||
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
|
||||
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/etc/bunkerweb/pro/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
|
||||
lua_package_cpath "/usr/share/bunkerweb/deps/lib/?.so;/usr/share/bunkerweb/deps/lib/lua/?.so;;";
|
||||
lua_ssl_trusted_certificate "/usr/share/bunkerweb/misc/root-ca.pem";
|
||||
lua_ssl_verify_depth 2;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ init_by_lua_block {
|
|||
-- Load plugins into the datastore
|
||||
logger:log(NOTICE, "saving plugins into datastore ...")
|
||||
local plugins = {}
|
||||
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins" }
|
||||
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins", "/etc/bunkerweb/pro/plugins" }
|
||||
for i, plugin_path in ipairs(plugin_paths) do
|
||||
local paths = popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
|
||||
for path in paths:lines() do
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ init_by_lua_block {
|
|||
-- Load plugins into the datastore
|
||||
logger:log(NOTICE, "saving plugins into datastore ...")
|
||||
local plugins = {}
|
||||
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins" }
|
||||
local plugin_paths = { "/usr/share/bunkerweb/core", "/etc/bunkerweb/plugins", "/etc/bunkerweb/pro/plugins" }
|
||||
for i, plugin_path in ipairs(plugin_paths) do
|
||||
local paths = popen("find -L " .. plugin_path .. " -maxdepth 1 -type d ! -path " .. plugin_path)
|
||||
for path in paths:lines() do
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ resolver_timeout 30s;
|
|||
tcp_nodelay on;
|
||||
|
||||
# lua path and dicts
|
||||
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
|
||||
lua_package_path "/usr/share/bunkerweb/lua/?.lua;/usr/share/bunkerweb/core/?.lua;/etc/bunkerweb/plugins/?.lua;/etc/bunkerweb/pro/plugins/?.lua;/usr/share/bunkerweb/deps/lib/lua/?.lua;;";
|
||||
lua_package_cpath "/usr/share/bunkerweb/deps/lib/?.so;/usr/share/bunkerweb/deps/lib/lua/?.so;;";
|
||||
lua_ssl_trusted_certificate "/usr/share/bunkerweb/misc/root-ca.pem";
|
||||
lua_ssl_verify_depth 2;
|
||||
|
|
|
|||
|
|
@ -68,7 +68,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -228,7 +228,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@
|
|||
|
||||
<!-- end status -->
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ try:
|
|||
elif db_config.get("USE_UI", {"value": "no"})["value"] == "yes":
|
||||
data["use_ui"] = "yes"
|
||||
|
||||
data["external_plugins"] = [f"{plugin['id']}/{plugin['version']}" for plugin in db.get_plugins(external=True)]
|
||||
data["external_plugins"] = [f"{plugin['id']}/{plugin['version']}" for plugin in db.get_plugins(_type="external")]
|
||||
data["os"] = {
|
||||
"name": "Linux",
|
||||
"version": "Unknown",
|
||||
|
|
|
|||
|
|
@ -48,7 +48,7 @@ def install_plugin(plugin_dir, db) -> bool:
|
|||
if EXTERNAL_PLUGINS_DIR.joinpath(metadata["id"], "plugin.json").is_file():
|
||||
old_version = None
|
||||
|
||||
for plugin in db.get_plugins(external=True):
|
||||
for plugin in db.get_plugins(_type="external"):
|
||||
if plugin["id"] == metadata["id"]:
|
||||
old_version = plugin["version"]
|
||||
break
|
||||
|
|
@ -179,7 +179,7 @@ try:
|
|||
|
||||
plugin_file.update(
|
||||
{
|
||||
"external": True,
|
||||
"type": "external",
|
||||
"page": False,
|
||||
"method": "scheduler",
|
||||
"data": value,
|
||||
|
|
@ -195,7 +195,7 @@ try:
|
|||
|
||||
lock = Lock()
|
||||
|
||||
for plugin in db.get_plugins(external=True, with_data=True):
|
||||
for plugin in db.get_plugins(_type="external", with_data=True):
|
||||
if plugin["method"] != "scheduler" and plugin["id"] not in external_plugins_ids:
|
||||
external_plugins.append(plugin)
|
||||
|
||||
|
|
|
|||
|
|
@ -71,7 +71,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -114,7 +114,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -87,7 +87,7 @@
|
|||
</div>
|
||||
<!-- end status -->
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -72,7 +72,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -65,7 +65,7 @@
|
|||
<!-- end icon -->
|
||||
</div>
|
||||
|
||||
<script>
|
||||
<script nonce="{{script_nonce}}">
|
||||
// Use SetupPlugin class that is on static/js/plugins/setup.js
|
||||
const setPlugin = new SetupPlugin({
|
||||
info: {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ from os.path import basename, normpath, join
|
|||
from pathlib import Path
|
||||
from re import compile as re_compile
|
||||
from sys import _getframe, path as sys_path
|
||||
from typing import Any, Dict, List, Optional, Tuple, Union
|
||||
from typing import Any, Dict, List, Literal, Optional, Tuple, Union
|
||||
from time import sleep
|
||||
from traceback import format_exc
|
||||
|
||||
|
|
@ -294,6 +294,7 @@ class Database:
|
|||
.with_entities(
|
||||
Metadata.custom_configs_changed,
|
||||
Metadata.external_plugins_changed,
|
||||
Metadata.pro_plugins_changed,
|
||||
Metadata.config_changed,
|
||||
Metadata.instances_changed,
|
||||
)
|
||||
|
|
@ -304,6 +305,7 @@ class Database:
|
|||
return dict(
|
||||
custom_configs_changed=metadata is not None and metadata.custom_configs_changed,
|
||||
external_plugins_changed=metadata is not None and metadata.external_plugins_changed,
|
||||
pro_plugins_changed=metadata is not None and metadata.pro_plugins_changed,
|
||||
config_changed=metadata is not None and metadata.config_changed,
|
||||
instances_changed=metadata is not None and metadata.instances_changed,
|
||||
)
|
||||
|
|
@ -316,6 +318,7 @@ class Database:
|
|||
"config",
|
||||
"custom_configs",
|
||||
"external_plugins",
|
||||
"pro_plugins",
|
||||
"instances",
|
||||
]
|
||||
with self.__db_session() as session:
|
||||
|
|
@ -333,6 +336,8 @@ class Database:
|
|||
metadata.custom_configs_changed = value
|
||||
if "external_plugins" in changes:
|
||||
metadata.external_plugins_changed = value
|
||||
if "pro_plugins" in changes:
|
||||
metadata.pro_plugins_changed = value
|
||||
if "instances" in changes:
|
||||
metadata.instances_changed = value
|
||||
session.commit()
|
||||
|
|
@ -356,7 +361,9 @@ class Database:
|
|||
has_all_tables = False
|
||||
continue
|
||||
missing_columns = []
|
||||
extra_columns = []
|
||||
|
||||
# Check if any columns are missing
|
||||
db_columns = inspector.get_columns(table)
|
||||
self.__logger.debug(f'Checking table "{table}" for missing columns')
|
||||
for column in Base.metadata.tables[table].columns:
|
||||
|
|
@ -365,12 +372,24 @@ class Database:
|
|||
self.__logger.warning(f'Column "{column.name}" is missing in table "{table}"')
|
||||
missing_columns.append(column)
|
||||
|
||||
# Check if any columns are extra
|
||||
self.__logger.debug(f'Checking table "{table}" for extra columns')
|
||||
for db_column in db_columns:
|
||||
self.__logger.debug(f'Checking column "{db_column["name"]}" in table "{table}"')
|
||||
if not any(column.name == db_column["name"] for column in Base.metadata.tables[table].columns):
|
||||
self.__logger.warning(f'Column "{db_column["name"]}" is extra in table "{table}"')
|
||||
extra_columns.append(db_column)
|
||||
|
||||
try:
|
||||
with self.__db_session() as session:
|
||||
if missing_columns:
|
||||
for column in missing_columns:
|
||||
self.__logger.warning(f'Adding column "{column.name}" to table "{table}"')
|
||||
session.execute(text(f"ALTER TABLE {table} ADD COLUMN {column.name} {column.type}"))
|
||||
if extra_columns:
|
||||
for column in extra_columns:
|
||||
self.__logger.warning(f'Removing column "{column["name"]}" from table "{table}"')
|
||||
session.execute(text(f"ALTER TABLE {table} DROP COLUMN {column['name']}"))
|
||||
session.commit()
|
||||
except BaseException:
|
||||
return False, format_exc()
|
||||
|
|
@ -400,7 +419,6 @@ class Database:
|
|||
"description": "The general settings for the server",
|
||||
"version": "0.1",
|
||||
"stream": "partial",
|
||||
"external": False,
|
||||
}
|
||||
else:
|
||||
settings = plugin.pop("settings", {})
|
||||
|
|
@ -423,8 +441,8 @@ class Database:
|
|||
if plugin["stream"] != db_plugin.stream:
|
||||
updates[Plugins.stream] = plugin["stream"]
|
||||
|
||||
if plugin.get("external", False) != db_plugin.external:
|
||||
updates[Plugins.external] = plugin.get("external", False)
|
||||
if plugin.get("type", "core") != db_plugin.type:
|
||||
updates[Plugins.type] = plugin.get("type", "core")
|
||||
|
||||
if plugin.get("method", "manual") != db_plugin.method:
|
||||
updates[Plugins.method] = plugin.get("method", "manual")
|
||||
|
|
@ -446,7 +464,7 @@ class Database:
|
|||
description=plugin["description"],
|
||||
version=plugin["version"],
|
||||
stream=plugin["stream"],
|
||||
external=plugin.get("external", False),
|
||||
type=plugin.get("type", "core"),
|
||||
method=plugin.get("method"),
|
||||
data=plugin.get("data"),
|
||||
checksum=plugin.get("checksum"),
|
||||
|
|
@ -1108,11 +1126,11 @@ class Database:
|
|||
|
||||
return ""
|
||||
|
||||
def update_external_plugins(self, plugins: List[Dict[str, Any]], *, delete_missing: bool = True) -> str:
|
||||
def update_external_plugins(self, plugins: List[Dict[str, Any]], *, _type: Literal["external", "pro"] = "external", delete_missing: bool = True) -> str:
|
||||
"""Update external plugins from the database"""
|
||||
to_put = []
|
||||
with self.__db_session() as session:
|
||||
db_plugins = session.query(Plugins).with_entities(Plugins.id).filter_by(external=True).all()
|
||||
db_plugins = session.query(Plugins).with_entities(Plugins.id).filter_by(type=_type).all()
|
||||
|
||||
db_ids = []
|
||||
if delete_missing and db_plugins:
|
||||
|
|
@ -1128,7 +1146,7 @@ class Database:
|
|||
settings = plugin.pop("settings", {})
|
||||
jobs = plugin.pop("jobs", [])
|
||||
page = plugin.pop("page", False)
|
||||
plugin["external"] = True
|
||||
plugin["type"] = _type
|
||||
db_plugin = (
|
||||
session.query(Plugins)
|
||||
.with_entities(
|
||||
|
|
@ -1139,16 +1157,16 @@ class Database:
|
|||
Plugins.method,
|
||||
Plugins.data,
|
||||
Plugins.checksum,
|
||||
Plugins.external,
|
||||
Plugins.type,
|
||||
)
|
||||
.filter_by(id=plugin["id"])
|
||||
.first()
|
||||
)
|
||||
|
||||
if db_plugin is not None:
|
||||
if db_plugin.external is False:
|
||||
if db_plugin.type not in ("external", "pro"):
|
||||
self.__logger.warning(
|
||||
f"Plugin \"{plugin['id']}\" is not external, skipping update (updating a non-external plugin is forbidden for security reasons)",
|
||||
f"Plugin \"{plugin['id']}\" is not {_type}, skipping update (updating a non-external or non-pro plugin is forbidden for security reasons)",
|
||||
)
|
||||
continue
|
||||
|
||||
|
|
@ -1175,6 +1193,9 @@ class Database:
|
|||
if plugin.get("checksum") != db_plugin.checksum:
|
||||
updates[Plugins.checksum] = plugin.get("checksum")
|
||||
|
||||
if plugin.get("type") != db_plugin.type:
|
||||
updates[Plugins.type] = plugin.get("type")
|
||||
|
||||
if updates:
|
||||
session.query(Plugins).filter(Plugins.id == plugin["id"]).update(updates)
|
||||
|
||||
|
|
@ -1364,7 +1385,7 @@ class Database:
|
|||
description=plugin["description"],
|
||||
version=plugin["version"],
|
||||
stream=plugin["stream"],
|
||||
external=True,
|
||||
type=_type,
|
||||
method=plugin["method"],
|
||||
data=plugin.get("data"),
|
||||
checksum=plugin.get("checksum"),
|
||||
|
|
@ -1464,7 +1485,10 @@ class Database:
|
|||
with suppress(ProgrammingError, OperationalError):
|
||||
metadata = session.query(Metadata).get(1)
|
||||
if metadata is not None:
|
||||
metadata.external_plugins_changed = True
|
||||
if _type == "external":
|
||||
metadata.external_plugins_changed = True
|
||||
elif _type == "pro":
|
||||
metadata.pro_plugins_changed = True
|
||||
|
||||
try:
|
||||
session.add_all(to_put)
|
||||
|
|
@ -1474,17 +1498,19 @@ class Database:
|
|||
|
||||
return ""
|
||||
|
||||
def get_plugins(self, *, external: bool = False, with_data: bool = False) -> List[Dict[str, Any]]:
|
||||
def get_plugins(self, *, _type: Literal["all", "external", "pro"] = "all", with_data: bool = False) -> List[Dict[str, Any]]:
|
||||
"""Get all plugins from the database."""
|
||||
plugins = []
|
||||
with self.__db_session() as session:
|
||||
entities = [Plugins.id, Plugins.stream, Plugins.name, Plugins.description, Plugins.version, Plugins.external, Plugins.method, Plugins.checksum]
|
||||
entities = [Plugins.id, Plugins.stream, Plugins.name, Plugins.description, Plugins.version, Plugins.type, Plugins.method, Plugins.checksum]
|
||||
if with_data:
|
||||
entities.append(Plugins.data)
|
||||
for plugin in session.query(Plugins).with_entities(*entities).all():
|
||||
if external and not plugin.external:
|
||||
continue
|
||||
|
||||
db_plugins = session.query(Plugins).with_entities(*entities)
|
||||
if _type != "all":
|
||||
db_plugins = db_plugins.filter_by(type=_type)
|
||||
|
||||
for plugin in db_plugins.all():
|
||||
page = session.query(Plugin_pages).with_entities(Plugin_pages.id).filter_by(plugin_id=plugin.id).first()
|
||||
data = {
|
||||
"id": plugin.id,
|
||||
|
|
@ -1492,7 +1518,7 @@ class Database:
|
|||
"name": plugin.name,
|
||||
"description": plugin.description,
|
||||
"version": plugin.version,
|
||||
"external": plugin.external,
|
||||
"type": plugin.type,
|
||||
"method": plugin.method,
|
||||
"page": page is not None,
|
||||
"settings": {},
|
||||
|
|
|
|||
|
|
@ -39,6 +39,7 @@ INTEGRATIONS_ENUM = Enum(
|
|||
"Unknown",
|
||||
name="integrations_enum",
|
||||
)
|
||||
PLUGIN_TYPES_ENUM = Enum("core", "external", "pro", name="plugin_types_enum")
|
||||
Base = declarative_base()
|
||||
|
||||
|
||||
|
|
@ -50,7 +51,7 @@ class Plugins(Base):
|
|||
description = Column(String(256), nullable=False)
|
||||
version = Column(String(32), nullable=False)
|
||||
stream = Column(String(16), nullable=False)
|
||||
external = Column(Boolean, default=False, nullable=False)
|
||||
type = Column(PLUGIN_TYPES_ENUM, default="core", nullable=False)
|
||||
method = Column(METHODS_ENUM, default="manual", nullable=False)
|
||||
data = Column(LargeBinary(length=(2**32) - 1), nullable=True)
|
||||
checksum = Column(String(128), nullable=True)
|
||||
|
|
@ -65,7 +66,6 @@ class Settings(Base):
|
|||
__table_args__ = (
|
||||
PrimaryKeyConstraint("id", "name"),
|
||||
UniqueConstraint("id"),
|
||||
UniqueConstraint("name"),
|
||||
)
|
||||
|
||||
id = Column(String(256), primary_key=True)
|
||||
|
|
@ -273,6 +273,7 @@ class Metadata(Base):
|
|||
scheduler_first_start = Column(Boolean, nullable=True)
|
||||
custom_configs_changed = Column(Boolean, default=False, nullable=True)
|
||||
external_plugins_changed = Column(Boolean, default=False, nullable=True)
|
||||
pro_plugins_changed = Column(Boolean, default=False, nullable=True)
|
||||
config_changed = Column(Boolean, default=False, nullable=True)
|
||||
instances_changed = Column(Boolean, default=False, nullable=True)
|
||||
integration = Column(INTEGRATIONS_ENUM, default="Unknown", nullable=False)
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ class Configurator:
|
|||
settings: str,
|
||||
core: str,
|
||||
external_plugins: Union[str, List[Dict[str, Any]]],
|
||||
pro_plugins: Union[str, List[Dict[str, Any]]],
|
||||
variables: Union[str, Dict[str, Any]],
|
||||
logger: Logger,
|
||||
):
|
||||
|
|
@ -46,6 +47,12 @@ class Configurator:
|
|||
else:
|
||||
self.__external_plugins = external_plugins
|
||||
|
||||
if isinstance(pro_plugins, str):
|
||||
self.__pro_plugins = []
|
||||
self.__load_plugins(pro_plugins, "pro")
|
||||
else:
|
||||
self.__pro_plugins = pro_plugins
|
||||
|
||||
if isinstance(variables, str):
|
||||
self.__variables = self.__load_variables(variables)
|
||||
else:
|
||||
|
|
@ -57,12 +64,14 @@ class Configurator:
|
|||
def get_settings(self) -> Dict[str, Any]:
|
||||
return self.__settings
|
||||
|
||||
def get_plugins(self, _type: Union[Literal["core"], Literal["external"]]) -> List[Dict[str, Any]]:
|
||||
return self.__core_plugins if _type == "core" else self.__external_plugins
|
||||
def get_plugins(self, _type: Literal["core", "external", "pro"]) -> List[Dict[str, Any]]:
|
||||
return {"core": self.__core_plugins, "external": self.__external_plugins, "pro": self.__pro_plugins}[_type]
|
||||
|
||||
def get_plugins_settings(self, _type: Union[Literal["core"], Literal["external"]]) -> Dict[str, Any]:
|
||||
def get_plugins_settings(self, _type: Literal["core", "external", "pro"]) -> Dict[str, Any]:
|
||||
if _type == "core":
|
||||
plugins = self.__core_plugins
|
||||
elif _type == "pro":
|
||||
plugins = self.__pro_plugins
|
||||
else:
|
||||
plugins = self.__external_plugins
|
||||
plugins_settings = {}
|
||||
|
|
@ -78,9 +87,7 @@ class Configurator:
|
|||
servers = {}
|
||||
for server_name in self.__variables["SERVER_NAME"].strip().split(" "):
|
||||
if not re_search(self.__settings["SERVER_NAME"]["regex"], server_name):
|
||||
self.__logger.warning(
|
||||
f"Ignoring server name {server_name} because regex is not valid",
|
||||
)
|
||||
self.__logger.warning(f"Ignoring server name {server_name} because regex is not valid")
|
||||
continue
|
||||
names = [server_name]
|
||||
if f"{server_name}_SERVER_NAME" in self.__variables:
|
||||
|
|
@ -88,9 +95,7 @@ class Configurator:
|
|||
self.__settings["SERVER_NAME"]["regex"],
|
||||
self.__variables[f"{server_name}_SERVER_NAME"],
|
||||
):
|
||||
self.__logger.warning(
|
||||
f"Ignoring {server_name}_SERVER_NAME because regex is not valid",
|
||||
)
|
||||
self.__logger.warning(f"Ignoring {server_name}_SERVER_NAME because regex is not valid")
|
||||
else:
|
||||
names = self.__variables[f"{server_name}_SERVER_NAME"].strip().split(" ")
|
||||
|
||||
|
|
@ -100,7 +105,7 @@ class Configurator:
|
|||
def __load_settings(self, path: str) -> Dict[str, Any]:
|
||||
return loads(Path(path).read_text())
|
||||
|
||||
def __load_plugins(self, path: str, _type: str = "core"):
|
||||
def __load_plugins(self, path: str, _type: Literal["core", "external", "pro"] = "core"):
|
||||
threads = []
|
||||
for file in glob(join(path, "*", "plugin.json")):
|
||||
thread = Thread(target=self.__load_plugin, args=(file, _type))
|
||||
|
|
@ -110,32 +115,26 @@ class Configurator:
|
|||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
def __load_plugin(self, file: str, _type: str = "core"):
|
||||
def __load_plugin(self, file: str, _type: Literal["core", "external", "pro"] = "core"):
|
||||
self.__semaphore.acquire(timeout=60)
|
||||
try:
|
||||
data = self.__load_settings(file)
|
||||
|
||||
resp, msg = self.__validate_plugin(data)
|
||||
if not resp:
|
||||
self.__logger.warning(
|
||||
f"Ignoring plugin {file} : {msg}",
|
||||
)
|
||||
self.__logger.warning(f"Ignoring {_type} plugin {file} : {msg}")
|
||||
return
|
||||
|
||||
if _type == "external":
|
||||
if _type != "core":
|
||||
plugin_content = BytesIO()
|
||||
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
|
||||
tar.add(
|
||||
dirname(file),
|
||||
arcname=basename(dirname(file)),
|
||||
recursive=True,
|
||||
)
|
||||
tar.add(dirname(file), arcname=basename(dirname(file)), recursive=True)
|
||||
plugin_content.seek(0, 0)
|
||||
value = plugin_content.getvalue()
|
||||
|
||||
data.update(
|
||||
{
|
||||
"external": True,
|
||||
"type": _type,
|
||||
"page": "ui" in listdir(dirname(file)),
|
||||
"method": "manual",
|
||||
"data": value,
|
||||
|
|
@ -144,14 +143,15 @@ class Configurator:
|
|||
)
|
||||
|
||||
with self.__thread_lock:
|
||||
self.__external_plugins.append(data)
|
||||
if _type == "pro":
|
||||
self.__pro_plugins.append(data)
|
||||
else:
|
||||
self.__external_plugins.append(data)
|
||||
else:
|
||||
with self.__thread_lock:
|
||||
self.__core_plugins.append(data)
|
||||
except:
|
||||
self.__logger.error(
|
||||
f"Exception while loading JSON from {file} : {format_exc()}",
|
||||
)
|
||||
self.__logger.error(f"Exception while loading JSON from {file} : {format_exc()}")
|
||||
self.__semaphore.release()
|
||||
|
||||
def __load_variables(self, path: str) -> Dict[str, Any]:
|
||||
|
|
@ -173,6 +173,7 @@ class Configurator:
|
|||
self.__settings,
|
||||
self.get_plugins_settings("core"),
|
||||
self.get_plugins_settings("external"),
|
||||
self.get_plugins_settings("pro"),
|
||||
]
|
||||
for settings in default_settings:
|
||||
for setting, data in settings.items():
|
||||
|
|
@ -226,10 +227,7 @@ class Configurator:
|
|||
if not where:
|
||||
return False, f"variable name {variable} doesn't exist"
|
||||
elif not re_search(where[real_var]["regex"], value):
|
||||
return (
|
||||
False,
|
||||
f"value {value} doesn't match regex {where[real_var]['regex']}",
|
||||
)
|
||||
return (False, f"value {value} doesn't match regex {where[real_var]['regex']}")
|
||||
return True, "ok"
|
||||
# MULTISITE=yes
|
||||
prefixed, real_var = self.__var_is_prefixed(variable)
|
||||
|
|
@ -239,10 +237,7 @@ class Configurator:
|
|||
elif prefixed and where[real_var]["context"] != "multisite":
|
||||
return False, f"context of {variable} isn't multisite"
|
||||
elif not re_search(where[real_var]["regex"], value):
|
||||
return (
|
||||
False,
|
||||
f"value {value} doesn't match regex {where[real_var]['regex']}",
|
||||
)
|
||||
return (False, f"value {value} doesn't match regex {where[real_var]['regex']}")
|
||||
return True, "ok"
|
||||
|
||||
def __find_var(self, variable: str) -> Tuple[Optional[Dict[str, Any]], str]:
|
||||
|
|
@ -250,6 +245,7 @@ class Configurator:
|
|||
self.__settings,
|
||||
self.get_plugins_settings("core"),
|
||||
self.get_plugins_settings("external"),
|
||||
self.get_plugins_settings("pro"),
|
||||
]
|
||||
for target in targets:
|
||||
if variable in target:
|
||||
|
|
@ -267,120 +263,57 @@ class Configurator:
|
|||
|
||||
def __validate_plugin(self, plugin: dict) -> Tuple[bool, str]:
|
||||
if not all(key in plugin for key in ("id", "name", "description", "version", "stream", "settings")):
|
||||
return (
|
||||
False,
|
||||
f"Missing mandatory keys for plugin {plugin.get('id', 'unknown')} (id, name, description, version, stream, settings)",
|
||||
)
|
||||
return (False, f"Missing mandatory keys for plugin {plugin.get('id', 'unknown')} (id, name, description, version, stream, settings)")
|
||||
|
||||
if not self.__plugin_id_rx.match(plugin["id"]):
|
||||
return (
|
||||
False,
|
||||
f"Invalid id for plugin {plugin['id']} (Can only contain numbers, letters, underscores and hyphens (min 1 characters and max 64))",
|
||||
)
|
||||
return (False, f"Invalid id for plugin {plugin['id']} (Can only contain numbers, letters, underscores and hyphens (min 1 characters and max 64))")
|
||||
elif len(plugin["name"]) > 128:
|
||||
return (
|
||||
False,
|
||||
f"Invalid name for plugin {plugin['id']} (Max 128 characters)",
|
||||
)
|
||||
return (False, f"Invalid name for plugin {plugin['id']} (Max 128 characters)")
|
||||
elif len(plugin["description"]) > 256:
|
||||
return (
|
||||
False,
|
||||
f"Invalid description for plugin {plugin['id']} (Max 256 characters)",
|
||||
)
|
||||
return (False, f"Invalid description for plugin {plugin['id']} (Max 256 characters)")
|
||||
elif not self.__plugin_version_rx.match(plugin["version"]):
|
||||
return (
|
||||
False,
|
||||
f"Invalid version for plugin {plugin['id']} (Must be in format \\d+\\.\\d+(\\.\\d+)?)",
|
||||
)
|
||||
return (False, f"Invalid version for plugin {plugin['id']} (Must be in format \\d+\\.\\d+(\\.\\d+)?)")
|
||||
elif plugin["stream"] not in ("yes", "no", "partial"):
|
||||
return (
|
||||
False,
|
||||
f"Invalid stream for plugin {plugin['id']} (Must be yes, no or partial)",
|
||||
)
|
||||
return (False, f"Invalid stream for plugin {plugin['id']} (Must be yes, no or partial)")
|
||||
|
||||
for setting, data in plugin["settings"].items():
|
||||
if not all(key in data.keys() for key in ("context", "default", "help", "id", "label", "regex", "type")):
|
||||
return (
|
||||
False,
|
||||
f"missing keys for setting {setting} in plugin {plugin['id']}, must have context, default, help, id, label, regex and type",
|
||||
)
|
||||
return (False, f"missing keys for setting {setting} in plugin {plugin['id']}, must have context, default, help, id, label, regex and type")
|
||||
|
||||
if not self.__setting_id_rx.match(setting):
|
||||
return (
|
||||
False,
|
||||
f"Invalid setting name for setting {setting} in plugin {plugin['id']} (Can only contain capital letters and underscores (min 1 characters and max 256))",
|
||||
)
|
||||
return (False, f"Invalid setting name for setting {setting} in plugin {plugin['id']} (Can only contain capital letters and underscores (min 1 characters and max 256))")
|
||||
elif data["context"] not in ("global", "multisite"):
|
||||
return (
|
||||
False,
|
||||
f"Invalid context for setting {setting} in plugin {plugin['id']} (Must be global or multisite)",
|
||||
)
|
||||
return (False, f"Invalid context for setting {setting} in plugin {plugin['id']} (Must be global or multisite)")
|
||||
elif len(data["default"]) > 4096:
|
||||
return (
|
||||
False,
|
||||
f"Invalid default for setting {setting} in plugin {plugin['id']} (Max 4096 characters)",
|
||||
)
|
||||
return (False, f"Invalid default for setting {setting} in plugin {plugin['id']} (Max 4096 characters)")
|
||||
elif len(data["help"]) > 512:
|
||||
return (
|
||||
False,
|
||||
f"Invalid help for setting {setting} in plugin {plugin['id']} (Max 512 characters)",
|
||||
)
|
||||
return (False, f"Invalid help for setting {setting} in plugin {plugin['id']} (Max 512 characters)")
|
||||
elif len(data["label"]) > 256:
|
||||
return (
|
||||
False,
|
||||
f"Invalid label for setting {setting} in plugin {plugin['id']} (Max 256 characters)",
|
||||
)
|
||||
return (False, f"Invalid label for setting {setting} in plugin {plugin['id']} (Max 256 characters)")
|
||||
elif len(data["regex"]) > 1024:
|
||||
return (
|
||||
False,
|
||||
f"Invalid regex for setting {setting} in plugin {plugin['id']} (Max 1024 characters)",
|
||||
)
|
||||
return (False, f"Invalid regex for setting {setting} in plugin {plugin['id']} (Max 1024 characters)")
|
||||
elif data["type"] not in ("password", "text", "check", "select"):
|
||||
return (
|
||||
False,
|
||||
f"Invalid type for setting {setting} in plugin {plugin['id']} (Must be password, text, check or select)",
|
||||
)
|
||||
return (False, f"Invalid type for setting {setting} in plugin {plugin['id']} (Must be password, text, check or select)")
|
||||
|
||||
if "multiple" in data:
|
||||
if not self.__name_rx.match(data["multiple"]):
|
||||
return (
|
||||
False,
|
||||
f"Invalid multiple for setting {setting} in plugin {plugin['id']} (Can only contain numbers, letters, underscores and hyphens (min 1 characters and max 128))",
|
||||
)
|
||||
return (False, f"Invalid multiple for setting {setting} in plugin {plugin['id']} (Can only contain numbers, letters, underscores and hyphens (min 1 characters and max 128))")
|
||||
|
||||
for select in data.get("select", []):
|
||||
if len(select) > 256:
|
||||
return (
|
||||
False,
|
||||
f"Invalid select value {select} for setting {setting} in plugin {plugin['id']} (Max 256 characters)",
|
||||
)
|
||||
return (False, f"Invalid select value {select} for setting {setting} in plugin {plugin['id']} (Max 256 characters)")
|
||||
|
||||
for job in plugin.get("jobs", []):
|
||||
if not all(key in job.keys() for key in ("name", "file", "every", "reload")):
|
||||
return (
|
||||
False,
|
||||
f"missing keys for job {job['name']} in plugin {plugin['id']}, must have name, file, every and reload",
|
||||
)
|
||||
return (False, f"missing keys for job {job['name']} in plugin {plugin['id']}, must have name, file, every and reload")
|
||||
|
||||
if not self.__name_rx.match(job["name"]):
|
||||
return (
|
||||
False,
|
||||
f"Invalid name for job {job['name']} in plugin {plugin['id']}",
|
||||
)
|
||||
return (False, f"Invalid name for job {job['name']} in plugin {plugin['id']}")
|
||||
elif not self.__job_file_rx.match(job["file"]):
|
||||
return (
|
||||
False,
|
||||
f"Invalid file for job {job['name']} in plugin {plugin['id']} (Can only contain numbers, letters, underscores, hyphens and slashes (min 1 characters and max 256))",
|
||||
)
|
||||
return (False, f"Invalid file for job {job['name']} in plugin {plugin['id']} (Can only contain numbers, letters, underscores, hyphens and slashes (min 1 characters and max 256))")
|
||||
elif job["every"] not in ("once", "minute", "hour", "day", "week"):
|
||||
return (
|
||||
False,
|
||||
f"Invalid every for job {job['name']} in plugin {plugin['id']} (Must be once, minute, hour, day or week)",
|
||||
)
|
||||
return (False, f"Invalid every for job {job['name']} in plugin {plugin['id']} (Must be once, minute, hour, day or week)")
|
||||
elif job["reload"] is not True and job["reload"] is not False:
|
||||
return (
|
||||
False,
|
||||
f"Invalid reload for job {job['name']} in plugin {plugin['id']} (Must be true or false)",
|
||||
)
|
||||
return (False, f"Invalid reload for job {job['name']} in plugin {plugin['id']} (Must be true or false)")
|
||||
|
||||
return True, "ok"
|
||||
|
|
|
|||
|
|
@ -16,18 +16,11 @@ from jinja2 import Environment, FileSystemLoader
|
|||
|
||||
|
||||
class Templator:
|
||||
def __init__(
|
||||
self,
|
||||
templates: str,
|
||||
core: str,
|
||||
plugins: str,
|
||||
output: str,
|
||||
target: str,
|
||||
config: Dict[str, Any],
|
||||
):
|
||||
def __init__(self, templates: str, core: str, plugins: str, pro_plugins: str, output: str, target: str, config: Dict[str, Any]):
|
||||
self.__templates = templates
|
||||
self.__core = core
|
||||
self.__plugins = plugins
|
||||
self.__pro_plugins = pro_plugins
|
||||
self.__output = output
|
||||
self.__target = target
|
||||
self.__config = config
|
||||
|
|
@ -43,14 +36,10 @@ class Templator:
|
|||
|
||||
def __load_jinja_env(self) -> Environment:
|
||||
searchpath = [self.__templates]
|
||||
for subpath in glob(join(self.__core, "*")) + glob(join(self.__plugins, "*")):
|
||||
for subpath in glob(join(self.__core, "*")) + glob(join(self.__plugins, "*")) + glob(join(self.__pro_plugins, "*")):
|
||||
if Path(subpath).is_dir():
|
||||
searchpath.append(join(subpath, "confs"))
|
||||
return Environment(
|
||||
loader=FileSystemLoader(searchpath=searchpath),
|
||||
lstrip_blocks=True,
|
||||
trim_blocks=True,
|
||||
)
|
||||
return Environment(loader=FileSystemLoader(searchpath=searchpath), lstrip_blocks=True, trim_blocks=True)
|
||||
|
||||
def __find_templates(self, contexts) -> List[str]:
|
||||
templates = []
|
||||
|
|
|
|||
|
|
@ -28,47 +28,14 @@ if __name__ == "__main__":
|
|||
try:
|
||||
# Parse arguments
|
||||
parser = ArgumentParser(description="BunkerWeb config generator")
|
||||
parser.add_argument(
|
||||
"--settings",
|
||||
default=join(sep, "usr", "share", "bunkerweb", "settings.json"),
|
||||
type=str,
|
||||
help="file containing the main settings",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--templates",
|
||||
default=join(sep, "usr", "share", "bunkerweb", "confs"),
|
||||
type=str,
|
||||
help="directory containing the main template files",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--core",
|
||||
default=join(sep, "usr", "share", "bunkerweb", "core"),
|
||||
type=str,
|
||||
help="directory containing the core plugins",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--plugins",
|
||||
default=join(sep, "etc", "bunkerweb", "plugins"),
|
||||
type=str,
|
||||
help="directory containing the external plugins",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default=join(sep, "etc", "nginx"),
|
||||
type=str,
|
||||
help="where to write the rendered files",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--target",
|
||||
default=join(sep, "etc", "nginx"),
|
||||
type=str,
|
||||
help="where nginx will search for configurations files",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--variables",
|
||||
type=str,
|
||||
help="path to the file containing environment variables",
|
||||
)
|
||||
parser.add_argument("--settings", default=join(sep, "usr", "share", "bunkerweb", "settings.json"), type=str, help="file containing the main settings")
|
||||
parser.add_argument("--templates", default=join(sep, "usr", "share", "bunkerweb", "confs"), type=str, help="directory containing the main template files")
|
||||
parser.add_argument("--core", default=join(sep, "usr", "share", "bunkerweb", "core"), type=str, help="directory containing the core plugins")
|
||||
parser.add_argument("--plugins", default=join(sep, "etc", "bunkerweb", "plugins"), type=str, help="directory containing the external plugins")
|
||||
parser.add_argument("--pro-plugins", default=join(sep, "etc", "bunkerweb", "pro", "plugins"), type=str, help="directory containing the pro plugins")
|
||||
parser.add_argument("--output", default=join(sep, "etc", "nginx"), type=str, help="where to write the rendered files")
|
||||
parser.add_argument("--target", default=join(sep, "etc", "nginx"), type=str, help="where nginx will search for configurations files")
|
||||
parser.add_argument("--variables", type=str, help="path to the file containing environment variables")
|
||||
parser.add_argument("--no-linux-reload", action="store_true", help="disable linux reload")
|
||||
args = parser.parse_args()
|
||||
|
||||
|
|
@ -76,6 +43,7 @@ if __name__ == "__main__":
|
|||
templates_path = Path(normpath(args.templates))
|
||||
core_path = Path(normpath(args.core))
|
||||
plugins_path = Path(normpath(args.plugins))
|
||||
pro_plugins_path = Path(normpath(args.pro_plugins))
|
||||
output_path = Path(normpath(args.output))
|
||||
target_path = Path(normpath(args.target))
|
||||
|
||||
|
|
@ -84,6 +52,7 @@ if __name__ == "__main__":
|
|||
logger.info(f"Templates : {templates_path}")
|
||||
logger.info(f"Core : {core_path}")
|
||||
logger.info(f"Plugins : {plugins_path}")
|
||||
logger.info(f"Pro plugins : {pro_plugins_path}")
|
||||
logger.info(f"Output : {output_path}")
|
||||
logger.info(f"Target : {target_path}")
|
||||
|
||||
|
|
@ -110,7 +79,7 @@ if __name__ == "__main__":
|
|||
# Check existences and permissions
|
||||
logger.info("Checking arguments ...")
|
||||
files = [settings_path, variables_path]
|
||||
paths_rx = [core_path, plugins_path, templates_path]
|
||||
paths_rx = [core_path, plugins_path, pro_plugins_path, templates_path]
|
||||
paths_rwx = [output_path]
|
||||
for file in files:
|
||||
if not file.is_file():
|
||||
|
|
@ -137,13 +106,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Compute the config
|
||||
logger.info("Computing config ...")
|
||||
config: Dict[str, Any] = Configurator(
|
||||
str(settings_path),
|
||||
str(core_path),
|
||||
str(plugins_path),
|
||||
str(variables_path),
|
||||
logger,
|
||||
).get_config()
|
||||
config: Dict[str, Any] = Configurator(str(settings_path), str(core_path), str(plugins_path), str(pro_plugins_path), str(variables_path), logger).get_config()
|
||||
else:
|
||||
if join(sep, "usr", "share", "bunkerweb", "db") not in sys_path:
|
||||
sys_path.append(join(sep, "usr", "share", "bunkerweb", "db"))
|
||||
|
|
@ -168,14 +131,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Render the templates
|
||||
logger.info("Rendering templates ...")
|
||||
templator = Templator(
|
||||
str(templates_path),
|
||||
str(core_path),
|
||||
str(plugins_path),
|
||||
str(output_path),
|
||||
str(target_path),
|
||||
config,
|
||||
)
|
||||
templator = Templator(str(templates_path), str(core_path), str(plugins_path), str(pro_plugins_path), str(output_path), str(target_path), config)
|
||||
templator.render()
|
||||
|
||||
if integration not in ("Autoconf", "Swarm", "Kubernetes", "Docker") and not args.no_linux_reload:
|
||||
|
|
|
|||
|
|
@ -79,55 +79,26 @@ if __name__ == "__main__":
|
|||
try:
|
||||
# Parse arguments
|
||||
parser = ArgumentParser(description="BunkerWeb config saver")
|
||||
parser.add_argument(
|
||||
"--settings",
|
||||
default=join(sep, "usr", "share", "bunkerweb", "settings.json"),
|
||||
type=str,
|
||||
help="file containing the main settings",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--core",
|
||||
default=join(sep, "usr", "share", "bunkerweb", "core"),
|
||||
type=str,
|
||||
help="directory containing the core plugins",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--plugins",
|
||||
default=join(sep, "etc", "bunkerweb", "plugins"),
|
||||
type=str,
|
||||
help="directory containing the external plugins",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--variables",
|
||||
type=str,
|
||||
help="path to the file containing environment variables",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--init",
|
||||
action="store_true",
|
||||
help="Only initialize the database",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--method",
|
||||
default="scheduler",
|
||||
type=str,
|
||||
help="The method that is used to save the config",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--no-check-changes",
|
||||
action="store_true",
|
||||
help="Set the changes to checked in the database",
|
||||
)
|
||||
parser.add_argument("--settings", default=join(sep, "usr", "share", "bunkerweb", "settings.json"), type=str, help="file containing the main settings")
|
||||
parser.add_argument("--core", default=join(sep, "usr", "share", "bunkerweb", "core"), type=str, help="directory containing the core plugins")
|
||||
parser.add_argument("--plugins", default=join(sep, "etc", "bunkerweb", "plugins"), type=str, help="directory containing the external plugins")
|
||||
parser.add_argument("--pro-plugins", default=join(sep, "etc", "bunkerweb", "pro", "plugins"), type=str, help="directory containing the pro plugins")
|
||||
parser.add_argument("--variables", type=str, help="path to the file containing environment variables")
|
||||
parser.add_argument("--init", action="store_true", help="Only initialize the database")
|
||||
parser.add_argument("--method", default="scheduler", type=str, help="The method that is used to save the config")
|
||||
parser.add_argument("--no-check-changes", action="store_true", help="Set the changes to checked in the database")
|
||||
args = parser.parse_args()
|
||||
|
||||
settings_path = Path(normpath(args.settings))
|
||||
core_path = Path(normpath(args.core))
|
||||
plugins_path = Path(normpath(args.plugins))
|
||||
pro_plugins_path = Path(normpath(args.pro_plugins))
|
||||
|
||||
logger.info("Save config started ...")
|
||||
logger.info(f"Settings : {settings_path}")
|
||||
logger.info(f"Core : {core_path}")
|
||||
logger.info(f"Plugins : {plugins_path}")
|
||||
logger.info(f"Pro plugins : {pro_plugins_path}")
|
||||
logger.info(f"Init : {args.init}")
|
||||
|
||||
integration = "Linux"
|
||||
|
|
@ -154,14 +125,16 @@ if __name__ == "__main__":
|
|||
apis = []
|
||||
|
||||
external_plugins = args.plugins
|
||||
pro_plugins = args.pro_plugins
|
||||
if not Path(sep, "usr", "sbin", "nginx").exists() and args.method == "ui":
|
||||
db = Database(logger, pool=False)
|
||||
external_plugins = db.get_plugins()
|
||||
external_plugins = db.get_plugins(_type="external")
|
||||
pro_plugins = db.get_plugins(_type="pro")
|
||||
|
||||
# Check existences and permissions
|
||||
logger.info("Checking arguments ...")
|
||||
files = [settings_path] + ([Path(normpath(args.variables))] if args.variables else [])
|
||||
paths_rx = [core_path, plugins_path]
|
||||
paths_rx = [core_path, plugins_path, pro_plugins_path]
|
||||
for file in files:
|
||||
if not file.is_file():
|
||||
logger.error(f"Missing file : {file}")
|
||||
|
|
@ -174,9 +147,7 @@ if __name__ == "__main__":
|
|||
logger.error(f"Missing directory : {path}")
|
||||
sys_exit(1)
|
||||
if not access(path, R_OK | X_OK):
|
||||
logger.error(
|
||||
f"Missing RX rights on directory : {path}",
|
||||
)
|
||||
logger.error(f"Missing RX rights on directory : {path}")
|
||||
sys_exit(1)
|
||||
|
||||
if args.variables:
|
||||
|
|
@ -185,13 +156,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Compute the config
|
||||
logger.info("Computing config ...")
|
||||
config = Configurator(
|
||||
str(settings_path),
|
||||
str(core_path),
|
||||
external_plugins,
|
||||
str(variables_path),
|
||||
logger,
|
||||
)
|
||||
config = Configurator(str(settings_path), str(core_path), external_plugins, pro_plugins, str(variables_path), logger)
|
||||
config_files = config.get_config()
|
||||
custom_confs = []
|
||||
for k, v in environ.items():
|
||||
|
|
@ -262,13 +227,7 @@ if __name__ == "__main__":
|
|||
# Compute the config
|
||||
if not config_files:
|
||||
logger.info("Computing config ...")
|
||||
config = Configurator(
|
||||
args.settings,
|
||||
args.core,
|
||||
external_plugins,
|
||||
tmp_config,
|
||||
logger,
|
||||
)
|
||||
config = Configurator(args.settings, args.core, external_plugins, pro_plugins, tmp_config, logger)
|
||||
config_files = config.get_config()
|
||||
|
||||
bunkerweb_version = Path(sep, "usr", "share", "bunkerweb", "VERSION").read_text().strip()
|
||||
|
|
@ -279,11 +238,7 @@ if __name__ == "__main__":
|
|||
"Database not initialized, initializing ...",
|
||||
)
|
||||
ret, err = db.init_tables(
|
||||
[
|
||||
config.get_settings(),
|
||||
config.get_plugins("core"),
|
||||
config.get_plugins("external"),
|
||||
],
|
||||
[config.get_settings(), config.get_plugins("core"), config.get_plugins("external"), config.get_plugins("pro")],
|
||||
bunkerweb_version,
|
||||
)
|
||||
|
||||
|
|
@ -303,11 +258,7 @@ if __name__ == "__main__":
|
|||
logger.info("Database is already initialized, checking for changes ...")
|
||||
|
||||
ret, err = db.init_tables(
|
||||
[
|
||||
config.get_settings(),
|
||||
config.get_plugins("core"),
|
||||
config.get_plugins("external"),
|
||||
],
|
||||
[config.get_settings(), config.get_plugins("core"), config.get_plugins("external"), config.get_plugins("pro")],
|
||||
bunkerweb_version,
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ log "$service" "ℹ️" "Setup and check /data folder ..."
|
|||
|
||||
# Create folders if missing and check permissions
|
||||
rwx_folders=("cache" "cache/letsencrypt" "lib")
|
||||
rx_folders=("configs" "configs/http" "configs/stream" "configs/server-http" "configs/server-stream" "configs/default-server-http" "configs/default-server-stream" "configs/modsec" "configs/modsec-crs" "plugins" "www")
|
||||
rx_folders=("pro" "pro/plugins" "configs" "configs/http" "configs/stream" "configs/server-http" "configs/server-stream" "configs/default-server-http" "configs/default-server-stream" "configs/modsec" "configs/modsec-crs" "plugins" "www")
|
||||
for folder in "${rwx_folders[@]}" ; do
|
||||
if [ ! -d "/data/${folder}" ] ; then
|
||||
mkdir -p "/data/${folder}"
|
||||
|
|
|
|||
|
|
@ -94,7 +94,7 @@ RUN cp helpers/bwcli /usr/bin/ && \
|
|||
chmod 755 /usr/bin/bwcli && \
|
||||
mkdir -p /etc/bunkerweb/configs /etc/bunkerweb/plugins /var/cache/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /var/lib/bunkerweb /var/www/html && \
|
||||
echo "Linux" > INTEGRATION && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "plugins pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
find . -path deps -prune -o -type f -exec chmod 0740 {} \; && \
|
||||
find . -path deps -prune -o -type d -exec chmod 0750 {} \; && \
|
||||
chmod 755 /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ RUN cp helpers/bwcli /usr/bin/ && \
|
|||
chmod 755 /usr/bin/bwcli && \
|
||||
mkdir -p /etc/bunkerweb/configs /etc/bunkerweb/plugins /var/cache/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /var/lib/bunkerweb /var/www/html && \
|
||||
echo "Linux" > INTEGRATION && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "plugins pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
find . -path deps -prune -o -type f -exec chmod 0740 {} \; && \
|
||||
find . -path deps -prune -o -type d -exec chmod 0750 {} \; && \
|
||||
chmod 755 /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -74,7 +74,7 @@ RUN cp helpers/bwcli /usr/bin/ && \
|
|||
chmod 755 /usr/bin/bwcli && \
|
||||
mkdir -p /etc/bunkerweb/configs /etc/bunkerweb/plugins /var/cache/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /var/lib/bunkerweb /var/www/html && \
|
||||
echo "Linux" > INTEGRATION && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "plugins pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
find . -path deps -prune -o -type f -exec chmod 0740 {} \; && \
|
||||
find . -path deps -prune -o -type d -exec chmod 0750 {} \; && \
|
||||
chmod 755 /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ RUN cp helpers/bwcli /usr/bin/ && \
|
|||
chmod 755 /usr/bin/bwcli && \
|
||||
mkdir -p /etc/bunkerweb/configs /etc/bunkerweb/plugins /var/cache/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /var/lib/bunkerweb /var/www/html && \
|
||||
echo "Linux" > INTEGRATION && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "plugins pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
find . -path deps -prune -o -type f -exec chmod 0740 {} \; && \
|
||||
find . -path deps -prune -o -type d -exec chmod 0750 {} \; && \
|
||||
chmod 755 /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -90,7 +90,7 @@ RUN cp helpers/bwcli /usr/bin/ && \
|
|||
chmod 755 /usr/bin/bwcli && \
|
||||
mkdir -p /etc/bunkerweb/configs /etc/bunkerweb/plugins /var/cache/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /var/lib/bunkerweb /var/www/html && \
|
||||
echo "Linux" > INTEGRATION && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "plugins pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
find . -path deps -prune -o -type f -exec chmod 0740 {} \; && \
|
||||
find . -path deps -prune -o -type d -exec chmod 0750 {} \; && \
|
||||
chmod 755 /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ RUN cp helpers/bwcli /usr/bin/ && \
|
|||
chmod 755 /usr/bin/bwcli && \
|
||||
mkdir -p /etc/bunkerweb/configs /etc/bunkerweb/plugins /var/cache/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /var/lib/bunkerweb /var/www/html && \
|
||||
echo "Linux" > INTEGRATION && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "plugins pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir -p "/etc/bunkerweb/${dir}" ; done && \
|
||||
find . -path deps -prune -o -type f -exec chmod 0740 {} \; && \
|
||||
find . -path deps -prune -o -type d -exec chmod 0750 {} \; && \
|
||||
chmod 755 /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -62,8 +62,8 @@ RUN apk add --no-cache bash libgcc libstdc++ libpq openssl libmagic && \
|
|||
mkdir -p /etc/bunkerweb && \
|
||||
mkdir -p /data/cache && ln -s /data/cache /var/cache/bunkerweb && \
|
||||
mkdir -p /data/lib && ln -s /data/lib /var/lib/bunkerweb && \
|
||||
for dir in $(echo "configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
|
||||
for dir in $(echo "pro configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
|
||||
chown -R root:scheduler /data /etc/nginx /var/cache/bunkerweb /var/lib/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb /usr/bin/bwcli && \
|
||||
chmod -R 770 /data /etc/nginx /var/cache/bunkerweb /var/lib/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb && \
|
||||
find core/*/jobs/* -type f -exec chmod 750 {} \; && \
|
||||
|
|
|
|||
|
|
@ -77,15 +77,10 @@ class JobScheduler(ApiCaller):
|
|||
with self.__thread_lock:
|
||||
instances = self.__db.get_instances()
|
||||
for instance in instances:
|
||||
api = API(
|
||||
f"http://{instance['hostname']}:{instance['port']}",
|
||||
host=instance["server_name"],
|
||||
)
|
||||
api = API(f"http://{instance['hostname']}:{instance['port']}", host=instance["server_name"])
|
||||
apis.append(api)
|
||||
except:
|
||||
self.__logger.warning(
|
||||
f"Exception while getting jobs instances : {format_exc()}",
|
||||
)
|
||||
self.__logger.warning(f"Exception while getting jobs instances : {format_exc()}")
|
||||
return apis
|
||||
|
||||
def update_jobs(self):
|
||||
|
|
@ -93,7 +88,9 @@ class JobScheduler(ApiCaller):
|
|||
|
||||
def __get_jobs(self):
|
||||
jobs = {}
|
||||
for plugin_file in glob(join(sep, "usr", "share", "bunkerweb", "core", "*", "plugin.json")) + glob(join(sep, "etc", "bunkerweb", "plugins", "*", "plugin.json")): # core plugins # external plugins
|
||||
for plugin_file in (
|
||||
glob(join(sep, "usr", "share", "bunkerweb", "core", "*", "plugin.json")) + glob(join(sep, "etc", "bunkerweb", "plugins", "*", "plugin.json")) + glob(join(sep, "etc", "bunkerweb", "pro", "plugins", "*", "plugin.json"))
|
||||
): # core plugins # external plugins # pro plugins
|
||||
plugin_name = basename(dirname(plugin_file))
|
||||
jobs[plugin_name] = []
|
||||
try:
|
||||
|
|
@ -104,15 +101,7 @@ class JobScheduler(ApiCaller):
|
|||
plugin_jobs = plugin_data["jobs"]
|
||||
|
||||
for x, job in enumerate(deepcopy(plugin_jobs)):
|
||||
if not all(
|
||||
key in job.keys()
|
||||
for key in (
|
||||
"name",
|
||||
"file",
|
||||
"every",
|
||||
"reload",
|
||||
)
|
||||
):
|
||||
if not all(key in job.keys() for key in ("name", "file", "every", "reload")):
|
||||
self.__logger.warning(f"missing keys for job {job['name']} in plugin {plugin_name}, must have name, file, every and reload, ignoring job")
|
||||
plugin_jobs.pop(x)
|
||||
continue
|
||||
|
|
@ -140,9 +129,7 @@ class JobScheduler(ApiCaller):
|
|||
except FileNotFoundError:
|
||||
pass
|
||||
except:
|
||||
self.__logger.warning(
|
||||
f"Exception while getting jobs for plugin {plugin_name} : {format_exc()}",
|
||||
)
|
||||
self.__logger.warning(f"Exception while getting jobs for plugin {plugin_name} : {format_exc()}")
|
||||
return jobs
|
||||
|
||||
def __str_to_schedule(self, every: str) -> Job:
|
||||
|
|
@ -160,20 +147,12 @@ class JobScheduler(ApiCaller):
|
|||
reload = True
|
||||
if self.__integration not in ("Autoconf", "Swarm", "Kubernetes", "Docker"):
|
||||
self.__logger.info("Reloading nginx ...")
|
||||
proc = run(
|
||||
[join(sep, "usr", "sbin", "nginx"), "-s", "reload"],
|
||||
stdin=DEVNULL,
|
||||
stderr=PIPE,
|
||||
env=self.__env,
|
||||
check=False,
|
||||
)
|
||||
proc = run([join(sep, "usr", "sbin", "nginx"), "-s", "reload"], stdin=DEVNULL, stderr=PIPE, env=self.__env, check=False)
|
||||
reload = proc.returncode == 0
|
||||
if reload:
|
||||
self.__logger.info("Successfully reloaded nginx")
|
||||
else:
|
||||
self.__logger.error(
|
||||
f"Error while reloading nginx - returncode: {proc.returncode} - error: {proc.stderr.decode() if proc.stderr else 'Missing stderr'}",
|
||||
)
|
||||
self.__logger.error(f"Error while reloading nginx - returncode: {proc.returncode} - error: {proc.stderr.decode() if proc.stderr else 'Missing stderr'}")
|
||||
else:
|
||||
self.__logger.info("Reloading nginx ...")
|
||||
reload = self.send_to_apis("POST", "/reload")
|
||||
|
|
@ -184,33 +163,21 @@ class JobScheduler(ApiCaller):
|
|||
return reload
|
||||
|
||||
def __job_wrapper(self, path: str, plugin: str, name: str, file: str) -> int:
|
||||
self.__logger.info(
|
||||
f"Executing job {name} from plugin {plugin} ...",
|
||||
)
|
||||
self.__logger.info(f"Executing job {name} from plugin {plugin} ...")
|
||||
success = True
|
||||
ret = -1
|
||||
try:
|
||||
proc = run(
|
||||
join(path, "jobs", file),
|
||||
stdin=DEVNULL,
|
||||
stderr=STDOUT,
|
||||
env=self.__env,
|
||||
check=False,
|
||||
)
|
||||
proc = run(join(path, "jobs", file), stdin=DEVNULL, stderr=STDOUT, env=self.__env, check=False)
|
||||
ret = proc.returncode
|
||||
except BaseException:
|
||||
success = False
|
||||
self.__logger.error(
|
||||
f"Exception while executing job {name} from plugin {plugin} :\n{format_exc()}",
|
||||
)
|
||||
self.__logger.error(f"Exception while executing job {name} from plugin {plugin} :\n{format_exc()}")
|
||||
with self.__thread_lock:
|
||||
self.__job_success = False
|
||||
|
||||
if self.__job_success and ret >= 2:
|
||||
success = False
|
||||
self.__logger.error(
|
||||
f"Error while executing job {name} from plugin {plugin}",
|
||||
)
|
||||
self.__logger.error(f"Error while executing job {name} from plugin {plugin}")
|
||||
with self.__thread_lock:
|
||||
self.__job_success = False
|
||||
|
||||
|
|
@ -223,13 +190,9 @@ class JobScheduler(ApiCaller):
|
|||
err = self.__db.update_job(plugin, name, success)
|
||||
|
||||
if not err:
|
||||
self.__logger.info(
|
||||
f"Successfully updated database for the job {name} from plugin {plugin}",
|
||||
)
|
||||
self.__logger.info(f"Successfully updated database for the job {name} from plugin {plugin}")
|
||||
else:
|
||||
self.__logger.warning(
|
||||
f"Failed to update database for the job {name} from plugin {plugin}: {err}",
|
||||
)
|
||||
self.__logger.warning(f"Failed to update database for the job {name} from plugin {plugin}: {err}")
|
||||
|
||||
def setup(self):
|
||||
for plugin, jobs in self.__jobs.items():
|
||||
|
|
@ -242,9 +205,7 @@ class JobScheduler(ApiCaller):
|
|||
if every != "once":
|
||||
self.__str_to_schedule(every).do(self.__job_wrapper, path, plugin, name, file)
|
||||
except:
|
||||
self.__logger.error(
|
||||
f"Exception while scheduling jobs for plugin {plugin} : {format_exc()}",
|
||||
)
|
||||
self.__logger.error(f"Exception while scheduling jobs for plugin {plugin} : {format_exc()}")
|
||||
|
||||
def run_pending(self) -> bool:
|
||||
if self.__lock:
|
||||
|
|
@ -278,9 +239,7 @@ class JobScheduler(ApiCaller):
|
|||
success = False
|
||||
except:
|
||||
success = False
|
||||
self.__logger.error(
|
||||
f"Exception while reloading after job scheduling : {format_exc()}",
|
||||
)
|
||||
self.__logger.error(f"Exception while reloading after job scheduling : {format_exc()}")
|
||||
|
||||
if self.__lock:
|
||||
self.__lock.release()
|
||||
|
|
@ -332,8 +291,6 @@ class JobScheduler(ApiCaller):
|
|||
ret = self.run_once()
|
||||
self.setup()
|
||||
except:
|
||||
self.__logger.error(
|
||||
f"Exception while reloading scheduler {format_exc()}",
|
||||
)
|
||||
self.__logger.error(f"Exception while reloading scheduler {format_exc()}")
|
||||
return False
|
||||
return ret
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ from tarfile import open as tar_open
|
|||
from threading import Thread
|
||||
from time import sleep
|
||||
from traceback import format_exc
|
||||
from typing import Any, Dict, List, Optional, Union
|
||||
from typing import Any, Dict, List, Literal, Optional, Union
|
||||
|
||||
for deps_path in [join(sep, "usr", "share", "bunkerweb", *paths) for paths in (("deps", "python"), ("utils",), ("api",), ("db",))]:
|
||||
if deps_path not in sys_path:
|
||||
|
|
@ -34,6 +34,8 @@ RUN = True
|
|||
SCHEDULER: Optional[JobScheduler] = None
|
||||
INTEGRATION = "Linux"
|
||||
CACHE_PATH = join(sep, "var", "cache", "bunkerweb")
|
||||
EXTERNAL_PLUGINS_PATH = Path(sep, "etc", "bunkerweb", "plugins")
|
||||
PRO_PLUGINS_PATH = Path(sep, "etc", "bunkerweb", "pro", "plugins")
|
||||
SCHEDULER_TMP_ENV_PATH = Path(sep, "var", "tmp", "bunkerweb", "scheduler.env")
|
||||
SCHEDULER_TMP_ENV_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||
SCHEDULER_TMP_ENV_PATH.touch()
|
||||
|
|
@ -61,13 +63,9 @@ def handle_reload(signum, frame):
|
|||
else:
|
||||
logger.error("Reload failed")
|
||||
else:
|
||||
logger.warning(
|
||||
"Ignored reload operation because scheduler is not running ...",
|
||||
)
|
||||
logger.warning("Ignored reload operation because scheduler is not running ...")
|
||||
except:
|
||||
logger.error(
|
||||
f"Exception while reloading scheduler : {format_exc()}",
|
||||
)
|
||||
logger.error(f"Exception while reloading scheduler : {format_exc()}")
|
||||
|
||||
|
||||
signal(SIGHUP, handle_reload)
|
||||
|
|
@ -79,11 +77,7 @@ def stop(status):
|
|||
_exit(status)
|
||||
|
||||
|
||||
def generate_custom_configs(
|
||||
configs: List[Dict[str, Any]],
|
||||
*,
|
||||
original_path: Union[Path, str] = join(sep, "etc", "bunkerweb", "configs"),
|
||||
):
|
||||
def generate_custom_configs(configs: List[Dict[str, Any]], *, original_path: Union[Path, str] = join(sep, "etc", "bunkerweb", "configs")):
|
||||
if not isinstance(original_path, Path):
|
||||
original_path = Path(original_path)
|
||||
|
||||
|
|
@ -113,21 +107,16 @@ def generate_custom_configs(
|
|||
ret = SCHEDULER.send_files(original_path, "/custom_configs")
|
||||
|
||||
if not ret:
|
||||
logger.error(
|
||||
"Sending custom configs failed, configuration will not work as expected...",
|
||||
)
|
||||
logger.error("Sending custom configs failed, configuration will not work as expected...")
|
||||
|
||||
|
||||
def generate_external_plugins(
|
||||
plugins: List[Dict[str, Any]],
|
||||
*,
|
||||
original_path: Union[Path, str] = join(sep, "etc", "bunkerweb", "plugins"),
|
||||
):
|
||||
def generate_external_plugins(plugins: List[Dict[str, Any]], *, original_path: Union[Path, str] = EXTERNAL_PLUGINS_PATH):
|
||||
if not isinstance(original_path, Path):
|
||||
original_path = Path(original_path)
|
||||
pro = original_path.as_posix().endswith("/pro/plugins")
|
||||
|
||||
# Remove old external plugins files
|
||||
logger.info("Removing old external plugins files ...")
|
||||
# Remove old external/pro plugins files
|
||||
logger.info(f"Removing old {'pro ' if pro else ''}external plugins files ...")
|
||||
for file in glob(str(original_path.joinpath("*"))):
|
||||
file = Path(file)
|
||||
if file.is_symlink() or file.is_file():
|
||||
|
|
@ -136,7 +125,7 @@ def generate_external_plugins(
|
|||
rmtree(file, ignore_errors=True)
|
||||
|
||||
if plugins:
|
||||
logger.info("Generating new external plugins ...")
|
||||
logger.info(f"Generating new {'pro ' if pro else ''}external plugins ...")
|
||||
original_path.mkdir(parents=True, exist_ok=True)
|
||||
for plugin in plugins:
|
||||
tmp_path = original_path.joinpath(plugin["id"], f"{plugin['name']}.tar.gz")
|
||||
|
|
@ -151,13 +140,11 @@ def generate_external_plugins(
|
|||
chmod(job_file, st.st_mode | S_IEXEC)
|
||||
|
||||
if SCHEDULER and SCHEDULER.apis:
|
||||
logger.info("Sending plugins to BunkerWeb")
|
||||
ret = SCHEDULER.send_files(original_path, "/plugins")
|
||||
logger.info(f"Sending {'pro ' if pro else ''}external plugins to BunkerWeb")
|
||||
ret = SCHEDULER.send_files(original_path, "/pro_plugins" if original_path.as_posix().endswith("/pro/plugins") else "/plugins")
|
||||
|
||||
if not ret:
|
||||
logger.error(
|
||||
"Sending plugins failed, configuration will not work as expected...",
|
||||
)
|
||||
logger.error(f"Sending {'pro ' if pro else ''}external plugins failed, configuration will not work as expected...")
|
||||
|
||||
|
||||
def dict_to_frozenset(d):
|
||||
|
|
@ -181,9 +168,7 @@ if __name__ == "__main__":
|
|||
# Don't execute if pid file exists
|
||||
pid_path = Path(sep, "var", "run", "bunkerweb", "scheduler.pid")
|
||||
if pid_path.is_file():
|
||||
logger.error(
|
||||
"Scheduler is already running, skipping execution ...",
|
||||
)
|
||||
logger.error("Scheduler is already running, skipping execution ...")
|
||||
_exit(1)
|
||||
|
||||
# Write pid to file
|
||||
|
|
@ -193,11 +178,7 @@ if __name__ == "__main__":
|
|||
|
||||
# Parse arguments
|
||||
parser = ArgumentParser(description="Job scheduler for BunkerWeb")
|
||||
parser.add_argument(
|
||||
"--variables",
|
||||
type=str,
|
||||
help="path to the file containing environment variables",
|
||||
)
|
||||
parser.add_argument("--variables", type=str, help="path to the file containing environment variables")
|
||||
args = parser.parse_args()
|
||||
|
||||
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
|
||||
|
|
@ -226,21 +207,13 @@ if __name__ == "__main__":
|
|||
)
|
||||
env = {}
|
||||
|
||||
if INTEGRATION in (
|
||||
"Swarm",
|
||||
"Kubernetes",
|
||||
"Autoconf",
|
||||
):
|
||||
if INTEGRATION in ("Swarm", "Kubernetes", "Autoconf"):
|
||||
while not db.is_initialized():
|
||||
logger.warning(
|
||||
"Database is not initialized, retrying in 5s ...",
|
||||
)
|
||||
logger.warning("Database is not initialized, retrying in 5s ...")
|
||||
sleep(5)
|
||||
|
||||
while not db.is_autoconf_loaded():
|
||||
logger.warning(
|
||||
"Autoconf is not loaded yet in the database, retrying in 5s ...",
|
||||
)
|
||||
logger.warning("Autoconf is not loaded yet in the database, retrying in 5s ...")
|
||||
sleep(5)
|
||||
|
||||
env = db.get_config()
|
||||
|
|
@ -259,21 +232,15 @@ if __name__ == "__main__":
|
|||
check=False,
|
||||
)
|
||||
if proc.returncode != 0:
|
||||
logger.error(
|
||||
"Config saver failed, configuration will not work as expected...",
|
||||
)
|
||||
logger.error("Config saver failed, configuration will not work as expected...")
|
||||
|
||||
while not db.is_initialized():
|
||||
logger.warning(
|
||||
"Database is not initialized, retrying in 5s ...",
|
||||
)
|
||||
logger.warning("Database is not initialized, retrying in 5s ...")
|
||||
sleep(5)
|
||||
|
||||
env = db.get_config()
|
||||
while not db.is_first_config_saved() or not env:
|
||||
logger.warning(
|
||||
"Database doesn't have any config saved yet, retrying in 5s ...",
|
||||
)
|
||||
logger.warning("Database doesn't have any config saved yet, retrying in 5s ...")
|
||||
sleep(5)
|
||||
env = db.get_config()
|
||||
else:
|
||||
|
|
@ -290,9 +257,7 @@ if __name__ == "__main__":
|
|||
SCHEDULER.auto_setup()
|
||||
|
||||
if not SCHEDULER.apis:
|
||||
logger.warning(
|
||||
"No BunkerWeb API found, retrying in 5s ...",
|
||||
)
|
||||
logger.warning("No BunkerWeb API found, retrying in 5s ...")
|
||||
sleep(5)
|
||||
db.update_instances([api_to_instance(api) for api in SCHEDULER.apis])
|
||||
|
||||
|
|
@ -337,73 +302,66 @@ if __name__ == "__main__":
|
|||
if changes:
|
||||
err = db.save_custom_configs(custom_configs, "manual")
|
||||
if err:
|
||||
logger.error(
|
||||
f"Couldn't save some manually created custom configs to database: {err}",
|
||||
)
|
||||
logger.error(f"Couldn't save some manually created custom configs to database: {err}")
|
||||
|
||||
if (scheduler_first_start and db_configs) or changes:
|
||||
Thread(
|
||||
target=generate_custom_configs,
|
||||
args=(db.get_custom_configs(),),
|
||||
kwargs={"original_path": configs_path},
|
||||
).start()
|
||||
Thread(target=generate_custom_configs, args=(db.get_custom_configs(),), kwargs={"original_path": configs_path}).start()
|
||||
|
||||
del custom_configs, db_configs
|
||||
|
||||
# Check if any external plugin has been added by the user
|
||||
external_plugins = []
|
||||
db_plugins = db.get_plugins(external=True)
|
||||
plugins_dir = Path(sep, "etc", "bunkerweb", "plugins")
|
||||
for filename in glob(str(plugins_dir.joinpath("*", "plugin.json"))):
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
_dir = dirname(filename)
|
||||
plugin_content = BytesIO()
|
||||
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
|
||||
tar.add(_dir, arcname=basename(_dir), recursive=True)
|
||||
plugin_content.seek(0, 0)
|
||||
value = plugin_content.getvalue()
|
||||
def check_plugin_changes(_type: Literal["external", "pro"] = "external"):
|
||||
# Check if any external or pro plugin has been added by the user
|
||||
external_plugins = []
|
||||
db_plugins = db.get_plugins(_type=_type)
|
||||
for filename in glob(str((EXTERNAL_PLUGINS_PATH if _type == "external" else PRO_PLUGINS_PATH).joinpath("*", "plugin.json"))):
|
||||
with open(filename, "r", encoding="utf-8") as f:
|
||||
_dir = dirname(filename)
|
||||
plugin_content = BytesIO()
|
||||
with tar_open(fileobj=plugin_content, mode="w:gz", compresslevel=9) as tar:
|
||||
tar.add(_dir, arcname=basename(_dir), recursive=True)
|
||||
plugin_content.seek(0, 0)
|
||||
value = plugin_content.getvalue()
|
||||
|
||||
external_plugins.append(
|
||||
json_load(f)
|
||||
| {
|
||||
"external": True,
|
||||
"page": Path(_dir, "ui").exists(),
|
||||
"method": "manual",
|
||||
"data": value,
|
||||
"checksum": sha256(value).hexdigest(),
|
||||
}
|
||||
external_plugins.append(
|
||||
json_load(f)
|
||||
| {
|
||||
"type": _type,
|
||||
"page": Path(_dir, "ui").exists(),
|
||||
"method": "manual",
|
||||
"data": value,
|
||||
"checksum": sha256(value).hexdigest(),
|
||||
}
|
||||
)
|
||||
|
||||
tmp_external_plugins = []
|
||||
for external_plugin in deepcopy(external_plugins):
|
||||
external_plugin.pop("data", None)
|
||||
external_plugin.pop("checksum", None)
|
||||
external_plugin.pop("jobs", None)
|
||||
external_plugin.pop("method", None)
|
||||
tmp_external_plugins.append(external_plugin)
|
||||
|
||||
tmp_db_plugins = []
|
||||
for db_plugin in db_plugins.copy():
|
||||
db_plugin.pop("method", None)
|
||||
tmp_db_plugins.append(db_plugin)
|
||||
|
||||
changes = {hash(dict_to_frozenset(d)) for d in tmp_external_plugins} != {hash(dict_to_frozenset(d)) for d in tmp_db_plugins}
|
||||
|
||||
if changes:
|
||||
err = db.update_external_plugins(external_plugins, _type=_type, delete_missing=True)
|
||||
if err:
|
||||
logger.error(f"Couldn't save some manually added {_type} plugins to database: {err}")
|
||||
|
||||
if (scheduler_first_start and db_plugins) or changes:
|
||||
generate_external_plugins(
|
||||
db.get_plugins(_type=_type, with_data=True),
|
||||
original_path=EXTERNAL_PLUGINS_PATH if _type == "external" else PRO_PLUGINS_PATH,
|
||||
)
|
||||
SCHEDULER.update_jobs()
|
||||
|
||||
tmp_external_plugins = []
|
||||
for external_plugin in deepcopy(external_plugins):
|
||||
external_plugin.pop("data", None)
|
||||
external_plugin.pop("checksum", None)
|
||||
external_plugin.pop("jobs", None)
|
||||
external_plugin.pop("method", None)
|
||||
tmp_external_plugins.append(external_plugin)
|
||||
|
||||
tmp_db_plugins = []
|
||||
for db_plugin in db_plugins.copy():
|
||||
db_plugin.pop("method", None)
|
||||
tmp_db_plugins.append(db_plugin)
|
||||
|
||||
changes = {hash(dict_to_frozenset(d)) for d in tmp_external_plugins} != {hash(dict_to_frozenset(d)) for d in tmp_db_plugins}
|
||||
|
||||
if changes:
|
||||
err = db.update_external_plugins(external_plugins, delete_missing=True)
|
||||
if err:
|
||||
logger.error(
|
||||
f"Couldn't save some manually added plugins to database: {err}",
|
||||
)
|
||||
|
||||
if (scheduler_first_start and db_plugins) or changes:
|
||||
generate_external_plugins(
|
||||
db.get_plugins(external=True, with_data=True),
|
||||
original_path=plugins_dir,
|
||||
)
|
||||
SCHEDULER.update_jobs()
|
||||
|
||||
del tmp_external_plugins, external_plugins, db_plugins
|
||||
check_plugin_changes("external")
|
||||
check_plugin_changes("pro")
|
||||
|
||||
logger.info("Executing scheduler ...")
|
||||
|
||||
|
|
@ -426,9 +384,7 @@ if __name__ == "__main__":
|
|||
logger.info(f"Sending {join(sep, 'etc', 'nginx')} folder ...")
|
||||
ret = SCHEDULER.send_files(join(sep, "etc", "nginx"), "/confs")
|
||||
if not ret:
|
||||
logger.error(
|
||||
"Sending nginx configs failed, configuration will not work as expected...",
|
||||
)
|
||||
logger.error("Sending nginx configs failed, configuration will not work as expected...")
|
||||
|
||||
def send_nginx_cache():
|
||||
logger.info(f"Sending {CACHE_PATH} folder ...")
|
||||
|
|
@ -498,14 +454,9 @@ if __name__ == "__main__":
|
|||
)
|
||||
|
||||
if proc.returncode != 0:
|
||||
logger.error(
|
||||
"Config generator failed, configuration will not work as expected...",
|
||||
)
|
||||
logger.error("Config generator failed, configuration will not work as expected...")
|
||||
else:
|
||||
copy(
|
||||
str(nginx_variables_path),
|
||||
join(sep, "var", "tmp", "bunkerweb", "variables.env"),
|
||||
)
|
||||
copy(str(nginx_variables_path), join(sep, "var", "tmp", "bunkerweb", "variables.env"))
|
||||
|
||||
if SCHEDULER.apis:
|
||||
# send nginx configs
|
||||
|
|
@ -542,15 +493,14 @@ if __name__ == "__main__":
|
|||
else:
|
||||
logger.warning("No BunkerWeb instance found, skipping nginx reload ...")
|
||||
except:
|
||||
logger.error(
|
||||
f"Exception while reloading after running jobs once scheduling : {format_exc()}",
|
||||
)
|
||||
logger.error(f"Exception while reloading after running jobs once scheduling : {format_exc()}")
|
||||
|
||||
NEED_RELOAD = False
|
||||
RUN_JOBS_ONCE = False
|
||||
CONFIG_NEED_GENERATION = False
|
||||
CONFIGS_NEED_GENERATION = False
|
||||
PLUGINS_NEED_GENERATION = False
|
||||
PRO_PLUGINS_NEED_GENERATION = False
|
||||
INSTANCES_NEED_GENERATION = False
|
||||
|
||||
# infinite schedule for the jobs
|
||||
|
|
@ -567,6 +517,11 @@ if __name__ == "__main__":
|
|||
stop(1)
|
||||
|
||||
# check if the plugins have changed since last time
|
||||
if changes["pro_plugins_changed"]:
|
||||
logger.info("Pro plugins changed, generating ...")
|
||||
changes["external_plugins_changed"] = True
|
||||
PRO_PLUGINS_NEED_GENERATION = True
|
||||
|
||||
if changes["external_plugins_changed"]:
|
||||
logger.info("External plugins changed, generating ...")
|
||||
|
||||
|
|
@ -650,10 +605,12 @@ if __name__ == "__main__":
|
|||
|
||||
if PLUGINS_NEED_GENERATION:
|
||||
CHANGES.append("external_plugins")
|
||||
generate_external_plugins(
|
||||
db.get_plugins(external=True, with_data=True),
|
||||
original_path=plugins_dir,
|
||||
)
|
||||
generate_external_plugins(db.get_plugins(_type="external", with_data=True))
|
||||
SCHEDULER.update_jobs()
|
||||
|
||||
if PRO_PLUGINS_NEED_GENERATION:
|
||||
CHANGES.append("pro_plugins")
|
||||
generate_external_plugins(db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
|
||||
SCHEDULER.update_jobs()
|
||||
|
||||
if CONFIG_NEED_GENERATION:
|
||||
|
|
@ -662,7 +619,5 @@ if __name__ == "__main__":
|
|||
env["DATABASE_URI"] = db.database_uri
|
||||
|
||||
except:
|
||||
logger.error(
|
||||
f"Exception while executing scheduler : {format_exc()}",
|
||||
)
|
||||
logger.error(f"Exception while executing scheduler : {format_exc()}")
|
||||
stop(1)
|
||||
|
|
|
|||
|
|
@ -57,8 +57,8 @@ RUN apk add --no-cache bash && \
|
|||
mkdir -p /data/cache && ln -s /data/cache /var/cache/bunkerweb && \
|
||||
mkdir -p /data/lib && ln -s /data/lib /var/lib/bunkerweb && \
|
||||
mkdir -p /var/log/bunkerweb/ && \
|
||||
for dir in $(echo "configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
|
||||
for dir in $(echo "pro configs plugins") ; do mkdir -p "/data/${dir}" && ln -s "/data/${dir}" "/etc/bunkerweb/${dir}" ; done && \
|
||||
for dir in $(echo "pro/plugins configs/http configs/stream configs/server-http configs/server-stream configs/default-server-http configs/default-server-stream configs/modsec configs/modsec-crs") ; do mkdir "/data/${dir}" ; done && \
|
||||
chown -R root:ui /data && \
|
||||
chmod -R 770 /data && \
|
||||
chown -R root:ui INTEGRATION /var/cache/bunkerweb /var/lib/bunkerweb /etc/bunkerweb /var/tmp/bunkerweb /var/run/bunkerweb /var/log/bunkerweb && \
|
||||
|
|
|
|||
|
|
@ -999,21 +999,18 @@ def plugins():
|
|||
variables = deepcopy(request.form.to_dict())
|
||||
del variables["csrf_token"]
|
||||
|
||||
if variables["external"] != "True":
|
||||
flash(f"Can't delete internal plugin {variables['name']}", "error")
|
||||
if variables["type"] in ("core", "pro"):
|
||||
flash(f"Can't delete {variables['type']} plugin {variables['name']}", "error")
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
|
||||
plugins = app.config["CONFIG"].get_plugins()
|
||||
for plugin in deepcopy(plugins):
|
||||
if plugin["external"] is False or plugin["id"] == variables["name"]:
|
||||
del plugins[plugins.index(plugin)]
|
||||
for x, plugin in enumerate(deepcopy(plugins)):
|
||||
if plugin["type"] in ("core", "pro") or plugin["id"] == variables["name"]:
|
||||
del plugins[x]
|
||||
|
||||
err = db.update_external_plugins(plugins)
|
||||
if err:
|
||||
flash(
|
||||
f"Couldn't update external plugins to database: {err}",
|
||||
"error",
|
||||
)
|
||||
flash(f"Couldn't update external plugins to database: {err}", "error")
|
||||
flash(f"Deleted plugin {variables['name']} successfully")
|
||||
else:
|
||||
if not tmp_ui_path.exists() or not listdir(str(tmp_ui_path)):
|
||||
|
|
@ -1130,7 +1127,7 @@ def plugins():
|
|||
new_plugins.append(
|
||||
plugin_file
|
||||
| {
|
||||
"external": True,
|
||||
"type": "external",
|
||||
"page": "ui" in listdir(str(temp_folder_path)),
|
||||
"method": "ui",
|
||||
"data": value,
|
||||
|
|
@ -1183,7 +1180,7 @@ def plugins():
|
|||
if errors >= files_count:
|
||||
return redirect(url_for("loading", next=url_for("plugins")))
|
||||
|
||||
plugins = app.config["CONFIG"].get_plugins(external=True, with_data=True)
|
||||
plugins = app.config["CONFIG"].get_plugins(_type="external", with_data=True)
|
||||
for plugin in deepcopy(plugins):
|
||||
if plugin["id"] in new_plugins_ids:
|
||||
flash(f"Plugin {plugin['id']} already exists", "error")
|
||||
|
|
@ -1191,10 +1188,7 @@ def plugins():
|
|||
|
||||
err = db.update_external_plugins(new_plugins, delete_missing=False)
|
||||
if err:
|
||||
flash(
|
||||
f"Couldn't update external plugins to database: {err}",
|
||||
"error",
|
||||
)
|
||||
flash(f"Couldn't update external plugins to database: {err}", "error")
|
||||
|
||||
if operation:
|
||||
flash(operation)
|
||||
|
|
@ -1217,18 +1211,22 @@ def plugins():
|
|||
plugins = app.config["CONFIG"].get_plugins()
|
||||
plugins_internal = 0
|
||||
plugins_external = 0
|
||||
plugins_pro = 0
|
||||
|
||||
for plugin in plugins:
|
||||
if plugin["external"] is True:
|
||||
if plugin["type"] == "external":
|
||||
plugins_external += 1
|
||||
elif plugin["type"] == "pro":
|
||||
plugins_pro += 1
|
||||
else:
|
||||
plugins_internal += 1
|
||||
|
||||
return render_template(
|
||||
"plugins.html",
|
||||
plugins=plugins,
|
||||
plugins_internal=plugins_internal,
|
||||
plugins_external=plugins_external,
|
||||
plugins_count_internal=plugins_internal,
|
||||
plugins_count_external=plugins_external,
|
||||
plugins_count_pro=plugins_pro,
|
||||
username=current_user.get_id(),
|
||||
)
|
||||
|
||||
|
|
@ -1391,15 +1389,15 @@ def custom_plugin(plugin: str):
|
|||
is_used = True
|
||||
break
|
||||
|
||||
return render_template(
|
||||
Environment(loader=FileSystemLoader(join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")).from_string(page.decode("utf-8")),
|
||||
username=current_user.get_id(),
|
||||
current_endpoint=plugin,
|
||||
plugin=curr_plugin,
|
||||
is_used=is_used,
|
||||
is_metrics=is_metrics_on,
|
||||
**app.jinja_env.globals,
|
||||
)
|
||||
return render_template(
|
||||
Environment(loader=FileSystemLoader(join(sep, "usr", "share", "bunkerweb", "ui", "templates") + "/")).from_string(page.decode("utf-8")),
|
||||
username=current_user.get_id(),
|
||||
current_endpoint=plugin,
|
||||
plugin=curr_plugin,
|
||||
is_used=is_used,
|
||||
is_metrics=is_metrics_on,
|
||||
**app.jinja_env.globals,
|
||||
)
|
||||
|
||||
module = db.get_plugin_actions(plugin)
|
||||
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from json import loads as json_loads
|
|||
from pathlib import Path
|
||||
from re import search as re_search
|
||||
from subprocess import run, DEVNULL, STDOUT
|
||||
from typing import List, Tuple
|
||||
from typing import List, Literal, Tuple
|
||||
from uuid import uuid4
|
||||
|
||||
|
||||
|
|
@ -83,8 +83,8 @@ class Config:
|
|||
**self.__settings,
|
||||
}
|
||||
|
||||
def get_plugins(self, *, external: bool = False, with_data: bool = False) -> List[dict]:
|
||||
plugins = self.__db.get_plugins(external=external, with_data=with_data)
|
||||
def get_plugins(self, *, _type: Literal["all", "external", "pro"] = "all", with_data: bool = False) -> List[dict]:
|
||||
plugins = self.__db.get_plugins(_type=_type, with_data=with_data)
|
||||
plugins.sort(key=itemgetter("name"))
|
||||
|
||||
general_plugin = None
|
||||
|
|
|
|||
|
|
@ -214,7 +214,7 @@ class Filter {
|
|||
if (this.lastType === "all") return;
|
||||
for (let i = 0; i < logs.length; i++) {
|
||||
const el = logs[i];
|
||||
const type = el.getAttribute(`data-${this.prefix}-external`).trim();
|
||||
const type = el.getAttribute(`data-${this.prefix}-type`).trim();
|
||||
if (type !== this.lastType) el.classList.add("hidden");
|
||||
}
|
||||
}
|
||||
|
|
@ -491,8 +491,8 @@ class Modal {
|
|||
this.modalTxt.textContent = `Are you sure you want to delete ${elName} ?`;
|
||||
//external
|
||||
const isExternal = el
|
||||
.closest("[data-plugins-external]")
|
||||
.getAttribute("data-plugins-external")
|
||||
.closest("[data-plugins-type]")
|
||||
.getAttribute("data-plugins-type")
|
||||
.trim()
|
||||
.includes("external")
|
||||
? "True"
|
||||
|
|
|
|||
|
|
@ -1,7 +1,6 @@
|
|||
class Filter {
|
||||
constructor(prefix = "reports") {
|
||||
this.prefix = prefix;
|
||||
this.container = document.querySelector(`[data-${this.prefix}-filter]`);
|
||||
this.keyInp = document.querySelector("input#keyword");
|
||||
this.methodValue = "all";
|
||||
this.statusValue = "all";
|
||||
|
|
@ -11,6 +10,11 @@ class Filter {
|
|||
}
|
||||
|
||||
initHandler() {
|
||||
this.container =
|
||||
document.querySelector(`[data-${this.prefix}-filter]`) || null;
|
||||
|
||||
if (!this.container) return;
|
||||
|
||||
//METHOD HANDLER
|
||||
this.container.addEventListener("click", (e) => {
|
||||
try {
|
||||
|
|
|
|||
63
src/ui/templates/plugins.html
vendored
63
src/ui/templates/plugins.html
vendored
|
|
@ -1,5 +1,4 @@
|
|||
{% extends "base.html" %} {% block content %}{% set current_endpoint =
|
||||
url_for(request.endpoint)[1:].split("/")[-1].strip().replace('_', '-') %} {%
|
||||
{% extends "base.html" %} {% block content %}{%
|
||||
include "plugins_modal.html" %}
|
||||
|
||||
<!-- info -->
|
||||
|
|
@ -28,7 +27,7 @@ include "plugins_modal.html" %}
|
|||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{plugins_internal}}
|
||||
{{plugins_count_internal}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex items-center my-4">
|
||||
|
|
@ -40,7 +39,19 @@ include "plugins_modal.html" %}
|
|||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{plugins_external}}
|
||||
{{plugins_count_external}}
|
||||
</p>
|
||||
</div>
|
||||
<div class="mx-1 flex items-center my-4">
|
||||
<p
|
||||
class="transition duration-300 ease-in-out font-bold mb-0 font-sans text-sm leading-normal uppercase dark:text-gray-500 dark:opacity-80"
|
||||
>
|
||||
PRO PLUGINS
|
||||
</p>
|
||||
<p
|
||||
class="transition duration-300 ease-in-out pl-2 col-span-1 mb-0 font-sans text-sm font-semibold leading-normal uppercase dark:text-white dark:opacity-80"
|
||||
>
|
||||
{{plugins_count_pro}}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
|
@ -48,7 +59,7 @@ include "plugins_modal.html" %}
|
|||
|
||||
<!-- upload layout -->
|
||||
<div
|
||||
data-{{current_endpoint}}-upload
|
||||
data-plugins-upload
|
||||
class="p-4 col-span-12 md:col-span-7 2xl:col-span-4 grid grid-cols-12 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="col-span-12 mb-4 font-bold dark:text-white/90">UPLOAD / RELOAD</h5>
|
||||
|
|
@ -100,7 +111,7 @@ include "plugins_modal.html" %}
|
|||
|
||||
<!-- filter -->
|
||||
<div
|
||||
data-{{current_endpoint}}-filter
|
||||
data-plugins-filter
|
||||
class="h-fit p-4 col-span-12 md:col-span-6 2xl:col-span-4 relative min-w-0 break-words bg-white shadow-xl dark:bg-slate-850 dark:shadow-dark-xl rounded-2xl bg-clip-border"
|
||||
>
|
||||
<h5 class="mb-2 font-bold dark:text-white/90">FILTER</h5>
|
||||
|
|
@ -132,7 +143,7 @@ include "plugins_modal.html" %}
|
|||
Select types
|
||||
</h5>
|
||||
<button
|
||||
data-{{current_endpoint}}-setting-select="types"
|
||||
data-plugins-setting-select="types"
|
||||
aria-controls="filter-types"
|
||||
class="disabled:opacity-75 dark:disabled:text-gray-300 disabled:text-gray-700 disabled:bg-gray-400 disabled:border-gray-400 dark:disabled:bg-gray-800 dark:disabled:border-gray-800 duration-300 ease-in-out dark:opacity-90 dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300 focus:border-green-500 flex justify-between align-middle items-center text-left text-sm leading-5.6 ease w-full rounded-lg border border-solid border-gray-300 bg-white bg-clip-padding px-1.5 py-1 md:px-3 font-normal text-gray-700 transition-all placeholder:text-gray-500"
|
||||
>
|
||||
|
|
@ -140,12 +151,12 @@ include "plugins_modal.html" %}
|
|||
aria-description="current type"
|
||||
id="types"
|
||||
data-name="types"
|
||||
data-{{current_endpoint}}-setting-select-text="types"
|
||||
data-plugins-setting-select-text="types"
|
||||
>all</span
|
||||
>
|
||||
<!-- chevron -->
|
||||
<svg
|
||||
data-{{current_endpoint}}-setting-select="types"
|
||||
data-plugins-setting-select="types"
|
||||
class="transition-transform h-4 w-4 fill-gray-500"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 512 512"
|
||||
|
|
@ -160,12 +171,12 @@ include "plugins_modal.html" %}
|
|||
<div
|
||||
id="filter-types"
|
||||
role="listbox"
|
||||
data-{{current_endpoint}}-setting-select-dropdown="types"
|
||||
data-plugins-setting-select-dropdown="types"
|
||||
class="hidden z-100 absolute h-full flex-col w-full translate-y-16"
|
||||
>
|
||||
<button
|
||||
role="option"
|
||||
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
|
||||
data-plugins-setting-select-dropdown-btn="types"
|
||||
value="all"
|
||||
class="border-t rounded-t border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:text-gray-300 dark:bg-primary bg-primary text-gray-300"
|
||||
>
|
||||
|
|
@ -173,20 +184,28 @@ include "plugins_modal.html" %}
|
|||
</button>
|
||||
<button
|
||||
role="option"
|
||||
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
|
||||
value="internal"
|
||||
data-plugins-setting-select-dropdown-btn="types"
|
||||
value="core"
|
||||
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
|
||||
>
|
||||
internal
|
||||
core
|
||||
</button>
|
||||
<button
|
||||
role="option"
|
||||
data-{{current_endpoint}}-setting-select-dropdown-btn="types"
|
||||
data-plugins-setting-select-dropdown-btn="types"
|
||||
value="external"
|
||||
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
|
||||
>
|
||||
external
|
||||
</button>
|
||||
<button
|
||||
role="option"
|
||||
data-plugins-setting-select-dropdown-btn="types"
|
||||
value="pro"
|
||||
class="border-b border-l border-r border-gray-300 dark:hover:brightness-90 hover:brightness-90 bg-white text-gray-700 my-0 relative py-2 px-3 text-left align-middle transition-all rounded-none cursor-pointer leading-normal text-sm ease-in tracking-tight-rem dark:border-slate-600 dark:bg-slate-700 dark:text-gray-300"
|
||||
>
|
||||
pro
|
||||
</button>
|
||||
</div>
|
||||
<!-- end dropdown-->
|
||||
</div>
|
||||
|
|
@ -200,14 +219,14 @@ include "plugins_modal.html" %}
|
|||
>
|
||||
<h5 class="mb-4 mt-2 font-bold dark:text-white/90 mx-2">LIST</h5>
|
||||
|
||||
<div data-{{current_endpoint}}-list class="grid grid-cols-12 gap-3">
|
||||
<div data-plugins-list class="grid grid-cols-12 gap-3">
|
||||
{% for plugin in plugins %}
|
||||
<div
|
||||
data-{{current_endpoint}}-external="{% if plugin['external'] %} external {% else %} internal {% endif %}"
|
||||
data-plugins-type="{{plugin['type']}}"
|
||||
class="py-3 min-h-12 relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3 p-1 flex justify-between items-center transition rounded bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800"
|
||||
>
|
||||
<p
|
||||
data-{{current_endpoint}}-content
|
||||
data-plugins-content
|
||||
class="ml-3 mr-2 break-words mb-0 transition duration-300 ease-in-out dark:opacity-90 text-left text-sm md:text-base text-slate-700 dark:text-gray-200"
|
||||
>
|
||||
{{plugin['name']}}
|
||||
|
|
@ -230,9 +249,9 @@ include "plugins_modal.html" %}
|
|||
</svg>
|
||||
</a>
|
||||
{%endif%}
|
||||
{% if plugin['external'] %}
|
||||
{% if plugin['type'] == "external" %}
|
||||
<button
|
||||
data-{{current_endpoint}}-action="delete"
|
||||
data-plugins-action="delete"
|
||||
name="{{plugin['id']}}"
|
||||
aria-label="delete plugin"
|
||||
class="z-20 mx-2 inline-block font-bold text-left text-white uppercase align-middle transition-all cursor-pointer text-xs ease-in tracking-tight-rem hover:-translate-y-px"
|
||||
|
|
@ -254,11 +273,11 @@ include "plugins_modal.html" %}
|
|||
{% if plugins_pro %}
|
||||
{% for plugin in plugins_pro %}
|
||||
<div
|
||||
data-{{current_endpoint}}-external="external"
|
||||
data-plugins-type="{{plugin['type']}}"
|
||||
class="py-3 min-h-12 relative col-span-12 sm:col-span-6 2xl:col-span-4 3xl:col-span-3 p-1 flex justify-between items-center transition rounded {% if is_plugin_pro %}bg-gray-100 hover:bg-gray-300 dark:bg-slate-700 dark:hover:bg-slate-800{% else %} bg-gray-100 dark:bg-slate-700 {% endif %}"
|
||||
>
|
||||
<p
|
||||
data-{{current_endpoint}}-content
|
||||
data-plugins-content
|
||||
class="{% if not is_plugin_pro %} opacity-80 dark:opacity-60{% endif%} ml-3 mr-2 break-words mb-0 transition duration-300 ease-in-out text-left text-sm md:text-base text-slate-700 dark:text-gray-200"
|
||||
>
|
||||
{{plugin['name']}}
|
||||
|
|
|
|||
|
|
@ -282,7 +282,7 @@ try:
|
|||
"description": "The general settings for the server",
|
||||
"version": "0.1",
|
||||
"stream": "partial",
|
||||
"external": False,
|
||||
"type": "core",
|
||||
"checked": False,
|
||||
"page_checked": True,
|
||||
"settings": global_settings,
|
||||
|
|
@ -316,27 +316,27 @@ try:
|
|||
Plugins.description,
|
||||
Plugins.version,
|
||||
Plugins.stream,
|
||||
Plugins.external,
|
||||
Plugins.type,
|
||||
Plugins.method,
|
||||
)
|
||||
.all()
|
||||
)
|
||||
|
||||
for plugin in plugins:
|
||||
if not plugin.external and plugin.id in core_plugins:
|
||||
if plugin.type == "core" and plugin.id in core_plugins:
|
||||
current_plugin = core_plugins
|
||||
elif plugin.external and plugin.id in external_plugins:
|
||||
elif plugin.type == "external" and plugin.id in external_plugins:
|
||||
current_plugin = external_plugins
|
||||
else:
|
||||
print(
|
||||
f"❌ The {'external' if plugin.external else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but should not be, exiting ...",
|
||||
f"❌ The {'external' if plugin.type == 'external' else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but should not be, exiting ...: {plugin}",
|
||||
flush=True,
|
||||
)
|
||||
exit(1)
|
||||
|
||||
if plugin.name != current_plugin[plugin.id]["name"] or plugin.description != current_plugin[plugin.id]["description"] or plugin.version != current_plugin[plugin.id]["version"] or plugin.stream != current_plugin[plugin.id]["stream"]:
|
||||
print(
|
||||
f"❌ The {'external' if plugin.external else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
f"❌ The {'external' if plugin.type == 'external' else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
+ f"{dumps({'name': plugin.name, 'description': plugin.description, 'version': plugin.version, 'stream': plugin.stream})}"
|
||||
+ f" (database) != {dumps({'name': current_plugin[plugin.id]['name'], 'description': current_plugin[plugin.id]['description'], 'version': current_plugin[plugin.id]['version'], 'stream': current_plugin[plugin.id]['stream']})} (file)", # noqa: E501
|
||||
flush=True,
|
||||
|
|
@ -357,7 +357,7 @@ try:
|
|||
or setting.multiple != current_plugin[plugin.id]["settings"][setting.id].get("multiple", None)
|
||||
):
|
||||
print(
|
||||
f"❌ The {'external' if plugin.external else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
f"❌ The {'external' if plugin.type == 'external' else 'core'} plugin {plugin.name} (id: {plugin.id}) is in the database but is not correct, exiting ...\n"
|
||||
+ f"{dumps({'default': setting.default, 'help': setting.help, 'label': setting.label, 'regex': setting.regex, 'type': setting.type})}"
|
||||
+ f" (database) != {dumps({'default': current_plugin[plugin.id]['settings'][setting.id]['default'], 'help': current_plugin[plugin.id]['settings'][setting.id]['help'], 'label': current_plugin[plugin.id]['settings'][setting.id]['label'], 'regex': current_plugin[plugin.id]['settings'][setting.id]['regex'], 'type': current_plugin[plugin.id]['settings'][setting.id]['type']})} (file)", # noqa: E501
|
||||
flush=True,
|
||||
|
|
|
|||
Loading…
Reference in a new issue