bunkerweb 1.4.0
This commit is contained in:
455
ui/src/Config.py
455
ui/src/Config.py
@@ -1,123 +1,338 @@
|
||||
import json, uuid, glob, copy, re, subprocess, os
|
||||
|
||||
class Config :
|
||||
|
||||
def __init__(self) :
|
||||
with open("/opt/bunkerized-nginx/settings.json", "r") as f :
|
||||
self.__settings = json.loads(f.read())
|
||||
|
||||
def __env_to_dict(self, filename) :
|
||||
if not os.path.isfile(filename) :
|
||||
return {}
|
||||
with open(filename, "r") as f :
|
||||
env = f.read()
|
||||
data = {}
|
||||
for line in env.split("\n") :
|
||||
if not "=" in line :
|
||||
continue
|
||||
var = line.split("=")[0]
|
||||
val = line.replace(var + "=", "", 1)
|
||||
data[var] = val
|
||||
return data
|
||||
|
||||
def __dict_to_env(self, filename, variables) :
|
||||
env = ""
|
||||
for k, v in variables.items() :
|
||||
env += k + "=" + v + "\n"
|
||||
with open(filename, "w") as f :
|
||||
f.write(env)
|
||||
|
||||
def __gen_conf(self, global_conf, services_conf) :
|
||||
conf = copy.deepcopy(global_conf)
|
||||
if not "SERVER_NAME" in conf :
|
||||
conf["SERVER_NAME"] = ""
|
||||
servers = conf["SERVER_NAME"].split(" ")
|
||||
if conf["SERVER_NAME"] == "" :
|
||||
servers = []
|
||||
for service in services_conf :
|
||||
first_server = service["SERVER_NAME"].split(" ")[0]
|
||||
if not first_server in servers :
|
||||
servers.append(first_server)
|
||||
for k, v in service.items() :
|
||||
if not k.startswith(first_server + "_") :
|
||||
conf[first_server + "_" + k] = v
|
||||
conf["SERVER_NAME"] = " ".join(servers)
|
||||
env_file = "/tmp/" + str(uuid.uuid4()) + ".env"
|
||||
self.__dict_to_env(env_file, conf)
|
||||
proc = subprocess.run(["/opt/bunkerized-nginx/gen/main.py", "--settings", "/opt/bunkerized-nginx/settings.json", "--templates", "/opt/bunkerized-nginx/confs", "--output", "/etc/nginx", "--variables", env_file], capture_output=True)
|
||||
stderr = proc.stderr.decode("ascii")
|
||||
stdout = proc.stdout.decode("ascii")
|
||||
if stderr != "" or proc.returncode != 0 :
|
||||
raise Exception("Error from generator (return code = " + str(proc.returncode) + ") : " + stderr + "\n" + stdout)
|
||||
|
||||
def get_settings(self) :
|
||||
return self.__settings
|
||||
|
||||
def get_config(self) :
|
||||
return self.__env_to_dict("/etc/nginx/global.env")
|
||||
|
||||
def get_services(self) :
|
||||
services = []
|
||||
for filename in glob.iglob("/etc/nginx/**/site.env") :
|
||||
env = self.__env_to_dict(filename)
|
||||
services.append(env)
|
||||
no_multisite = self.__env_to_dict("/etc/nginx/site.env")
|
||||
if len(no_multisite) > 0 :
|
||||
services.append(no_multisite)
|
||||
return services
|
||||
|
||||
def check_variables(self, variables) :
|
||||
for k, v in variables.items() :
|
||||
check = False
|
||||
for category in self.__settings :
|
||||
for param in self.__settings[category]["params"] :
|
||||
multiple = False
|
||||
if param["type"] != "multiple" :
|
||||
real_params = [param]
|
||||
else :
|
||||
real_params = param["params"]
|
||||
multiple = True
|
||||
for real_param in real_params :
|
||||
if (((not multiple and k == real_param["env"]) or
|
||||
(multiple and re.search("^" + real_param["env"] + "_" + "[0-9]+$", k))) and
|
||||
real_param["context"] == "multisite" and
|
||||
re.search(real_param["regex"], v)) :
|
||||
check = True
|
||||
if not check :
|
||||
raise Exception("Variable " + k + " is not valid.")
|
||||
|
||||
def new_service(self, variables) :
|
||||
global_env = self.__env_to_dict("/etc/nginx/global.env")
|
||||
services = self.get_services()
|
||||
for service in services :
|
||||
if service["SERVER_NAME"] == variables["SERVER_NAME"] or service["SERVER_NAME"] in variables["SERVER_NAME"].split(" ") :
|
||||
raise Exception("Service " + service["SERVER_NAME"] + " already exists.")
|
||||
services.append(variables)
|
||||
self.__gen_conf(global_env, services)
|
||||
return "Configuration for " + variables["SERVER_NAME"] + " has been generated."
|
||||
|
||||
def edit_service(self, old_server_name, variables) :
|
||||
self.delete_service(old_server_name)
|
||||
self.new_service(variables)
|
||||
return "Configuration for " + old_server_name + " has been edited."
|
||||
from copy import deepcopy
|
||||
from flask import flash
|
||||
from operator import xor
|
||||
from os.path import isfile
|
||||
from typing import Tuple
|
||||
from json import load as json_load
|
||||
from uuid import uuid4
|
||||
from glob import iglob
|
||||
from re import search as re_search
|
||||
from subprocess import run, DEVNULL, STDOUT
|
||||
|
||||
|
||||
def delete_service(self, server_name) :
|
||||
global_env = self.__env_to_dict("/etc/nginx/global.env")
|
||||
services = self.get_services()
|
||||
new_services = []
|
||||
found = False
|
||||
for service in services :
|
||||
if service["SERVER_NAME"].split(" ")[0] == server_name :
|
||||
found = True
|
||||
else :
|
||||
new_services.append(service)
|
||||
if not found :
|
||||
raise Exception("Can't delete missing " + server_name + " configuration.")
|
||||
new_servers = global_env["SERVER_NAME"].split(" ")
|
||||
if server_name in new_servers :
|
||||
new_servers.remove(server_name)
|
||||
global_env["SERVER_NAME"] = " ".join(new_servers)
|
||||
self.__gen_conf(global_env, new_services)
|
||||
return "Configuration for " + server_name + " has been deleted."
|
||||
class Config:
|
||||
def __init__(self):
|
||||
with open("/opt/bunkerweb/settings.json", "r") as f:
|
||||
self.__settings = json_load(f)
|
||||
|
||||
self.__plugins = []
|
||||
for filename in iglob("/opt/bunkerweb/core/**/plugin.json"):
|
||||
with open(filename, "r") as f:
|
||||
self.__plugins.append(json_load(f))
|
||||
|
||||
for filename in iglob("/opt/bunkerweb/plugins/**/plugin.json"):
|
||||
with open(filename, "r") as f:
|
||||
self.__plugins.append(json_load(f))
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
def reload_plugins(self) -> None:
|
||||
self.__plugins.clear()
|
||||
|
||||
for filename in iglob("/opt/bunkerweb/core/**/plugin.json"):
|
||||
with open(filename, "r") as f:
|
||||
self.__plugins.append(json_load(f))
|
||||
|
||||
for filename in iglob("/opt/bunkerweb/plugins/**/plugin.json"):
|
||||
with open(filename, "r") as f:
|
||||
self.__plugins.append(json_load(f))
|
||||
|
||||
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,
|
||||
}
|
||||
|
||||
def __env_to_dict(self, filename: str) -> dict:
|
||||
"""Converts the content of an env file into a dict
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
the path to the file to convert to dict
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The values of the file converted to dict
|
||||
"""
|
||||
if not isfile(filename):
|
||||
return {}
|
||||
|
||||
with open(filename, "r") as f:
|
||||
env = f.read()
|
||||
|
||||
data = {}
|
||||
for line in env.split("\n"):
|
||||
if not "=" in line:
|
||||
continue
|
||||
var = line.split("=")[0]
|
||||
val = line.replace(f"{var}=", "", 1)
|
||||
data[var] = val
|
||||
|
||||
return data
|
||||
|
||||
def __dict_to_env(self, filename: str, variables: dict) -> None:
|
||||
"""Converts the content of a dict into an env file
|
||||
|
||||
Parameters
|
||||
----------
|
||||
filename : str
|
||||
The path to save the env file
|
||||
variables : dict
|
||||
The dict to convert to env file
|
||||
"""
|
||||
with open(filename, "w") as f:
|
||||
f.write("\n".join(f"{k}={variables[k]}" for k in sorted(variables)))
|
||||
|
||||
def __gen_conf(self, global_conf: dict, services_conf: list[dict]) -> None:
|
||||
"""Generates the nginx configuration file from the given configuration
|
||||
|
||||
Parameters
|
||||
----------
|
||||
variables : dict
|
||||
The configuration to add to the file
|
||||
|
||||
Raises
|
||||
------
|
||||
Exception
|
||||
If an error occurred during the generation of the configuration file, raises this exception
|
||||
"""
|
||||
conf = deepcopy(global_conf)
|
||||
|
||||
servers = []
|
||||
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
|
||||
else True
|
||||
):
|
||||
server_key = f"{server_name}_{k}"
|
||||
if not k.startswith(server_name) or k in self.__plugins_settings:
|
||||
conf[server_key] = service[k]
|
||||
else:
|
||||
conf[k] = service[k]
|
||||
|
||||
servers.append(server_name)
|
||||
|
||||
conf["SERVER_NAME"] = " ".join(servers)
|
||||
env_file = "/tmp/" + str(uuid4()) + ".env"
|
||||
self.__dict_to_env(env_file, conf)
|
||||
proc = run(
|
||||
[
|
||||
"/opt/bunkerweb/gen/main.py",
|
||||
"--settings",
|
||||
"/opt/bunkerweb/settings.json",
|
||||
"--templates",
|
||||
"/opt/bunkerweb/confs",
|
||||
"--output",
|
||||
"/etc/nginx",
|
||||
"--variables",
|
||||
env_file,
|
||||
],
|
||||
stdin=DEVNULL,
|
||||
stderr=STDOUT,
|
||||
)
|
||||
|
||||
if proc.returncode != 0:
|
||||
raise Exception(f"Error from generator (return code = {proc.returncode})")
|
||||
|
||||
def get_plugins_settings(self) -> dict:
|
||||
return self.__plugins_settings
|
||||
|
||||
def get_plugins(self) -> dict:
|
||||
return self.__plugins
|
||||
|
||||
def get_settings(self) -> dict:
|
||||
return self.__settings
|
||||
|
||||
def get_config(self) -> dict:
|
||||
"""Get the nginx variables env file and returns it as a dict
|
||||
|
||||
Returns
|
||||
-------
|
||||
dict
|
||||
The nginx variables env file as a dict
|
||||
"""
|
||||
return self.__env_to_dict("/etc/nginx/variables.env")
|
||||
|
||||
def get_services(self) -> list[dict]:
|
||||
"""Get nginx's services
|
||||
|
||||
Returns
|
||||
-------
|
||||
list
|
||||
The services
|
||||
"""
|
||||
services = []
|
||||
for filename in iglob("/etc/nginx/**/variables.env"):
|
||||
env = self.__env_to_dict(filename)
|
||||
services.append(env)
|
||||
|
||||
return services
|
||||
|
||||
def check_variables(self, variables: dict, _global: bool = False) -> int:
|
||||
"""Testify that the variables passed are valid
|
||||
|
||||
Parameters
|
||||
----------
|
||||
variables : dict
|
||||
The dict to check
|
||||
|
||||
Returns
|
||||
-------
|
||||
int
|
||||
Return the error code
|
||||
"""
|
||||
error = 0
|
||||
for k, v in variables.items():
|
||||
check = False
|
||||
|
||||
if k in self.__plugins_settings:
|
||||
if _global ^ (self.__plugins_settings[k]["context"] == "global"):
|
||||
error = 1
|
||||
flash(f"Variable {k} is not valid.")
|
||||
continue
|
||||
|
||||
setting = k
|
||||
else:
|
||||
setting = k[0 : k.rfind("_")]
|
||||
if (
|
||||
setting not in self.__plugins_settings
|
||||
or "multiple" not in self.__plugins_settings[setting]
|
||||
):
|
||||
error = 1
|
||||
flash(f"Variable {k} is not valid.")
|
||||
continue
|
||||
|
||||
if not (
|
||||
_global ^ (self.__plugins_settings[setting]["context"] == "global")
|
||||
) and re_search(self.__plugins_settings[setting]["regex"], v):
|
||||
check = True
|
||||
|
||||
if not check:
|
||||
error = 1
|
||||
flash(f"Variable {k} is not valid.")
|
||||
continue
|
||||
|
||||
return error
|
||||
|
||||
def reload_config(self) -> None:
|
||||
self.__gen_conf(self.get_config(), self.get_services())
|
||||
|
||||
def new_service(self, variables: dict) -> Tuple[str, int]:
|
||||
"""Creates a new service from the given variables
|
||||
|
||||
Parameters
|
||||
----------
|
||||
variables : dict
|
||||
The settings for the new service
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The confirmation message
|
||||
|
||||
Raises
|
||||
------
|
||||
Exception
|
||||
raise this if the service already exists
|
||||
"""
|
||||
services = self.get_services()
|
||||
for service in services:
|
||||
if service["SERVER_NAME"] == variables["SERVER_NAME"] or service[
|
||||
"SERVER_NAME"
|
||||
] in variables["SERVER_NAME"].split(" "):
|
||||
return f"Service {service['SERVER_NAME']} already exists.", 1
|
||||
|
||||
services.append(variables)
|
||||
self.__gen_conf(self.get_config(), services)
|
||||
return f"Configuration for {variables['SERVER_NAME']} has been generated.", 0
|
||||
|
||||
def edit_service(self, old_server_name: str, variables: dict) -> str:
|
||||
"""Edits a service
|
||||
|
||||
Parameters
|
||||
----------
|
||||
old_server_name : str
|
||||
The old server name
|
||||
variables : dict
|
||||
The settings to change for the service
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
the confirmation message
|
||||
"""
|
||||
self.delete_service(old_server_name)
|
||||
self.new_service(variables)
|
||||
return f"Configuration for {old_server_name} has been edited."
|
||||
|
||||
def edit_global_conf(self, variables: dict) -> str:
|
||||
"""Edits the global conf
|
||||
|
||||
Parameters
|
||||
----------
|
||||
variables : dict
|
||||
The settings to change for the conf
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
the confirmation message
|
||||
"""
|
||||
self.__gen_conf(self.get_config() | variables, self.get_services())
|
||||
return f"The global configuration has been edited."
|
||||
|
||||
def delete_service(self, service_name: str) -> Tuple[str, int]:
|
||||
"""Deletes a service
|
||||
|
||||
Parameters
|
||||
----------
|
||||
service_name : str
|
||||
The name of the service to edit
|
||||
|
||||
Returns
|
||||
-------
|
||||
str
|
||||
The confirmation message
|
||||
|
||||
Raises
|
||||
------
|
||||
Exception
|
||||
raises this if the service_name given isn't found
|
||||
"""
|
||||
full_env = self.get_config()
|
||||
services = self.get_services()
|
||||
new_services = []
|
||||
found = False
|
||||
|
||||
for service in services:
|
||||
if service["SERVER_NAME"].split(" ")[0] == service_name:
|
||||
found = True
|
||||
else:
|
||||
new_services.append(service)
|
||||
|
||||
if not found:
|
||||
return f"Can't delete missing {service_name} configuration.", 1
|
||||
|
||||
full_env["SERVER_NAME"] = " ".join(
|
||||
[s for s in full_env["SERVER_NAME"].split(" ") if s != service_name]
|
||||
)
|
||||
|
||||
new_env = deepcopy(full_env)
|
||||
|
||||
for k in full_env:
|
||||
if k.startswith(service_name):
|
||||
del new_env[k]
|
||||
|
||||
self.__gen_conf(new_env, new_services)
|
||||
return f"Configuration for {service_name} has been deleted.", 0
|
||||
|
||||
122
ui/src/ConfigFiles.py
Normal file
122
ui/src/ConfigFiles.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import os
|
||||
from re import compile as re_compile
|
||||
from shutil import rmtree, move as shutil_move
|
||||
from typing import Tuple
|
||||
|
||||
from ui.utils import path_to_dict
|
||||
|
||||
|
||||
class ConfigFiles:
|
||||
def __init__(self):
|
||||
self.__name_regex = re_compile(r"^[a-zA-Z0-9_-]{1,64}$")
|
||||
self.__root_dirs = [
|
||||
child["name"]
|
||||
for child in path_to_dict("/opt/bunkerweb/configs")["children"]
|
||||
]
|
||||
self.__file_creation_blacklist = ["http", "stream"]
|
||||
|
||||
def check_name(self, name: str) -> bool:
|
||||
return self.__name_regex.match(name)
|
||||
|
||||
def check_path(self, path: str, root_path: str = "/opt/bunkerweb/configs/") -> str:
|
||||
root_dir: str = path.split("/")[4]
|
||||
if not (
|
||||
path.startswith(root_path)
|
||||
or root_path == "/opt/bunkerweb/configs/"
|
||||
and path.startswith(root_path)
|
||||
and root_dir in self.__root_dirs
|
||||
and (
|
||||
not path.endswith(".conf")
|
||||
or root_dir not in self.__file_creation_blacklist
|
||||
or len(path.split("/")) > 5
|
||||
)
|
||||
):
|
||||
return f"{path} is not a valid path"
|
||||
|
||||
if root_path == "/opt/bunkerweb/configs/":
|
||||
dirs = path.split("/")[5:]
|
||||
nbr_children = len(dirs)
|
||||
dirs = "/".join(dirs)
|
||||
if len(dirs) > 1:
|
||||
for x in range(nbr_children - 1):
|
||||
if not os.path.exists(
|
||||
f"{root_path}{root_dir}/{'/'.join(dirs.split('/')[0:-x])}"
|
||||
):
|
||||
return f"{root_path}{root_dir}/{'/'.join(dirs.split('/')[0:-x])} doesn't exist"
|
||||
|
||||
return ""
|
||||
|
||||
def delete_path(self, path: str) -> Tuple[str, int]:
|
||||
try:
|
||||
if os.path.isfile(path):
|
||||
os.remove(path)
|
||||
else:
|
||||
rmtree(path)
|
||||
except OSError:
|
||||
return f"Could not delete {path}", 1
|
||||
|
||||
return f"{path} was successfully deleted", 0
|
||||
|
||||
def create_folder(self, path: str, name: str) -> Tuple[str, int]:
|
||||
folder_path = os.path.join(path, name)
|
||||
try:
|
||||
os.mkdir(folder_path)
|
||||
except OSError:
|
||||
return f"Could not create {folder_path}", 1
|
||||
|
||||
return f"The folder {folder_path} was successfully created", 0
|
||||
|
||||
def create_file(self, path: str, name: str, content: str) -> Tuple[str, int]:
|
||||
file_path = os.path.join(path, name)
|
||||
with open(file_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
return f"The file {file_path} was successfully created", 0
|
||||
|
||||
def edit_folder(self, path: str, name: str) -> Tuple[str, int]:
|
||||
new_folder_path = os.path.dirname(os.path.join(path, name))
|
||||
|
||||
if path == new_folder_path:
|
||||
return (
|
||||
f"{path} was not renamed because the name didn't change",
|
||||
0,
|
||||
)
|
||||
|
||||
try:
|
||||
shutil_move(path, new_folder_path)
|
||||
except OSError:
|
||||
return f"Could not move {path}", 1
|
||||
|
||||
return f"The folder {path} was successfully renamed to {new_folder_path}", 0
|
||||
|
||||
def edit_file(self, path: str, name: str, content: str) -> Tuple[str, int]:
|
||||
new_path = os.path.dirname(os.path.join(path, name))
|
||||
try:
|
||||
with open(path, "r") as f:
|
||||
file_content = f.read()
|
||||
except FileNotFoundError:
|
||||
return f"Could not find {path}", 1
|
||||
|
||||
if path == new_path and file_content == content:
|
||||
return (
|
||||
f"{path} was not edited because the content and the name didn't change",
|
||||
0,
|
||||
)
|
||||
elif file_content == content:
|
||||
try:
|
||||
os.replace(path, new_path)
|
||||
return f"{path} was successfully renamed to {new_path}", 0
|
||||
except OSError:
|
||||
return f"Could not rename {path} into {new_path}", 1
|
||||
elif path == new_path:
|
||||
new_path = path
|
||||
else:
|
||||
try:
|
||||
os.remove(path)
|
||||
except OSError:
|
||||
return f"Could not remove {path}", 1
|
||||
|
||||
with open(new_path, "w") as f:
|
||||
f.write(content)
|
||||
|
||||
return f"The file {path} was successfully edited", 0
|
||||
@@ -1,156 +1,214 @@
|
||||
import docker, os, requests, subprocess
|
||||
import os
|
||||
from typing import Any
|
||||
from subprocess import run
|
||||
|
||||
class Instances :
|
||||
from api.API import API
|
||||
from utils.ApiCaller import ApiCaller
|
||||
|
||||
def __init__(self, docker_host, api_uri) :
|
||||
try :
|
||||
self.__docker = docker.DockerClient(base_url=docker_host)
|
||||
except :
|
||||
self.__docker = None
|
||||
self.__api_uri = api_uri
|
||||
|
||||
def __instance(self, id, name, type, status, data=None) :
|
||||
instance = {}
|
||||
instance["id"] = id
|
||||
instance["name"] = name
|
||||
instance["type"] = type
|
||||
instance["status"] = status
|
||||
instance["data"] = data
|
||||
return instance
|
||||
class Instance:
|
||||
_id: str
|
||||
name: str
|
||||
hostname: str
|
||||
_type: str
|
||||
health: bool
|
||||
env: Any
|
||||
apiCaller: ApiCaller
|
||||
|
||||
def __api_request(self, instance, order) :
|
||||
result = True
|
||||
hosts = []
|
||||
if instance["type"] == "container" :
|
||||
hosts.append(instance["name"])
|
||||
elif instances["type"] == "service" :
|
||||
for task in instance["data"].tasks() :
|
||||
host = instance["name"] + "." + task["NodeID"] + "." + task["ID"]
|
||||
hosts.append(host)
|
||||
for host in hosts :
|
||||
try :
|
||||
req = requests.post("http://" + host + ":8080" + self.__api_uri + order)
|
||||
if not req or req.status_code != 200 or req.text != "ok" :
|
||||
result = False
|
||||
except :
|
||||
result = False
|
||||
return result
|
||||
def __init__(
|
||||
self,
|
||||
_id: str,
|
||||
name: str,
|
||||
hostname: str,
|
||||
_type: str,
|
||||
status: str,
|
||||
data: Any = None,
|
||||
apiCaller: ApiCaller = ApiCaller(),
|
||||
) -> None:
|
||||
self._id = _id
|
||||
self.name = name
|
||||
self.hostname = hostname
|
||||
self._type = _type
|
||||
self.health = status == "up" and (
|
||||
(
|
||||
data.attrs["State"]["Health"]["Status"] == "healthy"
|
||||
if "Health" in data.attrs["State"]
|
||||
else False
|
||||
)
|
||||
if data
|
||||
else True
|
||||
)
|
||||
self.env = data
|
||||
self.apiCaller = apiCaller
|
||||
|
||||
def __instance_from_id(self, id) :
|
||||
instances = self.get_instances()
|
||||
for instance in instances :
|
||||
if instance["id"] == id :
|
||||
return instance
|
||||
raise Exception("Can't find instance with id " + id)
|
||||
def get_id(self) -> str:
|
||||
return self._id
|
||||
|
||||
def get_instances(self) :
|
||||
instances = []
|
||||
def run_jobs(self) -> bool:
|
||||
return self.apiCaller._send_to_apis("POST", "/jobs")
|
||||
|
||||
# Docker instances (containers or services)
|
||||
if self.__docker != None :
|
||||
for instance in self.__docker.containers.list(all=True, filters={"label" : "bunkerized-nginx.UI"}) :
|
||||
id = instance.id
|
||||
name = instance.name
|
||||
type = "container"
|
||||
status = "down"
|
||||
if instance.status == "running" :
|
||||
status = "up"
|
||||
instances.append(self.__instance(id, name, type, status, instance))
|
||||
is_swarm = True
|
||||
try :
|
||||
version = self.__docker.swarm.version
|
||||
except :
|
||||
is_swarm = False
|
||||
if is_swarm :
|
||||
for instance in self.__docker.services.list(filters={"label" : "bunkerized-nginx.UI"}) :
|
||||
id = instance.id
|
||||
name = instance.name
|
||||
type = "service"
|
||||
status = "down"
|
||||
desired_tasks = instance.attrs["ServiceStatus"]["DesiredTasks"]
|
||||
running_tasks = instance.attrs["ServiceStatus"]["RunningTasks"]
|
||||
if desired_tasks > 0 and (desired_tasks == running_tasks) :
|
||||
status = "up"
|
||||
instances.append(self.__instance(id, name, type, status, instance))
|
||||
def reload(self) -> bool:
|
||||
return self.apiCaller._send_to_apis("POST", "/reload")
|
||||
|
||||
# Local instance
|
||||
if os.path.exists("/usr/sbin/nginx") :
|
||||
id = "local"
|
||||
name = "local"
|
||||
type = "local"
|
||||
status = "down"
|
||||
if os.path.exists("/tmp/nginx.pid") :
|
||||
status = "up"
|
||||
instances.append(self.__instance(id, name, type, status))
|
||||
def start(self) -> bool:
|
||||
return self.apiCaller._send_to_apis("POST", "/start")
|
||||
|
||||
return instances
|
||||
def stop(self) -> bool:
|
||||
return self.apiCaller._send_to_apis("POST", "/stop")
|
||||
|
||||
def reload_instances(self) :
|
||||
all_reload = True
|
||||
for instance in self.get_instances() :
|
||||
if instance["status"] == "down" :
|
||||
all_reload = False
|
||||
continue
|
||||
if instance["type"] == "local" :
|
||||
proc = subprocess.run(["/opt/bunkerized-nginx/entrypoint/jobs.sh"], capture_output=True)
|
||||
if proc.returncode != 0 :
|
||||
all_reload = False
|
||||
else :
|
||||
proc = subprocess.run(["sudo", "/opt/bunkerized-nginx/ui/linux.sh", "reload"], capture_output=True)
|
||||
if proc.returncode != 0 :
|
||||
all_reload = False
|
||||
elif instance["type"] == "container" or instance["type"] == "service" :
|
||||
all_reload = self.__api_request(instance, "/reload")
|
||||
return all_reload
|
||||
def restart(self) -> bool:
|
||||
return self.apiCaller._send_to_apis("POST", "/restart")
|
||||
|
||||
def reload_instance(self, id) :
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
if instance["type"] == "local" :
|
||||
proc = subprocess.run(["/opt/bunkerized-nginx/entrypoint/jobs.sh"], capture_output=True)
|
||||
if proc.returncode != 0 :
|
||||
result = False
|
||||
else :
|
||||
proc = subprocess.run(["sudo", "/opt/bunkerized-nginx/ui/linux.sh", "reload"], capture_output=True)
|
||||
result = proc.returncode == 0
|
||||
elif instance["type"] == "container" or instance["type"] == "service" :
|
||||
result = self.__api_request(instance, "/reload")
|
||||
if result :
|
||||
return "Instance " + instance["name"] + " has been reloaded."
|
||||
return "Can't reload " + instance["name"]
|
||||
|
||||
def start_instance(self, id) :
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
if instance["type"] == "local" :
|
||||
proc = subprocess.run(["sudo", "/opt/bunkerized-nginx/ui/linux.sh", "start"], capture_output=True)
|
||||
result = proc.returncode == 0
|
||||
elif instance["type"] == "container" or instance["type"] == "service" :
|
||||
result = False #self.__api_request(instance, "/start")
|
||||
if result :
|
||||
return "Instance " + instance["name"] + " has been started."
|
||||
return "Can't start " + instance["name"]
|
||||
class Instances:
|
||||
def __init__(self, docker_client):
|
||||
self.__docker = docker_client
|
||||
|
||||
def stop_instance(self, id) :
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
if instance["type"] == "local" :
|
||||
proc = subprocess.run(["sudo", "/opt/bunkerized-nginx/ui/linux.sh", "stop"], capture_output=True)
|
||||
result = proc.returncode == 0
|
||||
elif instance["type"] == "container" or instance["type"] == "service" :
|
||||
result = self.__api_request(instance, "/stop")
|
||||
if result :
|
||||
return "Instance " + instance["name"] + " has been stopped."
|
||||
return "Can't stop " + instance["name"]
|
||||
def __instance_from_id(self, _id) -> Instance:
|
||||
instances: list[Instance] = self.get_instances()
|
||||
for instance in instances:
|
||||
if instance._id == _id:
|
||||
return instance
|
||||
|
||||
def restart_instance(self, id) :
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
if instance["type"] == "local" :
|
||||
proc = subprocess.run(["sudo", "/opt/bunkerized-nginx/ui/linux.sh", "restart"], capture_output=True)
|
||||
result = proc.returncode == 0
|
||||
elif instance["type"] == "container" or instance["type"] == "service" :
|
||||
result = False #self.__api_request(instance, "/restart")
|
||||
if result :
|
||||
return "Instance " + instance["name"] + " has been restarted."
|
||||
return "Can't restart " + instance["name"]
|
||||
raise Exception(f"Can't find instance with id {_id}")
|
||||
|
||||
def get_instances(self) -> list[Instance]:
|
||||
instances = []
|
||||
# Docker instances (containers or services)
|
||||
if self.__docker is not None:
|
||||
for instance in self.__docker.containers.list(
|
||||
all=True, filters={"label": "bunkerweb.UI"}
|
||||
):
|
||||
env_variables = {
|
||||
x[0]: x[1]
|
||||
for x in [env.split("=") for env in instance.attrs["Config"]["Env"]]
|
||||
}
|
||||
|
||||
apiCaller = ApiCaller()
|
||||
apiCaller._set_apis(
|
||||
[
|
||||
API(
|
||||
f"http://{instance.name}:{env_variables.get('API_HTTP_PORT', '5000')}",
|
||||
env_variables.get("API_SERVER_NAME", "bwapi"),
|
||||
)
|
||||
]
|
||||
)
|
||||
|
||||
instances.append(
|
||||
Instance(
|
||||
instance.id,
|
||||
instance.name,
|
||||
instance.name,
|
||||
"container",
|
||||
"up" if instance.status == "running" else "down",
|
||||
instance,
|
||||
apiCaller,
|
||||
)
|
||||
)
|
||||
|
||||
instances = sorted(
|
||||
instances,
|
||||
key=lambda x: x.name,
|
||||
)
|
||||
|
||||
# Local instance
|
||||
if os.path.exists("/usr/sbin/nginx"):
|
||||
instances.insert(
|
||||
0,
|
||||
Instance(
|
||||
"local",
|
||||
"local",
|
||||
"127.0.0.1",
|
||||
"local",
|
||||
"up" if os.path.exists("/opt/bunkerweb/tmp/nginx.pid") else "down",
|
||||
),
|
||||
)
|
||||
|
||||
return instances
|
||||
|
||||
def reload_instances(self) -> list[str]:
|
||||
not_reloaded: list[str] = []
|
||||
for instance in self.get_instances():
|
||||
if instance.health is False:
|
||||
not_reloaded.append(instance.name)
|
||||
continue
|
||||
|
||||
if self.reload_instance(None, instance).startswith("Can't reload"):
|
||||
not_reloaded.append(instance.name)
|
||||
|
||||
return not_reloaded
|
||||
|
||||
def reload_instance(self, id: int = None, instance: Instance = None) -> str:
|
||||
if instance is None:
|
||||
instance = self.__instance_from_id(id)
|
||||
|
||||
result = True
|
||||
if instance._type == "local":
|
||||
result = (
|
||||
run(
|
||||
["sudo", "systemctl", "restart", "bunkerweb"], capture_output=True
|
||||
).returncode
|
||||
!= 0
|
||||
)
|
||||
elif instance._type == "container":
|
||||
result = instance.run_jobs()
|
||||
result = result & instance.reload()
|
||||
|
||||
if result:
|
||||
return f"Instance {instance.name} has been reloaded."
|
||||
|
||||
return f"Can't reload {instance.name}"
|
||||
|
||||
def start_instance(self, id) -> str:
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
|
||||
if instance._type == "local":
|
||||
proc = run(
|
||||
["sudo", "/opt/bunkerweb/ui/linux.sh", "start"],
|
||||
capture_output=True,
|
||||
)
|
||||
result = proc.returncode == 0
|
||||
elif instance._type == "container":
|
||||
result = instance.start()
|
||||
|
||||
if result:
|
||||
return f"Instance {instance.name} has been started."
|
||||
|
||||
return f"Can't start {instance.name}"
|
||||
|
||||
def stop_instance(self, id) -> str:
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
|
||||
if instance._type == "local":
|
||||
proc = run(
|
||||
["sudo", "/opt/bunkerweb/ui/linux.sh", "stop"],
|
||||
capture_output=True,
|
||||
)
|
||||
result = proc.returncode == 0
|
||||
elif instance._type == "container":
|
||||
result = instance.stop()
|
||||
|
||||
if result:
|
||||
return f"Instance {instance.name} has been stopped."
|
||||
|
||||
return f"Can't stop {instance.name}"
|
||||
|
||||
def restart_instance(self, id) -> str:
|
||||
instance = self.__instance_from_id(id)
|
||||
result = True
|
||||
|
||||
if instance._type == "local":
|
||||
proc = run(
|
||||
["sudo", "/opt/bunkerweb/ui/linux.sh", "restart"],
|
||||
capture_output=True,
|
||||
)
|
||||
result = proc.returncode == 0
|
||||
elif instance._type == "container":
|
||||
result = instance.restart()
|
||||
|
||||
if result:
|
||||
return f"Instance {instance.name} has been restarted."
|
||||
|
||||
return f"Can't restart {instance.name}"
|
||||
|
||||
@@ -1,17 +1,24 @@
|
||||
class ReverseProxied(object):
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
|
||||
def __init__(self, app):
|
||||
self.app = app
|
||||
def __call__(self, environ, start_response):
|
||||
"""
|
||||
If the app is behind a reverse proxy, it will modify the
|
||||
environ object to make it look like the request was received on the app directly
|
||||
|
||||
def __call__(self, environ, start_response):
|
||||
script_name = environ.get('HTTP_X_SCRIPT_NAME', '')
|
||||
if script_name:
|
||||
environ['SCRIPT_NAME'] = script_name
|
||||
path_info = environ['PATH_INFO']
|
||||
if path_info.startswith(script_name):
|
||||
environ['PATH_INFO'] = path_info[len(script_name):]
|
||||
:param environ: The WSGI environment dict
|
||||
:param start_response: This is the WSGI-compatible start_response function that the
|
||||
:return: A WSGI application.
|
||||
"""
|
||||
script_name = environ.get("HTTP_X_SCRIPT_NAME", "")
|
||||
if script_name:
|
||||
environ["SCRIPT_NAME"] = script_name
|
||||
path_info = environ["PATH_INFO"]
|
||||
if path_info.startswith(script_name):
|
||||
environ["PATH_INFO"] = path_info[len(script_name) :]
|
||||
|
||||
scheme = environ.get('HTTP_X_FORWARDED_PROTO', '')
|
||||
if scheme:
|
||||
environ['wsgi.url_scheme'] = scheme
|
||||
return self.app(environ, start_response)
|
||||
scheme = environ.get("HTTP_X_FORWARDED_PROTO", "")
|
||||
if scheme:
|
||||
environ["wsgi.url_scheme"] = scheme
|
||||
return self.app(environ, start_response)
|
||||
|
||||
@@ -1,13 +1,25 @@
|
||||
import flask_login, bcrypt
|
||||
from flask_login import UserMixin
|
||||
from bcrypt import checkpw, hashpw, gensalt
|
||||
|
||||
class User(flask_login.UserMixin) :
|
||||
|
||||
def __init__(self, id, password) :
|
||||
self.__id = id
|
||||
self.__password = bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt())
|
||||
class User(UserMixin):
|
||||
def __init__(self, id, password):
|
||||
self.__id = id
|
||||
self.__password = hashpw(password.encode("utf-8"), gensalt())
|
||||
|
||||
def get_id(self) :
|
||||
def get_id(self):
|
||||
"""
|
||||
Get the id of the user
|
||||
:return: The id of the user
|
||||
"""
|
||||
return self.__id
|
||||
|
||||
def check_password(self, password) :
|
||||
return bcrypt.checkpw(password.encode("utf-8"), self.__password)
|
||||
def check_password(self, password):
|
||||
"""
|
||||
Check if the password is correct by hashing it and comparing it to the stored hash
|
||||
|
||||
:param password: The password to be checked
|
||||
:return: The password is being checked against the password hash. If the password is correct,
|
||||
the user is returned.
|
||||
"""
|
||||
return checkpw(password.encode("utf-8"), self.__password)
|
||||
|
||||
Reference in New Issue
Block a user