Update UI to automatically set SCRIPT_NAME and ABSOLUTE_URI
This commit is contained in:
parent
b27958a19c
commit
9829ef7525
|
@ -11,7 +11,7 @@ from os.path import basename, dirname, 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
|
||||
|
||||
|
@ -55,7 +55,13 @@ install_as_MySQLdb()
|
|||
|
||||
|
||||
class Database:
|
||||
def __init__(self, logger: Logger, sqlalchemy_string: Optional[str] = None) -> None:
|
||||
def __init__(
|
||||
self,
|
||||
logger: Logger,
|
||||
sqlalchemy_string: Optional[str] = None,
|
||||
*,
|
||||
ui: bool = False,
|
||||
) -> None:
|
||||
"""Initialize the database"""
|
||||
self.__logger = logger
|
||||
self.__sql_session = None
|
||||
|
@ -67,10 +73,14 @@ class Database:
|
|||
)
|
||||
|
||||
if sqlalchemy_string.startswith("sqlite"):
|
||||
with suppress(FileExistsError):
|
||||
Path(dirname(sqlalchemy_string.split("///")[1])).mkdir(
|
||||
parents=True, exist_ok=True
|
||||
)
|
||||
if ui:
|
||||
while not Path(sep, "var", "lib", "bunkerweb", "db.sqlite3"):
|
||||
sleep(1)
|
||||
else:
|
||||
with suppress(FileExistsError):
|
||||
Path(dirname(sqlalchemy_string.split("///")[1])).mkdir(
|
||||
parents=True, exist_ok=True
|
||||
)
|
||||
elif "+" in sqlalchemy_string and "+pymysql" not in sqlalchemy_string:
|
||||
splitted = sqlalchemy_string.split("+")
|
||||
sqlalchemy_string = f"{splitted[0]}:{':'.join(splitted[1].split(':')[1:])}"
|
||||
|
@ -254,31 +264,44 @@ class Database:
|
|||
|
||||
return ""
|
||||
|
||||
def check_changes(self) -> Union[Dict[str, bool], str]:
|
||||
def check_changes(
|
||||
self, _type: Union[Literal["scheduler"], Literal["ui"]] = "scheduler"
|
||||
) -> Union[Dict[str, bool], bool, str]:
|
||||
"""Check if either the config, the custom configs or plugins have changed inside the database"""
|
||||
with self.__db_session() as session:
|
||||
try:
|
||||
metadata = (
|
||||
session.query(Metadata)
|
||||
.with_entities(
|
||||
if _type == "scheduler":
|
||||
entities = (
|
||||
Metadata.custom_configs_changed,
|
||||
Metadata.external_plugins_changed,
|
||||
Metadata.config_changed,
|
||||
)
|
||||
else:
|
||||
entities = (Metadata.ui_config_changed,)
|
||||
|
||||
metadata = (
|
||||
session.query(Metadata)
|
||||
.with_entities(*entities)
|
||||
.filter_by(id=1)
|
||||
.first()
|
||||
)
|
||||
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,
|
||||
config_changed=metadata is not None and metadata.config_changed,
|
||||
)
|
||||
|
||||
if _type == "scheduler":
|
||||
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,
|
||||
config_changed=metadata is not None and metadata.config_changed,
|
||||
)
|
||||
else:
|
||||
return metadata is not None and metadata.ui_config_changed
|
||||
except BaseException:
|
||||
return format_exc()
|
||||
|
||||
def checked_changes(self) -> str:
|
||||
def checked_changes(
|
||||
self, _type: Union[Literal["scheduler"], Literal["ui"]] = "scheduler"
|
||||
) -> str:
|
||||
"""Set that the config, the custom configs and the plugins didn't change"""
|
||||
with self.__db_session() as session:
|
||||
try:
|
||||
|
@ -287,9 +310,12 @@ class Database:
|
|||
if not metadata:
|
||||
return "The metadata are not set yet, try again"
|
||||
|
||||
metadata.config_changed = False
|
||||
metadata.custom_configs_changed = False
|
||||
metadata.external_plugins_changed = False
|
||||
if _type == "scheduler":
|
||||
metadata.config_changed = False
|
||||
metadata.custom_configs_changed = False
|
||||
metadata.external_plugins_changed = False
|
||||
else:
|
||||
metadata.ui_config_changed = False
|
||||
session.commit()
|
||||
except BaseException:
|
||||
return format_exc()
|
||||
|
@ -658,6 +684,7 @@ class Database:
|
|||
if not metadata.first_config_saved:
|
||||
metadata.first_config_saved = True
|
||||
metadata.config_changed = bool(to_put)
|
||||
metadata.ui_config_changed = bool(to_put)
|
||||
|
||||
try:
|
||||
session.add_all(to_put)
|
||||
|
|
|
@ -282,5 +282,6 @@ class Metadata(Base):
|
|||
custom_configs_changed = Column(Boolean, default=False, nullable=True)
|
||||
external_plugins_changed = Column(Boolean, default=False, nullable=True)
|
||||
config_changed = Column(Boolean, default=False, nullable=True)
|
||||
ui_config_changed = Column(Boolean, default=False, nullable=True)
|
||||
integration = Column(INTEGRATIONS_ENUM, default="Unknown", nullable=False)
|
||||
version = Column(String(32), default="1.5.0", nullable=False)
|
||||
|
|
149
src/ui/main.py
149
src/ui/main.py
|
@ -1,12 +1,14 @@
|
|||
#!/usr/bin/python3
|
||||
|
||||
from os import _exit, getenv, getpid, listdir, sep
|
||||
from os import _exit, environ, getenv, listdir, sep
|
||||
from os.path import basename, dirname, join
|
||||
from sys import path as sys_path, modules as sys_modules
|
||||
from pathlib import Path
|
||||
|
||||
os_release_path = Path(sep, "etc", "os-release")
|
||||
if os_release_path.is_file() and "Alpine" not in os_release_path.read_text():
|
||||
if os_release_path.is_file() and "Alpine" not in os_release_path.read_text(
|
||||
encoding="utf-8"
|
||||
):
|
||||
sys_path.append(join(sep, "usr", "share", "bunkerweb", "deps", "python"))
|
||||
|
||||
del os_release_path
|
||||
|
@ -18,7 +20,7 @@ for deps_path in [
|
|||
if deps_path not in sys_path:
|
||||
sys_path.append(deps_path)
|
||||
|
||||
from gevent import monkey
|
||||
from gevent import monkey, spawn
|
||||
|
||||
monkey.patch_all()
|
||||
|
||||
|
@ -96,10 +98,10 @@ def stop_gunicorn():
|
|||
call(["kill", "-SIGTERM", pid])
|
||||
|
||||
|
||||
def stop(status, stop=True):
|
||||
def stop(status, _stop=True):
|
||||
Path(sep, "var", "run", "bunkerweb", "ui.pid").unlink(missing_ok=True)
|
||||
Path(sep, "var", "tmp", "bunkerweb", "ui.healthy").unlink(missing_ok=True)
|
||||
if stop is True:
|
||||
if _stop is True:
|
||||
stop_gunicorn()
|
||||
_exit(status)
|
||||
|
||||
|
@ -127,10 +129,7 @@ app.wsgi_app = ReverseProxied(app.wsgi_app)
|
|||
# Set variables and instantiate objects
|
||||
vars = get_variables()
|
||||
|
||||
if "ABSOLUTE_URI" not in vars:
|
||||
logger.error("ABSOLUTE_URI is not set")
|
||||
stop(1)
|
||||
elif "ADMIN_USERNAME" not in vars:
|
||||
if "ADMIN_USERNAME" not in vars:
|
||||
logger.error("ADMIN_USERNAME is not set")
|
||||
stop(1)
|
||||
elif "ADMIN_PASSWORD" not in vars:
|
||||
|
@ -146,14 +145,6 @@ if not vars.get("FLASK_DEBUG", False) and not regex_match(
|
|||
)
|
||||
stop(1)
|
||||
|
||||
if not vars["ABSOLUTE_URI"].endswith("/"):
|
||||
vars["ABSOLUTE_URI"] += "/"
|
||||
|
||||
if not vars.get("FLASK_DEBUG", False) and vars["ABSOLUTE_URI"].endswith("/changeme/"):
|
||||
logger.error("Please change the default URL.")
|
||||
stop(1)
|
||||
|
||||
|
||||
login_manager = LoginManager()
|
||||
login_manager.init_app(app)
|
||||
login_manager.login_view = "login"
|
||||
|
@ -167,33 +158,44 @@ PLUGIN_KEYS = [
|
|||
"settings",
|
||||
]
|
||||
|
||||
integration = "Linux"
|
||||
INTEGRATION = "Linux"
|
||||
integration_path = Path(sep, "usr", "share", "bunkerweb", "INTEGRATION")
|
||||
if getenv("KUBERNETES_MODE", "no").lower() == "yes":
|
||||
integration = "Kubernetes"
|
||||
INTEGRATION = "Kubernetes"
|
||||
elif getenv("SWARM_MODE", "no").lower() == "yes":
|
||||
integration = "Swarm"
|
||||
INTEGRATION = "Swarm"
|
||||
elif getenv("AUTOCONF_MODE", "no").lower() == "yes":
|
||||
integration = "Autoconf"
|
||||
INTEGRATION = "Autoconf"
|
||||
elif integration_path.is_file():
|
||||
integration = integration_path.read_text().strip()
|
||||
INTEGRATION = integration_path.read_text(encoding="utf-8").strip()
|
||||
|
||||
del integration_path
|
||||
|
||||
docker_client = None
|
||||
kubernetes_client = None
|
||||
if integration in ("Docker", "Swarm", "Autoconf"):
|
||||
if INTEGRATION in ("Docker", "Swarm", "Autoconf"):
|
||||
try:
|
||||
docker_client: DockerClient = DockerClient(
|
||||
base_url=vars.get("DOCKER_HOST", "unix:///var/run/docker.sock")
|
||||
)
|
||||
except (docker_APIError, DockerException):
|
||||
logger.warning("No docker host found")
|
||||
elif integration == "Kubernetes":
|
||||
elif INTEGRATION == "Kubernetes":
|
||||
kube_config.load_incluster_config()
|
||||
kubernetes_client = kube_client.CoreV1Api()
|
||||
|
||||
db = Database(logger)
|
||||
db = Database(logger, ui=True)
|
||||
|
||||
if INTEGRATION in (
|
||||
"Swarm",
|
||||
"Kubernetes",
|
||||
"Autoconf",
|
||||
):
|
||||
while not db.is_autoconf_loaded():
|
||||
logger.warning(
|
||||
"Autoconf is not loaded yet in the database, retrying in 5s ...",
|
||||
)
|
||||
sleep(5)
|
||||
|
||||
while not db.is_initialized():
|
||||
logger.warning(
|
||||
|
@ -209,6 +211,8 @@ while not db.is_first_config_saved() or not env:
|
|||
sleep(5)
|
||||
env = db.get_config()
|
||||
|
||||
del env
|
||||
|
||||
logger.info("Database is ready")
|
||||
Path(sep, "var", "tmp", "bunkerweb", "ui.healthy").write_text("ok", encoding="utf-8")
|
||||
bw_version = (
|
||||
|
@ -217,16 +221,101 @@ bw_version = (
|
|||
.strip()
|
||||
)
|
||||
|
||||
ABSOLUTE_URI = vars.get("ABSOLUTE_URI")
|
||||
CONFIG = Config(db)
|
||||
|
||||
|
||||
def update_config():
|
||||
global ABSOLUTE_URI
|
||||
|
||||
ret = db.checked_changes("ui")
|
||||
|
||||
if ret:
|
||||
logger.error(
|
||||
f"An error occurred when setting the changes to checked in the database : {ret}"
|
||||
)
|
||||
stop(1)
|
||||
|
||||
ssl = False
|
||||
server_name = None
|
||||
endpoint = None
|
||||
|
||||
for service in CONFIG.get_services():
|
||||
if service.get("USE_UI", "no") == "no":
|
||||
continue
|
||||
|
||||
server_name = service.get("SERVER_NAME", {"value": None})["value"]
|
||||
endpoint = service.get("REVERSE_PROXY_URL", {"value": "/"})["value"]
|
||||
|
||||
logger.warning(service.get("AUTO_LETS_ENCRYPT", {"value": "no"}))
|
||||
logger.warning(service.get("GENERATE_SELF_SIGNED_SSL", {"value": "no"}))
|
||||
logger.warning(service.get("USE_CUSTOM_SSL", {"value": "no"}))
|
||||
|
||||
if any(
|
||||
[
|
||||
service.get("AUTO_LETS_ENCRYPT", {"value": "no"})["value"] == "yes",
|
||||
service.get("GENERATE_SELF_SIGNED_SSL", {"value": "no"})["value"]
|
||||
== "yes",
|
||||
service.get("USE_CUSTOM_SSL", {"value": "no"})["value"] == "yes",
|
||||
]
|
||||
):
|
||||
ssl = True
|
||||
break
|
||||
|
||||
if not server_name:
|
||||
logger.error("No service found with USE_UI=yes")
|
||||
stop(1)
|
||||
|
||||
ABSOLUTE_URI = f"http{'s' if ssl else ''}://{server_name}{endpoint}"
|
||||
SCRIPT_NAME = f"/{basename(ABSOLUTE_URI[:-1] if ABSOLUTE_URI.endswith('/') and ABSOLUTE_URI != '/' else ABSOLUTE_URI)}"
|
||||
|
||||
if not ABSOLUTE_URI.endswith("/"):
|
||||
ABSOLUTE_URI += "/"
|
||||
|
||||
if ABSOLUTE_URI != app.config.get("ABSOLUTE_URI"):
|
||||
app.config["ABSOLUTE_URI"] = ABSOLUTE_URI
|
||||
app.config["SESSION_COOKIE_DOMAIN"] = server_name
|
||||
|
||||
logger.info(f"The ABSOLUTE_URI is now {ABSOLUTE_URI}")
|
||||
else:
|
||||
logger.info(f"The ABSOLUTE_URI is still {ABSOLUTE_URI}")
|
||||
|
||||
if SCRIPT_NAME != getenv("SCRIPT_NAME"):
|
||||
environ["SCRIPT_NAME"] = f"/{basename(ABSOLUTE_URI[:-1])}"
|
||||
logger.info(f"The script name is now {environ['SCRIPT_NAME']}")
|
||||
else:
|
||||
logger.info(f"The script name is still {environ['SCRIPT_NAME']}")
|
||||
|
||||
|
||||
def check_config_changes():
|
||||
while True:
|
||||
changes = db.check_changes("ui")
|
||||
|
||||
if isinstance(changes, str):
|
||||
continue
|
||||
|
||||
if changes:
|
||||
logger.info(
|
||||
"Config changed in the database, updating ABSOLUTE_URI and SCRIPT_NAME ..."
|
||||
)
|
||||
|
||||
update_config()
|
||||
|
||||
sleep(1)
|
||||
|
||||
|
||||
update_config()
|
||||
|
||||
spawn(check_config_changes)
|
||||
|
||||
try:
|
||||
app.config.update(
|
||||
DEBUG=True,
|
||||
SECRET_KEY=vars["FLASK_SECRET"],
|
||||
ABSOLUTE_URI=vars["ABSOLUTE_URI"],
|
||||
INSTANCES=Instances(docker_client, kubernetes_client, integration),
|
||||
INSTANCES=Instances(docker_client, kubernetes_client, INTEGRATION),
|
||||
CONFIG=Config(db),
|
||||
CONFIGFILES=ConfigFiles(logger, db),
|
||||
SESSION_COOKIE_DOMAIN=vars["ABSOLUTE_URI"]
|
||||
.replace("http://", "")
|
||||
SESSION_COOKIE_DOMAIN=ABSOLUTE_URI.replace("http://", "")
|
||||
.replace("https://", "")
|
||||
.split("/")[0],
|
||||
WTF_CSRF_SSL_STRICT=False,
|
||||
|
@ -1346,7 +1435,7 @@ def logs_container(container_id):
|
|||
tmp_logs = []
|
||||
if docker_client:
|
||||
try:
|
||||
if integration != "Swarm":
|
||||
if INTEGRATION != "Swarm":
|
||||
docker_logs = docker_client.containers.get(container_id).logs(
|
||||
stdout=True,
|
||||
stderr=True,
|
||||
|
|
Loading…
Reference in New Issue