crafty-4/app/classes/shared/main_controller.py
Zedifus 2a512d7273 Fix files to conform with new Black pylintrc
Mostly just breaking up strings and comments into new lines
Some strings dont require 'f' but keeping in for readability
with the rest of the concatinated string
2022-03-23 06:06:13 +00:00

848 lines
30 KiB
Python

import os
import pathlib
from pathlib import Path
import shutil
import time
import logging
import tempfile
from typing import Union
from app.classes.controllers.crafty_perms_controller import Crafty_Perms_Controller
from app.classes.controllers.management_controller import Management_Controller
from app.classes.controllers.users_controller import Users_Controller
from app.classes.controllers.roles_controller import Roles_Controller
from app.classes.controllers.server_perms_controller import Server_Perms_Controller
from app.classes.controllers.servers_controller import Servers_Controller
from app.classes.models.server_permissions import Enum_Permissions_Server
from app.classes.models.users import helper_users
from app.classes.models.management import helpers_management
from app.classes.models.servers import servers_helper
from app.classes.shared.console import console
from app.classes.shared.helpers import helper
from app.classes.shared.server import Server
from app.classes.shared.file_helpers import file_helper
from app.classes.minecraft.server_props import ServerProps
from app.classes.minecraft.serverjars import server_jar_obj
from app.classes.minecraft.stats import Stats
from app.classes.web.websocket_helper import websocket_helper
try:
from peewee import DoesNotExist
# TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone
from apscheduler.schedulers.background import BackgroundScheduler
except ModuleNotFoundError as err:
helper.auto_installer_fix(err)
logger = logging.getLogger(__name__)
class Controller:
def __init__(self):
self.servers_list = []
self.stats = Stats(self)
self.crafty_perms = Crafty_Perms_Controller()
self.management = Management_Controller()
self.roles = Roles_Controller()
self.server_perms = Server_Perms_Controller()
self.servers = Servers_Controller()
self.users = Users_Controller()
tz = get_localzone()
self.support_scheduler = BackgroundScheduler(timezone=str(tz))
self.support_scheduler.start()
def check_server_loaded(self, server_id_to_check: int):
logger.info(f"Checking to see if we already registered {server_id_to_check}")
for s in self.servers_list:
known_server = s.get("server_id")
if known_server is None:
return False
if known_server == server_id_to_check:
logger.info(
f"skipping initialization of server {server_id_to_check} "
f"because it is already loaded"
)
return True
return False
def init_all_servers(self):
servers = self.servers.get_all_defined_servers()
for s in servers:
server_id = s.get("server_id")
# if we have already initialized this server, let's skip it.
if self.check_server_loaded(server_id):
continue
# if this server path no longer exists - let's warn and bomb out
if not helper.check_path_exists(
helper.get_os_understandable_path(s["path"])
):
logger.warning(
f"Unable to find server {s['server_name']} at path {s['path']}. "
f"Skipping this server"
)
console.warning(
f"Unable to find server {s['server_name']} at path {s['path']}. "
f"Skipping this server"
)
continue
settings_file = os.path.join(
helper.get_os_understandable_path(s["path"]), "server.properties"
)
# if the properties file isn't there, let's warn
if not helper.check_file_exists(settings_file):
logger.error(f"Unable to find {settings_file}. Skipping this server.")
console.error(f"Unable to find {settings_file}. Skipping this server.")
continue
settings = ServerProps(settings_file)
temp_server_dict = {
"server_id": s.get("server_id"),
"server_data_obj": s,
"server_obj": Server(self.stats),
"server_settings": settings.props,
}
# setup the server, do the auto start and all that jazz
temp_server_dict["server_obj"].do_server_setup(s)
# add this temp object to the list of init servers
self.servers_list.append(temp_server_dict)
if s["auto_start"]:
self.servers.set_waiting_start(s["server_id"], True)
self.refresh_server_settings(s["server_id"])
console.info(
f"Loaded Server: ID {s['server_id']}"
+ f" | Name: {s['server_name']}"
+ f" | Autostart: {s['auto_start']}"
+ f" | Delay: {s['auto_start_delay']} "
)
def refresh_server_settings(self, server_id: int):
server_obj = self.get_server_obj(server_id)
server_obj.reload_server_settings()
@staticmethod
def check_system_user():
if helper_users.get_user_id_by_name("system") is not None:
return True
else:
return False
def set_project_root(self, root_dir):
self.project_root = root_dir
def package_support_logs(self, exec_user):
if exec_user["preparing"]:
return
self.users.set_prepare(exec_user["user_id"])
# pausing so on screen notifications can run for user
time.sleep(7)
websocket_helper.broadcast_user(
exec_user["user_id"], "notification", "Preparing your support logs"
)
tempDir = tempfile.mkdtemp()
tempZipStorage = tempfile.mkdtemp()
full_temp = os.path.join(tempDir, "support_logs")
os.mkdir(full_temp)
tempZipStorage = os.path.join(tempZipStorage, "support_logs")
crafty_path = os.path.join(full_temp, "crafty")
os.mkdir(crafty_path)
server_path = os.path.join(full_temp, "server")
os.mkdir(server_path)
if exec_user["superuser"]:
auth_servers = self.servers.get_all_defined_servers()
else:
user_servers = self.servers.get_authorized_servers(
int(exec_user["user_id"])
)
auth_servers = []
for server in user_servers:
if (
Enum_Permissions_Server.Logs
in self.server_perms.get_user_id_permissions_list(
exec_user["user_id"], server["server_id"]
)
):
auth_servers.append(server)
else:
logger.info(
f"Logs permission not available for server "
f"{server['server_name']}. Skipping."
)
# we'll iterate through our list of log paths from auth servers.
for server in auth_servers:
final_path = os.path.join(server_path, str(server["server_name"]))
try:
os.mkdir(final_path)
except FileExistsError:
final_path += "_" + server["server_uuid"]
os.mkdir(final_path)
try:
file_helper.copy_file(server["log_path"], final_path)
except Exception as e:
logger.warning(f"Failed to copy file with error: {e}")
# Copy crafty logs to archive dir
full_log_name = os.path.join(crafty_path, "logs")
file_helper.copy_dir(os.path.join(self.project_root, "logs"), full_log_name)
self.support_scheduler.add_job(
self.log_status,
"interval",
seconds=1,
id="logs_" + str(exec_user["user_id"]),
args=[full_temp, tempZipStorage + ".zip", exec_user],
)
file_helper.make_archive(tempZipStorage, tempDir)
if len(websocket_helper.clients) > 0:
websocket_helper.broadcast_user(
exec_user["user_id"],
"support_status_update",
helper.calc_percent(full_temp, tempZipStorage + ".zip"),
)
tempZipStorage += ".zip"
websocket_helper.broadcast_user(exec_user["user_id"], "send_logs_bootbox", {})
self.users.set_support_path(exec_user["user_id"], tempZipStorage)
self.users.stop_prepare(exec_user["user_id"])
self.support_scheduler.remove_job("logs_" + str(exec_user["user_id"]))
@staticmethod
def add_system_user():
helper_users.add_user(
"system",
helper.random_string_generator(64),
"default@example.com",
False,
False,
)
def get_server_settings(self, server_id):
for s in self.servers_list:
if int(s["server_id"]) == int(server_id):
return s["server_settings"]
logger.warning(f"Unable to find server object for server id {server_id}")
return False
def crash_detection(self, server_obj):
svr = self.get_server_obj(server_obj.server_id)
# start or stop crash detection depending upon user preference
# The below functions check to see if the server is running.
# They only execute if it's running.
if server_obj.crash_detection == 1:
svr.start_crash_detection()
else:
svr.stop_crash_detection()
def log_status(self, source_path, dest_path, exec_user):
results = helper.calc_percent(source_path, dest_path)
self.log_stats = results
if len(websocket_helper.clients) > 0:
websocket_helper.broadcast_user(
exec_user["user_id"], "support_status_update", results
)
def send_log_status(self):
try:
return self.log_stats
except:
return {"percent": 0, "total_files": 0}
def get_server_obj(self, server_id: Union[str, int]) -> Union[bool, Server]:
for s in self.servers_list:
if str(s["server_id"]) == str(server_id):
return s["server_obj"]
logger.warning(f"Unable to find server object for server id {server_id}")
return False # TODO: Change to None
def get_server_data(self, server_id: str):
for s in self.servers_list:
if str(s["server_id"]) == str(server_id):
return s["server_data_obj"]
logger.warning(f"Unable to find server object for server id {server_id}")
return False
@staticmethod
def list_defined_servers():
servers = servers_helper.get_all_defined_servers()
return servers
def list_running_servers(self):
running_servers = []
# for each server
for s in self.servers_list:
# is the server running?
srv_obj = s["server_obj"]
running = srv_obj.check_running()
# if so, let's add a dictionary to the list of running servers
if running:
running_servers.append({"id": srv_obj.server_id, "name": srv_obj.name})
return running_servers
def stop_all_servers(self):
servers = self.list_running_servers()
logger.info(f"Found {len(servers)} running server(s)")
console.info(f"Found {len(servers)} running server(s)")
logger.info("Stopping All Servers")
console.info("Stopping All Servers")
for s in servers:
logger.info(f"Stopping Server ID {s['id']} - {s['name']}")
console.info(f"Stopping Server ID {s['id']} - {s['name']}")
self.stop_server(s["id"])
# let's wait 2 seconds to let everything flush out
time.sleep(2)
logger.info("All Servers Stopped")
console.info("All Servers Stopped")
def stop_server(self, server_id):
# issue the stop command
svr_obj = self.get_server_obj(server_id)
svr_obj.stop_threaded_server()
def create_jar_server(
self,
server: str,
version: str,
name: str,
min_mem: int,
max_mem: int,
port: int,
):
server_id = helper.create_uuid()
server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
server_dir = helper.wtol_path(server_dir)
backup_path = helper.wtol_path(backup_path)
server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
server_file = f"{server}-{version}.jar"
full_jar_path = os.path.join(server_dir, server_file)
# make the dir - perhaps a UUID?
helper.ensure_dir_exists(server_dir)
helper.ensure_dir_exists(backup_path)
try:
# do a eula.txt
with open(os.path.join(server_dir, "eula.txt"), "w", encoding="utf-8") as f:
f.write("eula=false")
f.close()
# setup server.properties with the port
with open(
os.path.join(server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
except Exception as e:
logger.error(f"Unable to create required server files due to :{e}")
return False
if helper.is_os_windows():
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f'-jar "{full_jar_path}" nogui'
)
else:
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f"-jar {full_jar_path} nogui"
)
server_log_file = f"{server_dir}/logs/latest.log"
server_stop = "stop"
new_id = self.register_server(
name,
server_id,
server_dir,
backup_path,
server_command,
server_file,
server_log_file,
server_stop,
port,
server_type="minecraft-java",
)
# download the jar
server_jar_obj.download_jar(server, version, full_jar_path, new_id)
return new_id
@staticmethod
def verify_jar_server(server_path: str, server_jar: str):
server_path = helper.get_os_understandable_path(server_path)
path_check = helper.check_path_exists(server_path)
jar_check = helper.check_file_exists(os.path.join(server_path, server_jar))
if not path_check or not jar_check:
return False
return True
@staticmethod
def verify_zip_server(zip_path: str):
zip_path = helper.get_os_understandable_path(zip_path)
zip_check = helper.check_file_exists(zip_path)
if not zip_check:
return False
return True
def import_jar_server(
self,
server_name: str,
server_path: str,
server_jar: str,
min_mem: int,
max_mem: int,
port: int,
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
server_path = helper.get_os_understandable_path(server_path)
try:
file_helper.copy_dir(server_path, new_server_dir, True)
except shutil.Error as ex:
logger.error(f"Server import failed with error: {ex}")
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_jar)
if helper.is_os_windows():
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f'-jar "{full_jar_path}" nogui'
)
else:
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f"-jar {full_jar_path} nogui"
)
server_log_file = f"{new_server_dir}/logs/latest.log"
server_stop = "stop"
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_jar,
server_log_file,
server_stop,
port,
server_type="minecraft-java",
)
return new_id
def import_zip_server(
self,
server_name: str,
zip_path: str,
server_jar: str,
min_mem: int,
max_mem: int,
port: int,
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
tempDir = helper.get_os_understandable_path(zip_path)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
has_properties = False
# extracts archive to temp directory
for item in os.listdir(tempDir):
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(tempDir, item)):
file_helper.move_file(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
else:
file_helper.move_dir(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_jar)
if helper.is_os_windows():
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f'-jar "{full_jar_path}" nogui'
)
else:
server_command = (
f"java -Xms{helper.float_to_string(min_mem)}M "
f"-Xmx{helper.float_to_string(max_mem)}M "
f"-jar {full_jar_path} nogui"
)
logger.debug("command: " + server_command)
server_log_file = f"{new_server_dir}/logs/latest.log"
server_stop = "stop"
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_jar,
server_log_file,
server_stop,
port,
server_type="minecraft-java",
)
return new_id
# **********************************************************************************
# BEDROCK IMPORTS
# **********************************************************************************
def import_bedrock_server(
self, server_name: str, server_path: str, server_exe: str, port: int
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
server_path = helper.get_os_understandable_path(server_path)
try:
file_helper.copy_dir(server_path, new_server_dir, True)
except shutil.Error as ex:
logger.error(f"Server import failed with error: {ex}")
has_properties = False
for item in os.listdir(new_server_dir):
if str(item) == "server.properties":
has_properties = True
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_exe)
if helper.is_os_windows():
server_command = f'"{full_jar_path}"'
else:
server_command = f"./{server_exe}"
logger.debug("command: " + server_command)
server_log_file = "N/A"
server_stop = "stop"
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_exe,
server_log_file,
server_stop,
port,
server_type="minecraft-bedrock",
)
if os.name != "nt":
if helper.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2775)
return new_id
def import_bedrock_zip_server(
self, server_name: str, zip_path: str, server_exe: str, port: int
):
server_id = helper.create_uuid()
new_server_dir = os.path.join(helper.servers_dir, server_id)
backup_path = os.path.join(helper.backup_path, server_id)
if helper.is_os_windows():
new_server_dir = helper.wtol_path(new_server_dir)
backup_path = helper.wtol_path(backup_path)
new_server_dir.replace(" ", "^ ")
backup_path.replace(" ", "^ ")
tempDir = helper.get_os_understandable_path(zip_path)
helper.ensure_dir_exists(new_server_dir)
helper.ensure_dir_exists(backup_path)
has_properties = False
# extracts archive to temp directory
for item in os.listdir(tempDir):
if str(item) == "server.properties":
has_properties = True
try:
if not os.path.isdir(os.path.join(tempDir, item)):
file_helper.move_file(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
else:
file_helper.move_dir(
os.path.join(tempDir, item), os.path.join(new_server_dir, item)
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
if not has_properties:
logger.info(
f"No server.properties found on zip file import. "
f"Creating one with port selection of {str(port)}"
)
with open(
os.path.join(new_server_dir, "server.properties"), "w", encoding="utf-8"
) as f:
f.write(f"server-port={port}")
f.close()
full_jar_path = os.path.join(new_server_dir, server_exe)
if helper.is_os_windows():
server_command = f'"{full_jar_path}"'
else:
server_command = f"./{server_exe}"
logger.debug("command: " + server_command)
server_log_file = "N/A"
server_stop = "stop"
new_id = self.register_server(
server_name,
server_id,
new_server_dir,
backup_path,
server_command,
server_exe,
server_log_file,
server_stop,
port,
server_type="minecraft-bedrock",
)
if os.name != "nt":
if helper.check_file_exists(full_jar_path):
os.chmod(full_jar_path, 0o2775)
return new_id
# **********************************************************************************
# BEDROCK IMPORTS END
# **********************************************************************************
def rename_backup_dir(self, old_server_id, new_server_id, new_uuid):
server_data = self.servers.get_server_data_by_id(old_server_id)
old_bu_path = server_data["backup_path"]
Server_Perms_Controller.backup_role_swap(old_server_id, new_server_id)
if not helper.is_os_windows():
backup_path = helper.validate_traversal(helper.backup_path, old_bu_path)
if helper.is_os_windows():
backup_path = helper.validate_traversal(
helper.wtol_path(helper.backup_path), helper.wtol_path(old_bu_path)
)
backup_path = helper.wtol_path(str(backup_path))
backup_path.replace(" ", "^ ")
backup_path = Path(backup_path)
backup_path_components = list(backup_path.parts)
backup_path_components[-1] = new_uuid
new_bu_path = pathlib.PurePath(os.path.join(*backup_path_components))
if os.path.isdir(new_bu_path):
if helper.validate_traversal(helper.backup_path, new_bu_path):
os.rmdir(new_bu_path)
backup_path.rename(new_bu_path)
def register_server(
self,
name: str,
server_uuid: str,
server_dir: str,
backup_path: str,
server_command: str,
server_file: str,
server_log_file: str,
server_stop: str,
server_port: int,
server_type: str,
):
# put data in the db
new_id = self.servers.create_server(
name,
server_uuid,
server_dir,
backup_path,
server_command,
server_file,
server_log_file,
server_stop,
server_type,
server_port,
)
if not helper.check_file_exists(os.path.join(server_dir, "crafty_managed.txt")):
try:
# place a file in the dir saying it's owned by crafty
with open(
os.path.join(server_dir, "crafty_managed.txt"),
"w",
encoding="utf-8",
) as f:
f.write(
"The server is managed by Crafty Controller.\n "
"Leave this directory/files alone please"
)
f.close()
except Exception as e:
logger.error(f"Unable to create required server files due to :{e}")
return False
# let's re-init all servers
self.init_all_servers()
return new_id
def remove_server(self, server_id, files):
counter = 0
for s in self.servers_list:
# if this is the droid... im mean server we are looking for...
if str(s["server_id"]) == str(server_id):
server_data = self.get_server_data(server_id)
server_name = server_data["server_name"]
logger.info(f"Deleting Server: ID {server_id} | Name: {server_name} ")
console.info(f"Deleting Server: ID {server_id} | Name: {server_name} ")
srv_obj = s["server_obj"]
running = srv_obj.check_running()
if running:
self.stop_server(server_id)
if files:
try:
file_helper.del_dirs(
helper.get_os_understandable_path(
self.servers.get_server_data_by_id(server_id)["path"]
)
)
except Exception as e:
logger.error(
f"Unable to delete server files for server with ID: "
f"{server_id} with error logged: {e}"
)
if helper.check_path_exists(
self.servers.get_server_data_by_id(server_id)["backup_path"]
):
file_helper.del_dirs(
helper.get_os_understandable_path(
self.servers.get_server_data_by_id(server_id)[
"backup_path"
]
)
)
# Cleanup scheduled tasks
try:
helpers_management.delete_scheduled_task_by_server(server_id)
except DoesNotExist:
logger.info("No scheduled jobs exist. Continuing.")
# remove the server from the DB
self.servers.remove_server(server_id)
# remove the server from servers list
self.servers_list.pop(counter)
counter += 1
@staticmethod
def clear_unexecuted_commands():
helpers_management.clear_unexecuted_commands()
@staticmethod
def clear_support_status():
helper_users.clear_support_status()