Made the UI independent + update job download plugins

This commit is contained in:
Théophile Diot 2023-01-19 16:15:26 +01:00
parent 44ce5381c2
commit e6cb5b0b09
No known key found for this signature in database
GPG Key ID: E752C80DB72BB014
7 changed files with 452 additions and 185 deletions

View File

@ -1,14 +1,14 @@
#!/usr/bin/python3
from io import BytesIO
from os import getenv, listdir, makedirs, chmod, stat, _exit
from os.path import isfile, dirname
from os import getenv, listdir, makedirs, chmod, stat, _exit, walk
from os.path import join, isfile, dirname
from stat import S_IEXEC
from sys import exit as sys_exit, path as sys_path
from uuid import uuid4
from glob import glob
from json import load, loads
from shutil import copytree, rmtree
from shutil import chown, copytree, rmtree
from traceback import format_exc
from zipfile import ZipFile
@ -97,6 +97,7 @@ try:
continue
external_plugins = []
external_plugins_ids = []
for plugin in listdir("/etc/bunkerweb/plugins"):
with open(
f"/etc/bunkerweb/plugins/{plugin}/plugin.json",
@ -105,6 +106,18 @@ try:
plugin_file = load(f)
external_plugins.append(plugin_file)
external_plugins_ids.append(plugin_file["id"])
db_plugins = db.get_plugins()
for plugin in db_plugins:
if plugin["external"] is True and plugin["id"] not in external_plugins_ids:
external_plugins.append(plugin)
# Fix permissions for the certificates
for root, dirs, files in walk("/data/plugins", topdown=False):
for name in files + dirs:
chown(join(root, name), "root", 101)
chmod(join(root, name), 0o770)
if external_plugins:
err = db.update_external_plugins(external_plugins)

View File

@ -1111,10 +1111,14 @@ class Database:
Jobs.name == job["name"]
).update(updates)
if exists(f"/usr/share/bunkerweb/core/{plugin['id']}/ui"):
if {"template.html", "actions.py"}.issubset(
listdir(f"/usr/share/bunkerweb/core/{plugin['id']}/ui")
):
path_ui = (
f"/var/tmp/bunkerweb/ui/{plugin['id']}/ui"
if exists(f"/var/tmp/bunkerweb/ui/{plugin['id']}/ui")
else f"/etc/bunkerweb/plugins/{plugin['id']}/ui"
)
if exists(path_ui):
if {"template.html", "actions.py"}.issubset(listdir(path_ui)):
db_plugin_page = (
session.query(Plugin_pages)
.with_entities(
@ -1127,12 +1131,12 @@ class Database:
if db_plugin_page is None:
with open(
f"/usr/share/bunkerweb/core/{plugin['id']}/ui/template.html",
f"{path_ui}/template.html",
"r",
) as file:
template = file.read().encode("utf-8")
with open(
f"/usr/share/bunkerweb/core/{plugin['id']}/ui/actions.py",
f"{path_ui}/actions.py",
"r",
) as file:
actions = file.read().encode("utf-8")
@ -1149,18 +1153,16 @@ class Database:
else: # TODO test this
updates = {}
template_checksum = file_hash(
f"/usr/share/bunkerweb/core/{plugin['id']}/ui/template.html"
)
actions_checksum = file_hash(
f"/usr/share/bunkerweb/core/{plugin['id']}/ui/actions.py"
f"{path_ui}/template.html"
)
actions_checksum = file_hash(f"{path_ui}/actions.py")
if (
template_checksum
!= db_plugin_page.template_checksum
):
with open(
f"/usr/share/bunkerweb/core/{plugin['id']}/ui/template.html",
f"{path_ui}/template.html",
"r",
) as file:
updates.update(
@ -1174,7 +1176,7 @@ class Database:
if actions_checksum != db_plugin_page.actions_checksum:
with open(
f"/usr/share/bunkerweb/core/{plugin['id']}/ui/actions.py",
f"{path_ui}/actions.py",
"r",
) as file:
updates.update(
@ -1258,6 +1260,79 @@ class Database:
return ""
def get_plugins(self) -> List[Dict[str, Any]]:
"""Get plugins."""
plugins = []
with self.__db_session() as session:
for plugin in (
session.query(Plugins)
.with_entities(
Plugins.id,
Plugins.order,
Plugins.name,
Plugins.description,
Plugins.version,
Plugins.external,
)
.order_by(Plugins.order)
.all()
):
page = (
session.query(Plugin_pages)
.with_entities(Plugin_pages.id)
.filter_by(plugin_id=plugin.id)
.first()
)
data = {
"id": plugin.id,
"order": plugin.order,
"name": plugin.name,
"description": plugin.description,
"version": plugin.version,
"external": plugin.external,
"page": page is not None,
"settings": {},
}
for setting in (
session.query(Settings)
.with_entities(
Settings.id,
Settings.context,
Settings.default,
Settings.help,
Settings.name,
Settings.label,
Settings.regex,
Settings.type,
Settings.multiple,
)
.filter_by(plugin_id=plugin.id)
.all()
):
data["settings"][setting.id] = {
"context": setting.context,
"default": setting.default,
"help": setting.help,
"id": setting.name,
"label": setting.label,
"regex": setting.regex,
"type": setting.type,
} | ({"multiple": setting.multiple} if setting.multiple else {})
if setting.type == "select":
data["settings"][setting.id]["select"] = [
select.value
for select in session.query(Selects)
.with_entities(Selects.value)
.filter_by(setting_id=setting.id)
.all()
]
plugins.append(data)
return plugins
def get_plugins_errors(self) -> int:
"""Get plugins errors."""
with self.__db_session() as session:
@ -1381,3 +1456,33 @@ class Database:
.all()
)
]
def get_plugin_actions(self, plugin: str) -> Optional[Any]:
"""get actions file for the plugin"""
with self.__db_session() as session:
page = (
session.query(Plugin_pages)
.with_entities(Plugin_pages.actions_file)
.filter_by(plugin_id=plugin)
.first()
)
if page is None:
return None
return page.actions_file
def get_plugin_template(self, plugin: str) -> Optional[Any]:
"""get template file for the plugin"""
with self.__db_session() as session:
page = (
session.query(Plugin_pages)
.with_entities(Plugin_pages.template_file)
.filter_by(plugin_id=plugin)
.first()
)
if page is None:
return None
return page.template_file

View File

@ -61,12 +61,10 @@ class Plugins(Base):
external = Column(Boolean, default=False, nullable=False)
settings = relationship(
"Settings", back_populates="plugin", cascade="all, delete, delete-orphan"
"Settings", back_populates="plugin", cascade="all, delete-orphan"
)
jobs = relationship(
"Jobs", back_populates="plugin", cascade="all, delete, delete-orphan"
)
pages = relationship("Plugin_pages", back_populates="plugin", cascade="all, delete")
jobs = relationship("Jobs", back_populates="plugin", cascade="all, delete-orphan")
pages = relationship("Plugin_pages", back_populates="plugin", cascade="all")
class Settings(Base):
@ -81,7 +79,7 @@ class Settings(Base):
name = Column(String(256), primary_key=True)
plugin_id = Column(
String(64),
ForeignKey("plugins.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("plugins.id", onupdate="cascade", ondelete="cascade"),
nullable=False,
)
context = Column(CONTEXTS_ENUM, nullable=False)
@ -92,12 +90,12 @@ class Settings(Base):
type = Column(SETTINGS_TYPES_ENUM, nullable=False)
multiple = Column(String(128), nullable=True)
selects = relationship("Selects", back_populates="setting", cascade="all, delete")
selects = relationship("Selects", back_populates="setting", cascade="all")
services = relationship(
"Services_settings", back_populates="setting", cascade="all, delete"
"Services_settings", back_populates="setting", cascade="all"
)
global_value = relationship(
"Global_values", back_populates="setting", cascade="all, delete"
"Global_values", back_populates="setting", cascade="all"
)
plugin = relationship("Plugins", back_populates="settings")
@ -107,7 +105,7 @@ class Global_values(Base):
setting_id = Column(
String(256),
ForeignKey("settings.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("settings.id", onupdate="cascade", ondelete="cascade"),
primary_key=True,
)
value = Column(String(4096), nullable=False)
@ -124,14 +122,12 @@ class Services(Base):
method = Column(METHODS_ENUM, nullable=False)
settings = relationship(
"Services_settings", back_populates="service", cascade="all, delete"
"Services_settings", back_populates="service", cascade="all"
)
custom_configs = relationship(
"Custom_configs", back_populates="service", cascade="all, delete"
)
jobs_cache = relationship(
"Jobs_cache", back_populates="service", cascade="all, delete"
"Custom_configs", back_populates="service", cascade="all"
)
jobs_cache = relationship("Jobs_cache", back_populates="service", cascade="all")
class Services_settings(Base):
@ -139,12 +135,12 @@ class Services_settings(Base):
service_id = Column(
String(64),
ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("services.id", onupdate="cascade", ondelete="cascade"),
primary_key=True,
)
setting_id = Column(
String(256),
ForeignKey("settings.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("settings.id", onupdate="cascade", ondelete="cascade"),
primary_key=True,
)
value = Column(String(4096), nullable=False)
@ -162,7 +158,7 @@ class Jobs(Base):
name = Column(String(128), primary_key=True)
plugin_id = Column(
String(64),
ForeignKey("plugins.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("plugins.id", onupdate="cascade", ondelete="cascade"),
)
file_name = Column(String(256), nullable=False)
every = Column(SCHEDULES_ENUM, nullable=False)
@ -171,7 +167,7 @@ class Jobs(Base):
last_run = Column(DateTime, nullable=True)
plugin = relationship("Plugins", back_populates="jobs")
cache = relationship("Jobs_cache", back_populates="job", cascade="all, delete")
cache = relationship("Jobs_cache", back_populates="job", cascade="all")
class Plugin_pages(Base):
@ -184,7 +180,7 @@ class Plugin_pages(Base):
)
plugin_id = Column(
String(64),
ForeignKey("plugins.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("plugins.id", onupdate="cascade", ondelete="cascade"),
nullable=False,
)
template_file = Column(LargeBinary(length=(2**32) - 1), nullable=False)
@ -206,12 +202,12 @@ class Jobs_cache(Base):
)
job_name = Column(
String(128),
ForeignKey("jobs.name", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("jobs.name", onupdate="cascade", ondelete="cascade"),
nullable=False,
)
service_id = Column(
String(64),
ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("services.id", onupdate="cascade", ondelete="cascade"),
nullable=True,
)
file_name = Column(
@ -237,7 +233,7 @@ class Custom_configs(Base):
)
service_id = Column(
String(64),
ForeignKey("services.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("services.id", onupdate="cascade", ondelete="cascade"),
nullable=True,
)
type = Column(CUSTOM_CONFIGS_TYPES_ENUM, nullable=False)
@ -254,7 +250,7 @@ class Selects(Base):
setting_id = Column(
String(256),
ForeignKey("settings.id", onupdate="CASCADE", ondelete="CASCADE"),
ForeignKey("settings.id", onupdate="cascade", ondelete="cascade"),
primary_key=True,
)
value = Column(String(256), primary_key=True)

View File

@ -14,9 +14,11 @@ class Configurator:
self,
settings: str,
core: Union[str, dict],
plugins: str,
plugins: Union[str, dict],
variables: Union[str, dict],
logger: Logger,
*,
plugins_settings: list = None,
):
self.__logger = logger
self.__settings = self.__load_settings(settings)
@ -26,8 +28,12 @@ class Configurator:
else:
self.__core = core
self.__plugins_settings = []
self.__plugins = self.__load_plugins(plugins, "plugins")
self.__plugins_settings = plugins_settings or []
if isinstance(plugins, str):
self.__plugins = self.__load_plugins(plugins, "plugins")
else:
self.__plugins = plugins
if isinstance(variables, str):
self.__variables = self.__load_variables(variables)

View File

@ -145,6 +145,18 @@ if __name__ == "__main__":
db = None
apis = []
plugins = args.plugins
plugins_settings = None
if not exists("/usr/sbin/nginx") and args.method == "ui":
db = Database(logger)
plugins = {}
plugins_settings = []
for plugin in db.get_plugins():
del plugin["page"]
del plugin["external"]
plugins_settings.append(plugin)
plugins.update(plugin["settings"])
# Check existences and permissions
logger.info("Checking arguments ...")
files = [args.settings] + ([args.variables] if args.variables else [])
@ -200,7 +212,12 @@ if __name__ == "__main__":
# Compute the config
logger.info("Computing config ...")
config = Configurator(
args.settings, core_settings, args.plugins, args.variables, logger
args.settings,
core_settings,
plugins,
args.variables,
logger,
plugins_settings=plugins_settings,
)
config_files = config.get_config()
custom_confs = [
@ -288,7 +305,12 @@ if __name__ == "__main__":
if config_files is None:
logger.info("Computing config ...")
config = Configurator(
args.settings, core_settings, args.plugins, tmp_config, logger
args.settings,
core_settings,
plugins,
tmp_config,
logger,
plugins_settings=plugins_settings,
)
config_files = config.get_config()

View File

@ -1,7 +1,9 @@
from contextlib import suppress
from importlib.machinery import SourceFileLoader, SourcelessFileLoader
from io import BytesIO
from pathlib import Path
from signal import SIGINT, signal, SIGTERM
from tempfile import NamedTemporaryFile
from bs4 import BeautifulSoup
from copy import deepcopy
from datetime import datetime, timedelta, timezone
@ -688,25 +690,40 @@ def plugins():
variables = deepcopy(request.form.to_dict())
del variables["csrf_token"]
if variables["external"] == "false":
if variables["external"] != "True":
flash(f"Can't delete internal plugin {variables['name']}", "error")
return redirect(url_for("loading", next=url_for("plugins"))), 500
variables["path"] = f"/etc/bunkerweb/plugins/{variables['name']}"
if not exists("/usr/sbin/nginx"):
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)]
operation = app.config["CONFIGFILES"].check_path(
variables["path"], "/etc/bunkerweb/plugins/"
)
err = db.update_external_plugins(plugins)
if err:
flash(
f"Couldn't update external plugins to database: {err}",
"error",
)
else:
variables["path"] = f"/etc/bunkerweb/plugins/{variables['name']}"
if operation:
flash(operation, "error")
return redirect(url_for("loading", next=url_for("plugins"))), 500
operation = app.config["CONFIGFILES"].check_path(
variables["path"], "/etc/bunkerweb/plugins/"
)
operation, error = app.config["CONFIGFILES"].delete_path(variables["path"])
if operation:
flash(operation, "error")
return redirect(url_for("loading", next=url_for("plugins"))), 500
if error:
flash(operation, "error")
return redirect(url_for("loading", next=url_for("plugins")))
operation, error = app.config["CONFIGFILES"].delete_path(
variables["path"]
)
if error:
flash(operation, "error")
return redirect(url_for("loading", next=url_for("plugins")))
else:
if not exists("/var/tmp/bunkerweb/ui") or not listdir(
"/var/tmp/bunkerweb/ui"
@ -758,13 +775,33 @@ def plugins():
)
raise Exception
if exists(f"/etc/bunkerweb/plugins/{folder_name}"):
raise FileExistsError
if not exists("/usr/sbin/nginx"):
plugins = app.config["CONFIG"].get_plugins()
for plugin in deepcopy(plugins):
if plugin["id"] == folder_name:
raise FileExistsError
elif plugin["external"] is False:
del plugins[plugins.index(plugin)]
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
plugins.append(plugin_file)
err = db.update_external_plugins(plugins)
if err:
error = 1
flash(
f"Couldn't update external plugins to database: {err}",
"error",
)
raise Exception
else:
if exists(
f"/etc/bunkerweb/plugins/{folder_name}"
):
raise FileExistsError
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
except KeyError:
zip_file.extractall(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}"
@ -812,13 +849,33 @@ def plugins():
)
raise Exception
if exists(f"/etc/bunkerweb/plugins/{folder_name}"):
raise FileExistsError
if not exists("/usr/sbin/nginx"):
plugins = app.config["CONFIG"].get_plugins()
for plugin in deepcopy(plugins):
if plugin["id"] == folder_name:
raise FileExistsError
elif plugin["external"] is False:
del plugins[plugins.index(plugin)]
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
plugins.append(plugin_file)
err = db.update_external_plugins(plugins)
if err:
error = 1
flash(
f"Couldn't update external plugins to database: {err}",
"error",
)
raise Exception
else:
if exists(
f"/etc/bunkerweb/plugins/{folder_name}"
):
raise FileExistsError
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
except BadZipFile:
errors += 1
error = 1
@ -861,13 +918,33 @@ def plugins():
)
raise Exception
if exists(f"/etc/bunkerweb/plugins/{folder_name}"):
raise FileExistsError
if not exists("/usr/sbin/nginx"):
plugins = app.config["CONFIG"].get_plugins()
for plugin in deepcopy(plugins):
if plugin["id"] == folder_name:
raise FileExistsError
elif plugin["external"] is False:
del plugins[plugins.index(plugin)]
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
plugins.append(plugin_file)
err = db.update_external_plugins(plugins)
if err:
error = 1
flash(
f"Couldn't update external plugins to database: {err}",
"error",
)
raise Exception
else:
if exists(
f"/etc/bunkerweb/plugins/{folder_name}"
):
raise FileExistsError
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
except KeyError:
tar_file.extractall(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}",
@ -915,13 +992,33 @@ def plugins():
)
raise Exception
if exists(f"/etc/bunkerweb/plugins/{folder_name}"):
raise FileExistsError
if not exists("/usr/sbin/nginx"):
plugins = app.config["CONFIG"].get_plugins()
for plugin in deepcopy(plugins):
if plugin["id"] == folder_name:
raise FileExistsError
elif plugin["external"] is False:
del plugins[plugins.index(plugin)]
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
plugins.append(plugin_file)
err = db.update_external_plugins(plugins)
if err:
error = 1
flash(
f"Couldn't update external plugins to database: {err}",
"error",
)
raise Exception
else:
if exists(
f"/etc/bunkerweb/plugins/{folder_name}"
):
raise FileExistsError
copytree(
f"/var/tmp/bunkerweb/ui/{temp_folder_name}/{dirs[0]}",
f"/etc/bunkerweb/plugins/{folder_name}",
)
except ReadError:
errors += 1
error = 1
@ -993,14 +1090,12 @@ def plugins():
# Fix permissions for plugins folders
for root, dirs, files in walk("/etc/bunkerweb/plugins", topdown=False):
for name in files + dirs:
chown(join(root, name), 101, 101)
chown(join(root, name), "root", 101)
chmod(join(root, name), 0o770)
if operation:
flash(operation)
app.config["CONFIG"].reload_plugins()
# Reload instances
app.config["RELOADING"] = True
Thread(
@ -1023,19 +1118,28 @@ def plugins():
if request.args.get("plugin_id", False):
plugin_id = request.args.get("plugin_id")
page_path = ""
template = None
if exists(f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html"):
page_path = f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html"
elif exists(f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html"):
page_path = f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html"
if not exists("/usr/sbin/nginx"):
page = db.get_plugin_template(plugin_id)
if page is not None:
template = Template(page.decode("utf-8"))
else:
flash(f"Plugin {plugin_id} not found", "error")
page_path = ""
if page_path:
with open(page_path, "r") as f:
template = Template(f.read())
if exists(f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html"):
page_path = f"/etc/bunkerweb/plugins/{plugin_id}/ui/template.html"
elif exists(f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html"):
page_path = f"/usr/share/bunkerweb/core/{plugin_id}/ui/template.html"
else:
flash(f"Plugin {plugin_id} not found", "error")
if page_path:
with open(page_path, "r") as f:
template = Template(f.read())
if template is not None:
return template.render(
csrf_token=generate_csrf,
url_for=url_for,
@ -1047,7 +1151,6 @@ def plugins():
),
)
app.config["CONFIG"].reload_plugins()
plugins = app.config["CONFIG"].get_plugins()
plugins_internal = 0
plugins_external = 0
@ -1096,33 +1199,66 @@ def custom_plugin(plugin):
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
if not exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") and not exists(
f"/usr/share/bunkerweb/core/{plugin}/ui/actions.py"
):
flash(
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
"error",
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
if not exists("/usr/sbin/nginx"):
module = db.get_plugin_actions(plugin)
# Add the custom plugin to sys.path
sys_path.append(
(
"/etc/bunkerweb/plugins"
if exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py")
else "/usr/share/bunkerweb/core"
if module is None:
flash(
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
"error",
)
return redirect(
url_for("loading", next=url_for("plugins", plugin_id=plugin))
)
try:
# Try to import the custom plugin
with NamedTemporaryFile(mode="wb", suffix=".py", delete=True) as temp:
temp.write(module)
temp.flush()
temp.seek(0)
loader = SourceFileLoader("actions", temp.name)
actions = loader.load_module()
except:
flash(
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
"error",
)
return redirect(
url_for("loading", next=url_for("plugins", plugin_id=plugin))
)
else:
if not exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py") and not exists(
f"/usr/share/bunkerweb/core/{plugin}/ui/actions.py"
):
flash(
f"The <i>actions.py</i> file for the plugin <b>{plugin}</b> does not exist",
"error",
)
return redirect(
url_for("loading", next=url_for("plugins", plugin_id=plugin))
)
# Add the custom plugin to sys.path
sys_path.append(
(
"/etc/bunkerweb/plugins"
if exists(f"/etc/bunkerweb/plugins/{plugin}/ui/actions.py")
else "/usr/share/bunkerweb/core"
)
+ f"/{plugin}/ui/"
)
+ f"/{plugin}/ui/"
)
try:
# Try to import the custom plugin
import actions
except:
flash(
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
"error",
)
return redirect(url_for("loading", next=url_for("plugins", plugin_id=plugin)))
try:
# Try to import the custom plugin
import actions
except:
flash(
f"An error occurred while importing the plugin <b>{plugin}</b>:<br/>{format_exc()}",
"error",
)
return redirect(
url_for("loading", next=url_for("plugins", plugin_id=plugin))
)
error = False
res = None
@ -1145,10 +1281,11 @@ def custom_plugin(plugin):
)
error = True
finally:
# Remove the custom plugin from the shared library
sys_path.pop()
sys_modules.pop("actions")
del actions
if exists("/usr/sbin/nginx"):
# Remove the custom plugin from the shared library
sys_path.pop()
sys_modules.pop("actions")
del actions
if (
request.method != "POST"

View File

@ -1,6 +1,7 @@
from copy import deepcopy
from os import listdir, remove
from os import listdir, mkdir, remove
from pathlib import Path
from shutil import rmtree
from time import sleep
from flask import flash
from os.path import exists, isfile
@ -35,54 +36,6 @@ class Config:
sleep(3)
env = self.__db.get_config()
self.reload_plugins()
def reload_plugins(self) -> None:
self.__plugins = []
external_plugins = []
for foldername in list(iglob("/etc/bunkerweb/plugins/*")) + list(
iglob("/usr/share/bunkerweb/core/*")
):
content = listdir(foldername)
if "plugin.json" not in content:
continue
with open(f"{foldername}/plugin.json", "r") as f:
plugin = json_load(f)
plugin.update(
{
"page": False,
"external": foldername.startswith("/etc/bunkerweb/plugins"),
}
)
if plugin["external"] is True:
external_plugin = deepcopy(plugin)
del external_plugin["external"]
del external_plugin["page"]
external_plugins.append(external_plugin)
if "ui" in content:
if "template.html" in listdir(f"{foldername}/ui"):
plugin["page"] = True
self.__plugins.append(plugin)
self.__plugins.sort(key=lambda plugin: plugin.get("name"))
self.__plugins_settings = {
**{k: v for x in self.__plugins for k, v in x["settings"].items()},
**self.__settings,
}
if external_plugins:
err = self.__db.update_external_plugins(external_plugins)
if err:
self.__logger.error(
f"Couldn't update external plugins to database: {err}",
)
def __env_to_dict(self, filename: str) -> dict:
"""Converts the content of an env file into a dict
@ -139,17 +92,17 @@ class Config:
conf = deepcopy(global_conf)
servers = []
plugins_settings = self.get_plugins_settings()
for service in services_conf:
server_name = service["SERVER_NAME"].split(" ")[0]
for k in service.keys():
key_without_server_name = k.replace(f"{server_name}_", "")
if (
self.__plugins_settings[key_without_server_name]["context"]
!= "global"
if key_without_server_name in self.__plugins_settings
plugins_settings[key_without_server_name]["context"] != "global"
if key_without_server_name in plugins_settings
else True
):
if not k.startswith(server_name) or k in self.__plugins_settings:
if not k.startswith(server_name) or k in plugins_settings:
conf[f"{server_name}_{k}"] = service[k]
else:
conf[k] = service[k]
@ -178,10 +131,44 @@ class Config:
remove(env_file)
def get_plugins_settings(self) -> dict:
return self.__plugins_settings
return {
**{k: v for x in self.get_plugins() for k, v in x["settings"].items()},
**self.__settings,
}
def get_plugins(self) -> List[dict]:
return self.__plugins
if not exists("/usr/sbin/nginx"):
plugins = self.__db.get_plugins()
plugins.sort(key=lambda x: x["name"])
return plugins
plugins = []
for foldername in list(iglob("/etc/bunkerweb/plugins/*")) + list(
iglob("/usr/share/bunkerweb/core/*")
):
content = listdir(foldername)
if "plugin.json" not in content:
continue
with open(f"{foldername}/plugin.json", "r") as f:
plugin = json_load(f)
plugin.update(
{
"page": False,
"external": foldername.startswith("/etc/bunkerweb/plugins"),
}
)
if "ui" in content:
if "template.html" in listdir(f"{foldername}/ui"):
plugin["page"] = True
plugins.append(plugin)
plugins.sort(key=lambda x: x["name"])
return plugins
def get_settings(self) -> dict:
return self.__settings
@ -212,6 +199,7 @@ class Config:
"""
if exists("/usr/sbin/nginx"):
services = []
plugins_settings = self.get_plugins_settings()
for filename in iglob("/etc/nginx/**/variables.env"):
service = filename.split("/")[3]
env = {
@ -219,8 +207,7 @@ class Config:
{"value": v, "method": "ui"} if methods is True else v
)
for k, v in self.__env_to_dict(filename).items()
if k.startswith(f"{service}_")
or k in self.__plugins_settings.keys()
if k.startswith(f"{service}_") or k in plugins_settings.keys()
}
services.append(env)
@ -242,11 +229,12 @@ class Config:
Return the error code
"""
error = 0
plugins_settings = self.get_plugins_settings()
for k, v in variables.items():
check = False
if k in self.__plugins_settings:
if _global ^ (self.__plugins_settings[k]["context"] == "global"):
if k in plugins_settings:
if _global ^ (plugins_settings[k]["context"] == "global"):
error = 1
flash(f"Variable {k} is not valid.", "error")
continue
@ -255,16 +243,16 @@ class Config:
else:
setting = k[0 : k.rfind("_")]
if (
setting not in self.__plugins_settings
or "multiple" not in self.__plugins_settings[setting]
setting not in plugins_settings
or "multiple" not in plugins_settings[setting]
):
error = 1
flash(f"Variable {k} is not valid.", "error")
continue
if not (
_global ^ (self.__plugins_settings[setting]["context"] == "global")
) and re_search(self.__plugins_settings[setting]["regex"], v):
_global ^ (plugins_settings[setting]["context"] == "global")
) and re_search(plugins_settings[setting]["regex"], v):
check = True
if not check: