Merge branch 'dev' into bugfix/config-empty-mounts

This commit is contained in:
Zedifus 2023-12-07 13:05:40 +00:00
commit 1383accefd
13 changed files with 349 additions and 153 deletions

View File

@ -1,21 +1,25 @@
# Changelog # Changelog
## --- [4.2.2] - 2023/TBD ## --- [4.2.2] - 2023/TBD
### New features ### New features
TBD - Loading Screen for Crafty during startup ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/668))
### Refactor ### Refactor
- Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670)) - Remove deprecated API V1 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/670))
- Tidy up main.py to be more comprehensive ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/668))
### Bug fixes ### Bug fixes
- Remove webhook `custom` option from webook provider list as it's not currently an option ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/664)) - Remove webhook `custom` option from webook provider list as it's not currently an option ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/664))
- Bump cryptography for CVE-2023-49083 ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/680))
- Fix bug where su cannot edit general user password ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/676))
- Fix bug where no file error on import root dir ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/677))
- Fix Unban button failing to pardon users ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/671))
- Fix stack in API error handling ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/674))
### Tweaks ### Tweaks
- Homogenize Panel logos/branding ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/666)) - Homogenize Panel logos/branding ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/666))
- Retain previous tab when revisiting server details page (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667)) - Retain previous tab when revisiting server details page (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667))
- Add server name tag in panel header (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667)) - Add server name tag in panel header (#272)([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/667))
- Setup logging for panel authentication attempts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669)) - Setup logging for panel authentication attempts ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669))
- Update minimum password length from 6 to 8, and unrestrict maximum password length ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669)) - Update minimum password length from 6 to 8, and unrestrict maximum password length ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/669))
- Fix Unban button failing to pardon users ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/671))
- Fix stack in API error handling ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/674))
### Lang ### Lang
TBD - pl_PL Minor fixes ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/675))
<br><br> <br><br>
## --- [4.2.1] - 2023/11/01 ## --- [4.2.1] - 2023/11/01

View File

@ -22,6 +22,7 @@ from app.classes.models.server_permissions import (
PermissionsServers, PermissionsServers,
EnumPermissionsServer, EnumPermissionsServer,
) )
from app.classes.shared.websocket_manager import WebSocketManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@ -36,6 +37,7 @@ class ServersController(metaclass=Singleton):
self.management_helper = management_helper self.management_helper = management_helper
self.servers_list = [] self.servers_list = []
self.stats = Stats(self.helper, self) self.stats = Stats(self.helper, self)
self.web_sock = WebSocketManager()
self.server_subpage = {} self.server_subpage = {}
# ********************************************************************************** # **********************************************************************************
@ -170,8 +172,15 @@ class ServersController(metaclass=Singleton):
def init_all_servers(self): def init_all_servers(self):
servers = self.get_all_defined_servers() servers = self.get_all_defined_servers()
self.failed_servers = [] self.failed_servers = []
for server in servers: for server in servers:
self.web_sock.broadcast_to_admins(
"update",
{"section": "server", "server": server["server_name"]},
)
self.web_sock.broadcast_to_non_admins(
"update",
{"section": "init"},
)
server_id = server.get("server_id") server_id = server.get("server_id")
# if we have already initialized this server, let's skip it. # if we have already initialized this server, let's skip it.

View File

@ -80,6 +80,7 @@ class Helpers:
self.translation = Translation(self) self.translation = Translation(self)
self.update_available = False self.update_available = False
self.ignored_names = ["crafty_managed.txt", "db_stats"] self.ignored_names = ["crafty_managed.txt", "db_stats"]
self.crafty_starting = False
@staticmethod @staticmethod
def auto_installer_fix(ex): def auto_installer_fix(ex):

View File

@ -85,7 +85,7 @@ class Controller:
encoding="utf-8", encoding="utf-8",
) as f: ) as f:
self.auth_tracker = json.load(f) self.auth_tracker = json.load(f)
except: except (FileNotFoundError, json.JSONDecodeError):
self.auth_tracker = {} self.auth_tracker = {}
def log_attempt(self, remote_ip, username): def log_attempt(self, remote_ip, username):

View File

@ -37,7 +37,15 @@ class WebSocketManager(metaclass=Singleton):
def broadcast_to_admins(self, event_type: str, data): def broadcast_to_admins(self, event_type: str, data):
def filter_fn(client): def filter_fn(client):
if client.get_user_id in HelperUsers.get_super_user_list(): if str(client.get_user_id()) in str(HelperUsers.get_super_user_list()):
return True
return False
self.broadcast_with_fn(filter_fn, event_type, data)
def broadcast_to_non_admins(self, event_type: str, data):
def filter_fn(client):
if str(client.get_user_id()) not in str(HelperUsers.get_super_user_list()):
return True return True
return False return False

View File

@ -210,6 +210,8 @@ class PanelHandler(BaseHandler):
error = self.get_argument("error", "WTF Error!") error = self.get_argument("error", "WTF Error!")
template = "panel/denied.html" template = "panel/denied.html"
if self.helper.crafty_starting:
page = "loading"
now = time.time() now = time.time()
formatted_time = str( formatted_time = str(
@ -243,9 +245,13 @@ class PanelHandler(BaseHandler):
for r in exec_user["roles"]: for r in exec_user["roles"]:
role = self.controller.roles.get_role(r) role = self.controller.roles.get_role(r)
exec_user_role.add(role["role_name"]) exec_user_role.add(role["role_name"])
defined_servers = self.controller.servers.get_authorized_servers( # get_auth_servers will throw an exception if run while Crafty is starting
exec_user["user_id"] if not self.helper.crafty_starting:
) defined_servers = self.controller.servers.get_authorized_servers(
exec_user["user_id"]
)
else:
defined_servers = []
user_order = self.controller.users.get_user_by_id(exec_user["user_id"]) user_order = self.controller.users.get_user_by_id(exec_user["user_id"])
user_order = user_order["server_order"].split(",") user_order = user_order["server_order"].split(",")
@ -1615,7 +1621,8 @@ class PanelHandler(BaseHandler):
logs_thread.start() logs_thread.start()
self.redirect("/panel/dashboard") self.redirect("/panel/dashboard")
return return
if self.helper.crafty_starting:
template = "panel/loading.html"
self.render( self.render(
template, template,
data=page_data, data=page_data,

View File

@ -7,6 +7,7 @@ from jsonschema.exceptions import ValidationError
from app.classes.models.crafty_permissions import EnumPermissionsCrafty from app.classes.models.crafty_permissions import EnumPermissionsCrafty
from app.classes.shared.helpers import Helpers from app.classes.shared.helpers import Helpers
from app.classes.web.base_api_handler import BaseApiHandler from app.classes.web.base_api_handler import BaseApiHandler
from app.classes.web.websocket_handler import WebSocketManager
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
files_get_schema = { files_get_schema = {
@ -73,7 +74,7 @@ class ApiImportFilesIndexHandler(BaseApiHandler):
else: else:
if user_id: if user_id:
user_lang = self.controller.users.get_user_lang_by_id(user_id) user_lang = self.controller.users.get_user_lang_by_id(user_id)
self.helper.websocket_helper.broadcast_user( WebSocketManager().broadcast_user(
user_id, user_id,
"send_start_error", "send_start_error",
{ {
@ -85,7 +86,7 @@ class ApiImportFilesIndexHandler(BaseApiHandler):
else: else:
if not self.helper.check_path_exists(folder) and user_id: if not self.helper.check_path_exists(folder) and user_id:
user_lang = self.controller.users.get_user_lang_by_id(user_id) user_lang = self.controller.users.get_user_lang_by_id(user_id)
self.helper.websocket_helper.broadcast_user( WebSocketManager().broadcast_user(
user_id, user_id,
"send_start_error", "send_start_error",
{ {

View File

@ -215,7 +215,7 @@ class ApiUsersUserIndexHandler(BaseApiHandler):
user_obj = HelperUsers.get_user_model(user_id) user_obj = HelperUsers.get_user_model(user_id)
if "password" in data and str(user["user_id"]) != str(user_id): if "password" in data and str(user["user_id"]) != str(user_id):
if str(user["user_id"]) != str(user_obj.manager): if str(user["user_id"]) != str(user_obj.manager) and not user["superuser"]:
# TODO: edit your own password # TODO: edit your own password
return self.finish_json( return self.finish_json(
400, {"status": "error", "error": "INVALID_PASSWORD_MODIFY"} 400, {"status": "error", "error": "INVALID_PASSWORD_MODIFY"}

View File

@ -0,0 +1,73 @@
{% extends ../base.html %}
{% block meta %}
{% end %}
{% block title %}Crafty Controller Starting{% end %}
{% block content %}
<div class="content-wrapper">
<div class="card-header justify-content-between align-items-center" style="border: none;">
<div id="image-div" style="width: 100%;">
<img class="img-center" id="logo-animate" src="../static/assets/images/crafty-logo-square-1024.png"
alt="Crafty Logo, Crafty is loading" width="20%" style="clear: both;">
</div>
<br>
</br>
<div id="text-div" style="width: 100%; text-align: center;">
<h2 id="status" style="display: block;" data-init="{{ translate('startup', 'serverInit', data['lang']) }}"
data-server="{{ translate('startup', 'server', data['lang']) }}"
data-internet="{{ translate('startup', 'internet', data['lang']) }}"
data-tasks="{{ translate('startup', 'tasks', data['lang']) }}"
data-internals="{{ translate('startup', 'internals', data['lang']) }}"
data-almost="{{ translate('startup', 'almost', data['lang']) }}">
{{ translate('startup', 'starting', data['lang']) }}</h2>
</div>
</div>
</div>
<style>
.img-center {
display: block;
margin-left: auto;
margin-right: auto;
}
</style>
<script>
function rotateImage(degree) {
$('#logo-animate').animate({ transform: degree }, {
step: function (now, fx) {
$(this).css({
'-webkit-transform': 'rotate(' + now + 'deg)',
'-moz-transform': 'rotate(' + now + 'deg)',
'transform': 'rotate(' + now + 'deg)'
});
}
});
setTimeout(function () {
rotateImage(360);
}, 2000);
}
$(document).ready(function () {
setTimeout(function () {
rotateImage(360);
}, 2000);
if (webSocket) {
webSocket.on('update', function (data) {
if ("server" in data) {
$("#status").html(`${$("#status").data(data.section)} ${data.server}...`);
} else {
$("#status").html($("#status").data(data.section));
}
});
webSocket.on('send_start_reload', function () {
setTimeout(function () {
location.href = '/panel/dashboard'
}, 5000);
});
}
});
</script>
{% end %}

View File

@ -591,6 +591,15 @@
"newServer": "Create New Server", "newServer": "Create New Server",
"servers": "Servers" "servers": "Servers"
}, },
"startup": {
"starting": "Crafty Is Starting...",
"serverInit": "Initializing Servers",
"server": "Initializing ",
"internet": "Checking for internet connection",
"tasks": "Starting Tasks Scheduler",
"internals": "Configuring and starting Crafty's internal componenets",
"almost": "Finishing up. Hang on tight..."
},
"userConfig": { "userConfig": {
"apiKey": "API Keys", "apiKey": "API Keys",
"auth": "Authorized? ", "auth": "Authorized? ",

View File

@ -326,8 +326,8 @@
"bePatientDeleteFiles": "Poczekaj, aż usuniemy twój serwer i jego pliki. Strona za chwilę się zamknie.", "bePatientDeleteFiles": "Poczekaj, aż usuniemy twój serwer i jego pliki. Strona za chwilę się zamknie.",
"bePatientUpdate": "Poczekaj kiedy my aktualizujemy twój serwer. Pobieranie zależy od prędkości twojego internetu.<br /> Strona się odświeży za chwile.", "bePatientUpdate": "Poczekaj kiedy my aktualizujemy twój serwer. Pobieranie zależy od prędkości twojego internetu.<br /> Strona się odświeży za chwile.",
"cancel": "Anuluj", "cancel": "Anuluj",
"crashTime": "Crash wyszedł poza limit czasu", "crashTime": "Crash serwera wyszedł poza limit czasu",
"crashTimeDesc": "How long should we wait before we consider your server as crashed?", "crashTimeDesc": "Jak długo powinniśmy poczekać zanim uznać serwer za zcrashowany?",
"deleteFilesQuestion": "Usuń pliki serwera z maszyny?", "deleteFilesQuestion": "Usuń pliki serwera z maszyny?",
"deleteFilesQuestionMessage": "Czy chcesz aby Crafty usunął wszystkie pliki tego serwera? <br><br><strong>To zawiera backupy.</strong>", "deleteFilesQuestionMessage": "Czy chcesz aby Crafty usunął wszystkie pliki tego serwera? <br><br><strong>To zawiera backupy.</strong>",
"deleteServer": "Usuń serwer", "deleteServer": "Usuń serwer",
@ -403,7 +403,7 @@
"filterList": "Filtrowane słowa", "filterList": "Filtrowane słowa",
"logs": "Logi", "logs": "Logi",
"metrics": "Statystyki", "metrics": "Statystyki",
"playerControls": "Player Management", "playerControls": "Zarządzanie użytkownikami",
"reset": "Resetuj Scrolla", "reset": "Resetuj Scrolla",
"schedule": "Harmonogram", "schedule": "Harmonogram",
"serverDetails": "Detale serwera", "serverDetails": "Detale serwera",
@ -421,7 +421,7 @@
"deleteItemQuestion": "Czy jesteś pewien że chcesz usunąć \" + name + \"?", "deleteItemQuestion": "Czy jesteś pewien że chcesz usunąć \" + name + \"?",
"deleteItemQuestionMessage": "Usuwasz \\\"\" + path + \"\\\"!<br/><br/>Ta akcja jest nieodwracalna i zostanie usunięta na zawsze!", "deleteItemQuestionMessage": "Usuwasz \\\"\" + path + \"\\\"!<br/><br/>Ta akcja jest nieodwracalna i zostanie usunięta na zawsze!",
"download": "Pobierz", "download": "Pobierz",
"editingFile": "Edytuję plik", "editingFile": "Edytuj plik",
"error": "Error while getting files", "error": "Error while getting files",
"fileReadError": "Error odczytu pliku", "fileReadError": "Error odczytu pliku",
"files": "Pliki", "files": "Pliki",
@ -432,7 +432,7 @@
"rename": "Zmień nazwę", "rename": "Zmień nazwę",
"renameItemQuestion": "Jaka ma być nowa nazwa?", "renameItemQuestion": "Jaka ma być nowa nazwa?",
"save": "Zapisz", "save": "Zapisz",
"size": "Włącz zmienianie rozmiaru edytora", "size": "Włącz rozszerzanie i zmniejszanie edytora",
"stayHere": "NIE WYCHODŹ Z TEJ STRONY!", "stayHere": "NIE WYCHODŹ Z TEJ STRONY!",
"unsupportedLanguage": "Uwaga: To nie jest wspierany typ pliku", "unsupportedLanguage": "Uwaga: To nie jest wspierany typ pliku",
"unzip": "Rozpakuj", "unzip": "Rozpakuj",
@ -545,7 +545,7 @@
"buildServer": "Zbuduj serwer!", "buildServer": "Zbuduj serwer!",
"clickRoot": "Kilknij tutaj aby zaznaczyć główną ścieżkę", "clickRoot": "Kilknij tutaj aby zaznaczyć główną ścieżkę",
"close": "Zamknij", "close": "Zamknij",
"defaultPort": "25565 podstawowy", "defaultPort": "Domyślnie 25565",
"downloading": "Pobieranie serwera...", "downloading": "Pobieranie serwera...",
"explainRoot": "Proszę, kliknij przycisk poniżej aby zaznaczyć główną ścieżkę w tym archiwum", "explainRoot": "Proszę, kliknij przycisk poniżej aby zaznaczyć główną ścieżkę w tym archiwum",
"importServer": "Importuj egzystujący serwer", "importServer": "Importuj egzystujący serwer",
@ -640,12 +640,12 @@
"edit": "Edytuj", "edit": "Edytuj",
"enabled": "Włączony", "enabled": "Włączony",
"jar_update": "Plik startowy zaktualizowany", "jar_update": "Plik startowy zaktualizowany",
"kill": "Serwer zatrzymany", "kill": "Serwer został zabity",
"name": "Nazwa", "name": "Nazwa",
"new": "Nowy Webhook", "new": "Nowy Webhook",
"newWebhook": "Nowy Webhook", "newWebhook": "Nowy Webhook",
"no-webhook": "Nie posiadasz aktualnie żadnych Webhooków dla tego serwera. Aby dodać webhook kliknij na", "no-webhook": "Nie posiadasz aktualnie żadnych Webhooków dla tego serwera. Aby dodać webhook kliknij na",
"run": "Włącz Webhook", "run": "Przetestuj Webhook",
"send_command": "Komenda serwera otrzymana!", "send_command": "Komenda serwera otrzymana!",
"start_server": "Serwer włączony", "start_server": "Serwer włączony",
"stop_server": "Serwer wyłączony", "stop_server": "Serwer wyłączony",

342
main.py
View File

@ -20,6 +20,18 @@ from app.classes.shared.websocket_manager import WebSocketManager
console = Console() console = Console()
helper = Helpers() helper = Helpers()
# Get the path our application is running on.
if getattr(sys, "frozen", False):
APPLICATION_PATH = os.path.dirname(sys.executable)
RUNNING_MODE = "Frozen/executable"
else:
try:
app_full_path = os.path.realpath(__file__)
APPLICATION_PATH = os.path.dirname(app_full_path)
RUNNING_MODE = "Non-interactive (e.g. 'python main.py')"
except NameError:
APPLICATION_PATH = os.getcwd()
RUNNING_MODE = "Interactive"
if helper.check_root(): if helper.check_root():
Console.critical( Console.critical(
"Root detected. Root/Admin access denied. " "Root detected. Root/Admin access denied. "
@ -51,7 +63,178 @@ except ModuleNotFoundError as err:
helper.auto_installer_fix(err) helper.auto_installer_fix(err)
def internet_check():
"""
This checks to see if the Crafty host is connected to the
internet. This will show a warning in the console if no interwebs.
"""
print()
logger.info("Checking Internet. This may take a minute.")
Console.info("Checking Internet. This may take a minute.")
if not helper.check_internet():
logger.warning(
"We have detected the machine running Crafty has no "
"connection to the internet. Client connections to "
"the server may be limited."
)
Console.warning(
"We have detected the machine running Crafty has no "
"connection to the internet. Client connections to "
"the server may be limited."
)
def controller_setup():
"""
Method sets up the software controllers.
This also sets the application path as well as the
master server dir (if not set).
This also clears the support logs status.
"""
if not controller.check_system_user():
controller.add_system_user()
master_server_dir = controller.management.get_master_server_dir()
if master_server_dir == "":
logger.debug("Could not find master server path. Setting default")
controller.set_master_server_dir(
os.path.join(controller.project_root, "servers")
)
else:
helper.servers_dir = master_server_dir
Console.debug(f"Execution Mode: {RUNNING_MODE}")
Console.debug(f"Application path : '{APPLICATION_PATH}'")
controller.clear_support_status()
def tasks_starter():
"""
Method starts stats recording, app scheduler, and
serverjars/steamCMD cache refreshers
"""
# start stats logging
tasks_manager.start_stats_recording()
# once the controller is up and stats are logging, we can kick off
# the scheduler officially
tasks_manager.start_scheduler()
# refresh our cache and schedule for every 12 hoursour cache refresh
# for serverjars.com
tasks_manager.serverjar_cache_refresher()
def signal_handler(signum, _frame):
"""
Method handles sigterm and shuts the app down.
"""
if not args.daemon:
print() # for newline after prompt
signame = signal.Signals(signum).name
logger.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
Console.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
tasks_manager._main_graceful_exit()
crafty_prompt.universal_exit()
def do_cleanup():
"""
Checks Crafty's temporary directory and clears it out on boot.
"""
try:
logger.info("Removing old temp dirs")
FileHelpers.del_dirs(os.path.join(controller.project_root, "temp"))
except:
logger.info("Did not find old temp dir.")
os.mkdir(os.path.join(controller.project_root, "temp"))
def do_version_check():
"""
Checks for remote version differences.
Prints in terminal with differences if true.
Also sets helper variable to update available when pages
are served.
"""
# Check if new version available
remote_ver = helper.check_remote_version()
if remote_ver:
notice = f"""
A new version of Crafty is available!
{'/' * 37}
New version available: {remote_ver}
Current version: {pkg_version.parse(helper.get_version_string())}
{'/' * 37}
"""
Console.yellow(notice)
crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > "
def setup_starter():
"""
This method starts our setup threads.
(tasks scheduler, internet checks, controller setups)
Once our threads complete we will set our startup
variable to false and send a reload to any clients waiting.
"""
if not args.daemon:
time.sleep(0.01) # Wait for the prompt to start
print() # Make a newline after the prompt so logs are on an empty line
else:
time.sleep(0.01) # Wait for the daemon info message
Console.info("Setting up Crafty's internal components...")
# Start the setup threads
web_sock.broadcast("update", {"section": "tasks"})
time.sleep(2)
tasks_starter_thread.start()
web_sock.broadcast("update", {"section": "internet"})
time.sleep(2)
internet_check_thread.start()
web_sock.broadcast(
"update",
{"section": "internals"},
)
time.sleep(2)
controller_setup_thread.start()
# Wait for the setup threads to finish
web_sock.broadcast(
"update",
{"section": "almost"},
)
tasks_starter_thread.join()
internet_check_thread.join()
controller_setup_thread.join()
helper.crafty_starting = False
web_sock.broadcast("send_start_reload", "")
do_version_check()
Console.info("Crafty has fully started and is now ready for use!")
do_cleanup()
if not args.daemon:
# Put the prompt under the cursor
crafty_prompt.print_prompt()
def do_intro(): def do_intro():
"""
Runs the Crafty Controller Terminal Intro with information about the software
This method checks for a "settings file" or config.json. If it does not find
one it will create one.
"""
logger.info("***** Crafty Controller Started *****") logger.info("***** Crafty Controller Started *****")
version = helper.get_version_string() version = helper.get_version_string()
@ -72,6 +255,11 @@ def do_intro():
def setup_logging(debug=True): def setup_logging(debug=True):
"""
This method sets up our logging for Crafty. It takes
one optional (defaulted to True) parameter which
determines whether or not the logging level is "debug" or verbose.
"""
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json") logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json")
if not helper.check_file_exists( if not helper.check_file_exists(
os.path.join(os.path.curdir, "logs", "auth_tracker.log") os.path.join(os.path.curdir, "logs", "auth_tracker.log")
@ -117,11 +305,11 @@ if __name__ == "__main__":
) )
args = parser.parse_args() args = parser.parse_args()
helper.ensure_logging_setup() helper.ensure_logging_setup()
helper.crafty_starting = True
# Init WebSocket Manager Here
web_sock = WebSocketManager()
setup_logging(debug=args.verbose) setup_logging(debug=args.verbose)
if args.verbose: if args.verbose:
Console.level = "debug" Console.level = "debug"
@ -133,23 +321,27 @@ if __name__ == "__main__":
# print our pretty start message # print our pretty start message
do_intro() do_intro()
# our session file, helps prevent multiple controller agents on the same machine. # our session file, helps prevent multiple controller agents on the same machine.
helper.create_session_file(ignore=args.ignore) helper.create_session_file(ignore=args.ignore)
# start the database # start the database
database = peewee.SqliteDatabase( database = peewee.SqliteDatabase(
helper.db_path, pragmas={"journal_mode": "wal", "cache_size": -1024 * 10} helper.db_path, pragmas={"journal_mode": "wal", "cache_size": -1024 * 10}
) )
database_proxy.initialize(database) database_proxy.initialize(database)
migration_manager = MigrationManager(database, helper) migration_manager = MigrationManager(database, helper)
migration_manager.up() # Automatically runs migrations migration_manager.up() # Automatically runs migrations
# do our installer stuff # init classes
# now the tables are created, we can load the tasks_manager and server controller
user_helper = HelperUsers(database, helper) user_helper = HelperUsers(database, helper)
management_helper = HelpersManagement(database, helper) management_helper = HelpersManagement(database, helper)
installer = DatabaseBuilder(database, helper, user_helper, management_helper) installer = DatabaseBuilder(database, helper, user_helper, management_helper)
file_helper = FileHelpers(helper)
import_helper = ImportHelpers(helper, file_helper)
controller = Controller(database, helper, file_helper, import_helper)
controller.set_project_root(APPLICATION_PATH)
tasks_manager = TasksManager(helper, controller, file_helper)
import3 = Import3(helper, controller)
FRESH_INSTALL = installer.is_fresh_install() FRESH_INSTALL = installer.is_fresh_install()
if FRESH_INSTALL: if FRESH_INSTALL:
@ -171,153 +363,45 @@ if __name__ == "__main__":
helper.set_setting("reset_secrets_on_next_boot", False) helper.set_setting("reset_secrets_on_next_boot", False)
else: else:
Console.info("No flag found. Secrets are staying") Console.info("No flag found. Secrets are staying")
file_helper = FileHelpers(helper)
import_helper = ImportHelpers(helper, file_helper) # Check to see if client config.json version is different than the
# Init WebSocket Manager Here # Master config.json in helpers.py
WebSocketManager()
# now the tables are created, we can load the tasks_manager and server controller
controller = Controller(database, helper, file_helper, import_helper)
Console.info("Checking for remote changes to config.json") Console.info("Checking for remote changes to config.json")
controller.get_config_diff() controller.get_config_diff()
Console.info("Remote change complete.") Console.info("Remote change complete.")
import3 = Import3(helper, controller) # startup the web server
tasks_manager = TasksManager(helper, controller, file_helper)
tasks_manager.start_webserver() tasks_manager.start_webserver()
def signal_handler(signum, _frame):
if not args.daemon:
print() # for newline after prompt
signame = signal.Signals(signum).name
logger.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
Console.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
tasks_manager._main_graceful_exit()
crafty_prompt.universal_exit()
signal.signal(signal.SIGTERM, signal_handler) signal.signal(signal.SIGTERM, signal_handler)
signal.signal(signal.SIGINT, signal_handler) signal.signal(signal.SIGINT, signal_handler)
# init servers # init servers
logger.info("Initializing all servers defined") logger.info("Initializing all servers defined")
Console.info("Initializing all servers defined") Console.info("Initializing all servers defined")
web_sock.broadcast(
"update",
{"section": "serverInit"},
)
controller.servers.init_all_servers() controller.servers.init_all_servers()
def tasks_starter(): # start up our tasks handler in tasks.py
# start stats logging
tasks_manager.start_stats_recording()
# once the controller is up and stats are logging, we can kick off
# the scheduler officially
tasks_manager.start_scheduler()
# refresh our cache and schedule for every 12 hoursour cache refresh
# for serverjars.com
tasks_manager.serverjar_cache_refresher()
tasks_starter_thread = Thread(target=tasks_starter, name="tasks_starter") tasks_starter_thread = Thread(target=tasks_starter, name="tasks_starter")
def internet_check(): # check to see if instance has internet
print()
logger.info("Checking Internet. This may take a minute.")
Console.info("Checking Internet. This may take a minute.")
if not helper.check_internet():
logger.warning(
"We have detected the machine running Crafty has no "
"connection to the internet. Client connections to "
"the server may be limited."
)
Console.warning(
"We have detected the machine running Crafty has no "
"connection to the internet. Client connections to "
"the server may be limited."
)
internet_check_thread = Thread(target=internet_check, name="internet_check") internet_check_thread = Thread(target=internet_check, name="internet_check")
def controller_setup(): # start the Crafty console.
if not controller.check_system_user():
controller.add_system_user()
if getattr(sys, "frozen", False):
application_path = os.path.dirname(sys.executable)
running_mode = "Frozen/executable"
else:
try:
app_full_path = os.path.realpath(__file__)
application_path = os.path.dirname(app_full_path)
running_mode = "Non-interactive (e.g. 'python main.py')"
except NameError:
application_path = os.getcwd()
running_mode = "Interactive"
controller.set_project_root(application_path)
master_server_dir = controller.management.get_master_server_dir()
if master_server_dir == "":
logger.debug("Could not find master server path. Setting default")
controller.set_master_server_dir(
os.path.join(controller.project_root, "servers")
)
else:
helper.servers_dir = master_server_dir
Console.debug(f"Execution Mode: {running_mode}")
Console.debug(f"Application path : '{application_path}'")
controller.clear_support_status()
crafty_prompt = MainPrompt( crafty_prompt = MainPrompt(
helper, tasks_manager, migration_manager, controller, import3 helper, tasks_manager, migration_manager, controller, import3
) )
# set up all controllers
controller_setup_thread = Thread(target=controller_setup, name="controller_setup") controller_setup_thread = Thread(target=controller_setup, name="controller_setup")
def setup_starter(): setup_starter_thread = Thread(target=setup_starter, name="setup_starter")
if not args.daemon:
time.sleep(0.01) # Wait for the prompt to start
print() # Make a newline after the prompt so logs are on an empty line
else:
time.sleep(0.01) # Wait for the daemon info message
Console.info("Setting up Crafty's internal components...") setup_starter_thread.start()
# Start the setup threads
tasks_starter_thread.start()
internet_check_thread.start()
controller_setup_thread.start()
# Wait for the setup threads to finish
tasks_starter_thread.join()
internet_check_thread.join()
controller_setup_thread.join()
Console.info("Crafty has fully started and is now ready for use!")
# Check if new version available
remote_ver = helper.check_remote_version()
if remote_ver:
notice = f"""
A new version of Crafty is available!
{'/' * 37}
New version available: {remote_ver}
Current version: {pkg_version.parse(helper.get_version_string())}
{'/' * 37}
"""
Console.yellow(notice)
crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > "
try:
logger.info("Removing old temp dirs")
FileHelpers.del_dirs(os.path.join(controller.project_root, "temp"))
except:
logger.info("Did not find old temp dir.")
os.mkdir(os.path.join(controller.project_root, "temp"))
if not args.daemon:
# Put the prompt under the cursor
crafty_prompt.print_prompt()
Thread(target=setup_starter, name="setup_starter").start()
if not args.daemon: if not args.daemon:
# Start the Crafty prompt # Start the Crafty prompt

View File

@ -4,13 +4,13 @@ argon2-cffi==23.1.0
cached_property==1.5.2 cached_property==1.5.2
colorama==0.4.6 colorama==0.4.6
croniter==1.4.1 croniter==1.4.1
cryptography==41.0.4 cryptography==41.0.7
libgravatar==1.0.4 libgravatar==1.0.4
nh3==0.2.14 nh3==0.2.14
packaging==23.2 packaging==23.2
peewee==3.13 peewee==3.13
psutil==5.9.5 psutil==5.9.5
pyOpenSSL==23.2.0 pyOpenSSL==23.3.0
pyjwt==2.8.0 pyjwt==2.8.0
PyYAML==6.0.1 PyYAML==6.0.1
requests==2.31.0 requests==2.31.0