mirror of
https://github.com/bunkerity/bunkerweb
synced 2026-05-24 09:28:37 +00:00
Soft merge branch 'dev' into branch '1.6'
This commit is contained in:
commit
9e7d824d82
8 changed files with 108 additions and 59 deletions
|
|
@ -752,6 +752,16 @@ When your BunkerWeb instance has upgraded to the PRO version, you will see your
|
|||
|
||||
### Username / Password
|
||||
|
||||
!!! tip "Overriding admin credentials from environment variables"
|
||||
|
||||
If you want to override the admin credentials from environment variables, you can set the following variables :
|
||||
|
||||
- `OVERRIDE_ADMIN_CREDS` : set it to `yes` to enable the override even if the admin credentials are already set (default is `no`)
|
||||
- `ADMIN_USERNAME` : username to access the web UI
|
||||
- `ADMIN_PASSWORD` : password to access the web UI
|
||||
|
||||
The web UI will use these variables to authenticate you.
|
||||
|
||||
!!! warning "Lost password/username"
|
||||
|
||||
In case you forgot your UI credentials, you can reset them from the CLI following [the steps described in the troubleshooting section](troubleshooting.md#web-ui).
|
||||
|
|
|
|||
|
|
@ -1,4 +1,13 @@
|
|||
{% if UI_HOST != "" and not has_variable(all, "USE_UI", "yes") +%}
|
||||
access_by_lua_block {
|
||||
local ngx_var = ngx.var
|
||||
local scheme = ngx_var.scheme
|
||||
local http_host = ngx_var.http_host
|
||||
local request_uri = ngx_var.request_uri
|
||||
if scheme == "http" and http_host ~= nil and http_host ~= "" and request_uri and request_uri ~= "" then
|
||||
return ngx.redirect("https://" .. http_host .. request_uri, ngx.HTTP_MOVED_PERMANENTLY)
|
||||
end
|
||||
}
|
||||
location /setup {
|
||||
etag off;
|
||||
add_header Last-Modified "";
|
||||
|
|
|
|||
|
|
@ -454,7 +454,7 @@ class Database:
|
|||
# Rename the old tables
|
||||
db_version_id = db_version.replace(".", "_")
|
||||
for table_name in metadata.tables.keys():
|
||||
if table_name not in Base.metadata.tables:
|
||||
if table_name in Base.metadata.tables:
|
||||
with self.__db_session() as session:
|
||||
if inspector.has_table(f"{table_name}_{db_version_id}"):
|
||||
self.logger.warning(f'Table "{table_name}" already exists, dropping it to make room for the new one')
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ from os.path import sep
|
|||
from pathlib import Path
|
||||
from shutil import rmtree
|
||||
from sys import argv
|
||||
from tarfile import open as tar_open
|
||||
from tarfile import TarFile, open as tar_open
|
||||
from threading import Lock
|
||||
from traceback import format_exc
|
||||
from typing import Any, Dict, Literal, Optional, Tuple, Union
|
||||
|
|
@ -78,15 +78,22 @@ class Job:
|
|||
rmtree(extract_path, ignore_errors=True)
|
||||
extract_path.mkdir(parents=True, exist_ok=True)
|
||||
with tar_open(fileobj=BytesIO(job_cache_file["data"]), mode="r:gz") as tar:
|
||||
assert isinstance(tar, TarFile)
|
||||
try:
|
||||
tar.extractall(extract_path, filter="fully_trusted")
|
||||
except TypeError:
|
||||
tar.extractall(extract_path)
|
||||
for member in tar.getmembers():
|
||||
try:
|
||||
tar.extract(member, path=extract_path)
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error extracting {member.name}: {e}")
|
||||
except Exception as e:
|
||||
self.logger.error(f"Error extracting tar file: {e}")
|
||||
self.logger.debug(f"Restored cache directory {extract_path}")
|
||||
continue
|
||||
elif job_cache_file["job_name"] != job_name:
|
||||
continue
|
||||
cache_path.parent.mkdir(parents=True, exist_ok=True)
|
||||
cache_path.write_bytes(job_cache_file["data"])
|
||||
self.logger.debug(f"Restored cache file {job_cache_file['file_name']}")
|
||||
except BaseException as e:
|
||||
self.logger.error(f"Exception while restoring cache file {job_cache_file['file_name']} :\n{e}")
|
||||
ret = False
|
||||
|
|
@ -203,7 +210,7 @@ class Job:
|
|||
tgz.add(dir_path, arcname=".")
|
||||
content.seek(0, 0)
|
||||
|
||||
return self.cache_file(file_name, content.read(), job_name=job_name, service_id=service_id)
|
||||
return self.cache_file(file_name, content.getvalue(), job_name=job_name, service_id=service_id)
|
||||
|
||||
def del_cache(self, name: str, *, job_name: str = "", service_id: str = "") -> Tuple[bool, str]:
|
||||
"""Delete cache file from database and local cache file."""
|
||||
|
|
|
|||
|
|
@ -178,11 +178,16 @@ def generate_custom_configs(configs: List[Dict[str, Any]], *, original_path: Uni
|
|||
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] = EXTERNAL_PLUGINS_PATH):
|
||||
def generate_external_plugins(plugins: Optional[List[Dict[str, Any]]], *, original_path: Union[Path, str] = EXTERNAL_PLUGINS_PATH):
|
||||
if not isinstance(original_path, Path):
|
||||
original_path = Path(original_path)
|
||||
pro = "pro" in original_path.parts
|
||||
|
||||
if not plugins:
|
||||
assert SCHEDULER is not None
|
||||
plugins = SCHEDULER.db.get_plugins(_type="pro" if pro else "external", with_data=True)
|
||||
assert plugins is not None, "Couldn't get plugins from database"
|
||||
|
||||
# Remove old external/pro plugins files
|
||||
LOGGER.info(f"Removing old/changed {'pro ' if pro else ''}external plugins files ...")
|
||||
ignored_plugins = set()
|
||||
|
|
@ -486,6 +491,8 @@ if __name__ == "__main__":
|
|||
# Instantiate scheduler environment
|
||||
SCHEDULER.env = env
|
||||
|
||||
threads = []
|
||||
|
||||
SCHEDULER.apis = []
|
||||
for db_instance in SCHEDULER.db.get_instances():
|
||||
SCHEDULER.apis.append(API(f"http://{db_instance['hostname']}:{db_instance['port']}", db_instance["server_name"]))
|
||||
|
|
@ -494,43 +501,43 @@ if __name__ == "__main__":
|
|||
|
||||
LOGGER.info("Scheduler started ...")
|
||||
|
||||
# Checking if any custom config has been created by the user
|
||||
LOGGER.info("Checking if there are any changes in custom configs ...")
|
||||
custom_configs = []
|
||||
db_configs = SCHEDULER.db.get_custom_configs()
|
||||
changes = False
|
||||
for file in CUSTOM_CONFIGS_PATH.rglob("*.conf"):
|
||||
if len(file.parts) > len(CUSTOM_CONFIGS_PATH.parts) + 3:
|
||||
LOGGER.warning(f"Custom config file {file} is not in the correct path, skipping ...")
|
||||
def check_configs_changes():
|
||||
# Checking if any custom config has been created by the user
|
||||
LOGGER.info("Checking if there are any changes in custom configs ...")
|
||||
custom_configs = []
|
||||
db_configs = SCHEDULER.db.get_custom_configs()
|
||||
changes = False
|
||||
for file in CUSTOM_CONFIGS_PATH.rglob("*.conf"):
|
||||
if len(file.parts) > len(CUSTOM_CONFIGS_PATH.parts) + 3:
|
||||
LOGGER.warning(f"Custom config file {file} is not in the correct path, skipping ...")
|
||||
|
||||
content = file.read_text(encoding="utf-8")
|
||||
service_id = file.parent.name if file.parent.name not in CUSTOM_CONFIGS_DIRS else None
|
||||
config_type = file.parent.parent.name if service_id else file.parent.name
|
||||
content = file.read_text(encoding="utf-8")
|
||||
service_id = file.parent.name if file.parent.name not in CUSTOM_CONFIGS_DIRS else None
|
||||
config_type = file.parent.parent.name if service_id else file.parent.name
|
||||
|
||||
saving = True
|
||||
in_db = False
|
||||
for db_conf in db_configs:
|
||||
if db_conf["service_id"] == service_id and db_conf["name"] == file.stem:
|
||||
in_db = True
|
||||
saving = True
|
||||
in_db = False
|
||||
for db_conf in db_configs:
|
||||
if db_conf["service_id"] == service_id and db_conf["name"] == file.stem:
|
||||
in_db = True
|
||||
|
||||
if not in_db and content.startswith("# CREATED BY ENV"):
|
||||
saving = False
|
||||
changes = True
|
||||
if not in_db and content.startswith("# CREATED BY ENV"):
|
||||
saving = False
|
||||
changes = True
|
||||
|
||||
if saving:
|
||||
custom_configs.append({"value": content, "exploded": (service_id, config_type, file.stem)})
|
||||
if saving:
|
||||
custom_configs.append({"value": content, "exploded": (service_id, config_type, file.stem)})
|
||||
|
||||
changes = changes or {hash(dict_to_frozenset(d)) for d in custom_configs} != {hash(dict_to_frozenset(d)) for d in db_configs}
|
||||
changes = changes or {hash(dict_to_frozenset(d)) for d in custom_configs} != {hash(dict_to_frozenset(d)) for d in db_configs}
|
||||
|
||||
if changes:
|
||||
err = SCHEDULER.db.save_custom_configs(custom_configs, "manual")
|
||||
if err:
|
||||
LOGGER.error(f"Couldn't save some manually created custom configs to database: {err}")
|
||||
if changes:
|
||||
err = SCHEDULER.db.save_custom_configs(custom_configs, "manual")
|
||||
if err:
|
||||
LOGGER.error(f"Couldn't save some manually created custom configs to database: {err}")
|
||||
|
||||
if (scheduler_first_start and db_configs) or changes:
|
||||
generate_custom_configs(SCHEDULER.db.get_custom_configs())
|
||||
|
||||
del custom_configs, db_configs
|
||||
threads.append(Thread(target=check_configs_changes))
|
||||
|
||||
def check_plugin_changes(_type: Literal["external", "pro"] = "external"):
|
||||
# Check if any external or pro plugin has been added by the user
|
||||
|
|
@ -581,11 +588,15 @@ if __name__ == "__main__":
|
|||
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(SCHEDULER.db.get_plugins(_type=_type, with_data=True), original_path=plugin_path)
|
||||
generate_external_plugins(SCHEDULER.db.get_plugins(_type=_type, with_data=True), original_path=plugin_path)
|
||||
|
||||
check_plugin_changes("external")
|
||||
check_plugin_changes("pro")
|
||||
threads.extend([Thread(target=check_plugin_changes, args=("external",)), Thread(target=check_plugin_changes, args=("pro",))])
|
||||
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
LOGGER.info("Running plugins download jobs ...")
|
||||
|
||||
|
|
@ -598,10 +609,18 @@ if __name__ == "__main__":
|
|||
|
||||
db_metadata = SCHEDULER.db.get_metadata()
|
||||
if db_metadata["pro_plugins_changed"] or db_metadata["external_plugins_changed"]:
|
||||
threads.clear()
|
||||
|
||||
if db_metadata["pro_plugins_changed"]:
|
||||
generate_external_plugins(SCHEDULER.db.get_plugins(_type="pro", with_data=True), original_path=PRO_PLUGINS_PATH)
|
||||
threads.append(Thread(target=generate_external_plugins, args=(None,), kwargs={"original_path": PRO_PLUGINS_PATH}))
|
||||
if db_metadata["external_plugins_changed"]:
|
||||
generate_external_plugins(SCHEDULER.db.get_plugins(_type="external", with_data=True))
|
||||
threads.append(Thread(target=generate_external_plugins, args=(None,)))
|
||||
|
||||
for thread in threads:
|
||||
thread.start()
|
||||
|
||||
for thread in threads:
|
||||
thread.join()
|
||||
|
||||
# run the config saver to save potential ignored external plugins settings
|
||||
LOGGER.info("Running config saver to save potential ignored external plugins settings ...")
|
||||
|
|
@ -630,7 +649,6 @@ if __name__ == "__main__":
|
|||
CONFIG_NEED_GENERATION = True
|
||||
RUN_JOBS_ONCE = True
|
||||
CHANGES = []
|
||||
threads = []
|
||||
|
||||
def send_nginx_configs():
|
||||
LOGGER.info(f"Sending {join(sep, 'etc', 'nginx')} folder ...")
|
||||
|
|
|
|||
|
|
@ -76,21 +76,29 @@ def on_starting(server):
|
|||
USER = User(**USER)
|
||||
|
||||
if getenv("ADMIN_USERNAME") or getenv("ADMIN_PASSWORD"):
|
||||
if USER.method == "manual":
|
||||
override_admin_creds = getenv("OVERRIDE_ADMIN_CREDS", "no").lower() == "yes"
|
||||
if USER.method == "manual" or override_admin_creds:
|
||||
updated = False
|
||||
if getenv("ADMIN_USERNAME", "") and USER.get_id() != getenv("ADMIN_USERNAME", ""):
|
||||
USER.id = getenv("ADMIN_USERNAME", "")
|
||||
updated = True
|
||||
if getenv("ADMIN_PASSWORD", "") and not USER.check_password(getenv("ADMIN_PASSWORD", "")):
|
||||
USER.update_password(getenv("ADMIN_PASSWORD", ""))
|
||||
updated = True
|
||||
if not USER_PASSWORD_RX.match(getenv("ADMIN_PASSWORD", "")):
|
||||
LOGGER.warning(
|
||||
"The admin password is not strong enough. It must contain at least 8 characters, including at least 1 uppercase letter, 1 lowercase letter, 1 number and 1 special character (#@?!$%^&*-). It will not be updated."
|
||||
)
|
||||
else:
|
||||
USER.update_password(getenv("ADMIN_PASSWORD", ""))
|
||||
updated = True
|
||||
|
||||
if updated:
|
||||
ret = db.update_ui_user(USER.get_id(), USER.password_hash, USER.is_two_factor_enabled, USER.secret_token)
|
||||
if ret:
|
||||
LOGGER.error(f"Couldn't update the admin user in the database: {ret}")
|
||||
exit(1)
|
||||
LOGGER.info("The admin user was updated successfully")
|
||||
if override_admin_creds:
|
||||
LOGGER.warning("Overriding the admin user credentials, as the OVERRIDE_ADMIN_CREDS environment variable is set to 'yes'.")
|
||||
err = db.update_ui_user(USER.get_id(), USER.password_hash, USER.is_two_factor_enabled, USER.secret_token, method="manual")
|
||||
if err:
|
||||
LOGGER.error(f"Couldn't update the admin user in the database: {err}")
|
||||
else:
|
||||
LOGGER.info("The admin user was updated successfully")
|
||||
else:
|
||||
LOGGER.warning("The admin user wasn't created manually. You can't change it from the environment variables.")
|
||||
elif getenv("ADMIN_USERNAME") and getenv("ADMIN_PASSWORD"):
|
||||
|
|
|
|||
|
|
@ -568,6 +568,7 @@ def setup():
|
|||
"REVERSE_PROXY_HOST": request.form["ui_host"],
|
||||
"REVERSE_PROXY_URL": request.form["ui_url"] or "/",
|
||||
"AUTO_LETS_ENCRYPT": request.form.get("auto_lets_encrypt", "no"),
|
||||
"GENERATE_SELF_SIGNED_SSL": "yes" if request.form.get("auto_lets_encrypt", "no") == "no" else "no",
|
||||
"INTERCEPTED_ERROR_CODES": "400 404 405 413 429 500 501 502 503 504",
|
||||
"MAX_CLIENT_SIZE": "50m",
|
||||
},
|
||||
|
|
|
|||
14
src/ui/templates/setup.html
vendored
14
src/ui/templates/setup.html
vendored
|
|
@ -320,7 +320,7 @@
|
|||
Your BunkerWeb UI final URL will be
|
||||
</h5>
|
||||
<p class="family-text text-center text-sm md:text-base break-words w-full px-4"
|
||||
data-resume>http://</p>
|
||||
data-resume>https://</p>
|
||||
</div>
|
||||
<div class="col-span-12 flex justify-center">
|
||||
<button tabindex="2"
|
||||
|
|
@ -370,7 +370,7 @@
|
|||
e.preventDefault();
|
||||
this.updateCheck("unknown");
|
||||
// get resume
|
||||
const api = `http://${this.servInp.value}/setup/check`;
|
||||
const api = `${location.protocol}://${this.servInp.value}/setup/check`;
|
||||
fetch(api)
|
||||
.then((res) => {
|
||||
this.updateCheck("success");
|
||||
|
|
@ -437,14 +437,12 @@
|
|||
}
|
||||
|
||||
updateResume() {
|
||||
this.servInp.value = this.servInp.value.replace('https://', '').replace('http://', '');
|
||||
this.servInp.value = this.servInp.value.replace('https://', '');
|
||||
if (!this.urlInp.value.startsWith("/")) {
|
||||
this.urlInp.value = "/" + this.urlInp.value;
|
||||
}
|
||||
this.urlInp.value = this.urlInp.value.replace("//", "/");
|
||||
this.resumeEl.textContent = `http${
|
||||
this.sslCheck.getAttribute("data-checked") === "true" ? "s" : ""
|
||||
}://${this.servInp.value}${this.urlInp.value}`;
|
||||
this.resumeEl.textContent = `https://${this.servInp.value}${this.urlInp.value}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -600,9 +598,7 @@
|
|||
|
||||
// send form and wait for response
|
||||
|
||||
let api = `http${
|
||||
this.sslCheck.getAttribute("data-checked") === "true" ? "s" : ""
|
||||
}://${this.servInp.value}${this.urlInp.value}`;
|
||||
let api = `https://${this.servInp.value}${this.urlInp.value}`;
|
||||
if (!api.endsWith("/")) {
|
||||
api = `${api}/`;
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue