diff --git a/CHANGELOG.md b/CHANGELOG.md
index 2dc69fc9..f47beb06 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -2,9 +2,11 @@
## --- [4.0.12] - 2022/09/04
### New features
- Win Portable Updater will now be included in Windows Package ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/446))
+- Bedrock Server Creator ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/443))
### Bug fixes
-- Fix performance issues on server metrics panels (Temporarily setting to 24hr query) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/440))
+- Fix performance issues on server metrics panels 'with metrics range' ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/440)) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/448))
- Fix no id on import3 servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/442))
+- Fix functionality of bedrock update ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/441))
### Tweaks
TBD
### Lang
diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py
index c9434b13..1bf81564 100644
--- a/app/classes/controllers/servers_controller.py
+++ b/app/classes/controllers/servers_controller.py
@@ -105,10 +105,9 @@ class ServersController(metaclass=Singleton):
server_instance.update_server_instance()
return ret
- def get_history_stats(self, server_id):
- now = datetime.datetime.now()
+ def get_history_stats(self, server_id, days):
srv = ServersController().get_server_instance_by_id(server_id)
- return srv.stats_helper.get_history_stats(server_id)
+ return srv.stats_helper.get_history_stats(server_id, days)
@staticmethod
def update_unloaded_server(server_obj):
diff --git a/app/classes/models/server_stats.py b/app/classes/models/server_stats.py
index d6c6efdc..29fdb856 100644
--- a/app/classes/models/server_stats.py
+++ b/app/classes/models/server_stats.py
@@ -138,8 +138,8 @@ class HelperServerStats:
)
return server_data
- def get_history_stats(self, server_id):
- max_age = datetime.datetime.now() - timedelta(days=1)
+ def get_history_stats(self, server_id, num_days):
+ max_age = datetime.datetime.now() - timedelta(days=num_days)
return (
ServerStats.select()
.where(ServerStats.created > max_age)
diff --git a/app/classes/shared/file_helpers.py b/app/classes/shared/file_helpers.py
index 04ec3305..28edbef7 100644
--- a/app/classes/shared/file_helpers.py
+++ b/app/classes/shared/file_helpers.py
@@ -64,6 +64,11 @@ class FileHelpers:
FileHelpers.copy_dir(src_path, dest_path)
FileHelpers.del_dirs(src_path)
+ @staticmethod
+ def move_dir_exist(src_path, dest_path):
+ FileHelpers.copy_dir(src_path, dest_path, True)
+ FileHelpers.del_dirs(src_path)
+
@staticmethod
def move_file(src_path, dest_path):
FileHelpers.copy_file(src_path, dest_path)
@@ -290,7 +295,7 @@ class FileHelpers:
for item in os.listdir(full_root_path):
if os.path.isdir(os.path.join(full_root_path, item)):
try:
- FileHelpers.move_dir(
+ FileHelpers.move_dir_exist(
os.path.join(full_root_path, item),
os.path.join(new_dir, item),
)
diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py
index 40a57219..be278578 100644
--- a/app/classes/shared/helpers.py
+++ b/app/classes/shared/helpers.py
@@ -107,6 +107,42 @@ class Helpers:
logger.error(f"Unable to check for new crafty version! \n{e}")
return False
+ @staticmethod
+ def get_latest_bedrock_url():
+ """
+ Get latest bedrock executable url \n\n
+ returns url if successful, False if not
+ """
+ url = "https://minecraft.net/en-us/download/server/bedrock/"
+ headers = {
+ "Accept-Encoding": "identity",
+ "Accept-Language": "en",
+ "User-Agent": (
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
+ "Chrome/104.0.0.0 Safari/537.36"
+ ),
+ }
+ target_win = 'https://minecraft.azureedge.net/bin-win/[^"]*'
+ target_linux = 'https://minecraft.azureedge.net/bin-linux/[^"]*'
+
+ try:
+ # Get minecraft server download page
+ # (hopefully the don't change the structure)
+ download_page = get(url, headers=headers)
+
+ # Search for our string targets
+ win_download_url = re.search(target_win, download_page.text).group(0)
+ linux_download_url = re.search(target_linux, download_page.text).group(0)
+
+ if os.name == "nt":
+ return win_download_url
+
+ return linux_download_url
+ except Exception as e:
+ logger.error(f"Unable to resolve remote bedrock download url! \n{e}")
+ return False
+
@staticmethod
def find_java_installs():
# If we're windows return oracle java versions,
diff --git a/app/classes/shared/import_helper.py b/app/classes/shared/import_helper.py
index 769ebc3a..88ee91fc 100644
--- a/app/classes/shared/import_helper.py
+++ b/app/classes/shared/import_helper.py
@@ -3,6 +3,7 @@ import time
import shutil
import logging
import threading
+import urllib
from app.classes.controllers.server_perms_controller import PermissionsServers
from app.classes.controllers.servers_controller import ServersController
@@ -214,3 +215,43 @@ class ImportHelpers:
os.chmod(full_jar_path, 0o2760)
# deletes temp dir
FileHelpers.del_dirs(temp_dir)
+
+ def download_bedrock_server(self, path, new_id):
+ download_thread = threading.Thread(
+ target=self.download_threaded_bedrock_server,
+ daemon=True,
+ args=(path, new_id),
+ name=f"{new_id}_download",
+ )
+ download_thread.start()
+
+ def download_threaded_bedrock_server(self, path, new_id):
+
+ # downloads zip from remote url
+ try:
+ bedrock_url = Helpers.get_latest_bedrock_url()
+ if bedrock_url.lower().startswith("https"):
+ urllib.request.urlretrieve(
+ bedrock_url,
+ os.path.join(path, "bedrock_server.zip"),
+ )
+
+ unzip_path = os.path.join(path, "bedrock_server.zip")
+ unzip_path = self.helper.wtol_path(unzip_path)
+ # unzips archive that was downloaded.
+ FileHelpers.unzip_file(unzip_path)
+ # adjusts permissions for execution if os is not windows
+ if not self.helper.is_os_windows():
+ os.chmod(os.path.join(path, "bedrock_server"), 0o0744)
+
+ # we'll delete the zip we downloaded now
+ os.remove(os.path.join(path, "bedrock_server.zip"))
+ except Exception as e:
+ logger.critical(
+ f"Failed to download bedrock executable during server creation! \n{e}"
+ )
+
+ ServersController.finish_import(new_id)
+ server_users = PermissionsServers.get_server_user_list(new_id)
+ for user in server_users:
+ self.helper.websocket_helper.broadcast_user(user, "send_start_reload", {})
diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py
index cfda4c8d..68645b86 100644
--- a/app/classes/shared/main_controller.py
+++ b/app/classes/shared/main_controller.py
@@ -697,6 +697,49 @@ class Controller:
)
return new_id
+ def create_bedrock_server(self, server_name, user_id):
+ server_id = Helpers.create_uuid()
+ new_server_dir = os.path.join(self.helper.servers_dir, server_id)
+ backup_path = os.path.join(self.helper.backup_path, server_id)
+ server_exe = "bedrock_server"
+ if Helpers.is_os_windows():
+ # if this is windows we will override the linux bedrock server name.
+ server_exe = "bedrock_server.exe"
+ new_server_dir = Helpers.wtol_path(new_server_dir)
+ backup_path = Helpers.wtol_path(backup_path)
+ new_server_dir.replace(" ", "^ ")
+ backup_path.replace(" ", "^ ")
+
+ Helpers.ensure_dir_exists(new_server_dir)
+ Helpers.ensure_dir_exists(backup_path)
+
+ full_jar_path = os.path.join(new_server_dir, server_exe)
+
+ if Helpers.is_os_windows():
+ server_command = f'"{full_jar_path}"'
+ else:
+ server_command = f"./{server_exe}"
+ logger.debug("command: " + server_command)
+ server_log_file = ""
+ 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,
+ "19132",
+ user_id,
+ server_type="minecraft-bedrock",
+ )
+ ServersController.set_import(new_id)
+ self.import_helper.download_bedrock_server(new_server_dir, new_id)
+ return new_id
+
def import_bedrock_zip_server(
self,
server_name: str,
diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py
index 0b2c5b96..6501fb1c 100644
--- a/app/classes/shared/server.py
+++ b/app/classes/shared/server.py
@@ -9,6 +9,7 @@ import threading
import logging.config
import subprocess
import html
+import urllib.request
# TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone
@@ -337,7 +338,7 @@ class ServerInstance:
"eula =true",
]
- if not e_flag:
+ if not e_flag and self.settings["type"] == "minecraft-java":
if user_id:
self.helper.websocket_helper.broadcast_user(
user_id, "send_eula_bootbox", {"id": self.server_id}
@@ -1078,66 +1079,91 @@ class ServerInstance:
)
# checks if backup directory already exists
if os.path.isdir(backup_dir):
- backup_executable = os.path.join(backup_dir, "old_server.jar")
+ backup_executable = os.path.join(backup_dir, self.settings["executable"])
else:
logger.info(
f"Executable backup directory not found for Server: {self.name}."
f" Creating one."
)
os.mkdir(backup_dir)
- backup_executable = os.path.join(backup_dir, "old_server.jar")
+ backup_executable = os.path.join(backup_dir, self.settings["executable"])
- if os.path.isfile(backup_executable):
- # removes old backup
- logger.info(f"Old backup found for server: {self.name}. Removing...")
- os.remove(backup_executable)
- logger.info(f"Old backup removed for server: {self.name}.")
- else:
- logger.info(f"No old backups found for server: {self.name}")
+ if len(os.listdir(backup_dir)) > 0:
+ # removes old backup
+ logger.info(f"Old backups found for server: {self.name}. Removing...")
+ for item in os.listdir(backup_dir):
+ os.remove(os.path.join(backup_dir, item))
+ logger.info(f"Old backups removed for server: {self.name}.")
+ else:
+ logger.info(f"No old backups found for server: {self.name}")
current_executable = os.path.join(
Helpers.get_os_understandable_path(self.settings["path"]),
self.settings["executable"],
)
- # copies to backup dir
- FileHelpers.copy_file(current_executable, backup_executable)
+ try:
+ # copies to backup dir
+ FileHelpers.copy_file(current_executable, backup_executable)
+ except FileNotFoundError:
+ logger.error("Could not create backup of jarfile. File not found.")
- # boolean returns true for false for success
- downloaded = Helpers.download_file(
- self.settings["executable_update_url"], current_executable
- )
+ # wait for backup
+ while self.is_backingup:
+ time.sleep(10)
- while self.stats_helper.get_server_stats()["updating"]:
- if downloaded and not self.is_backingup:
- logger.info("Executable updated successfully. Starting Server")
+ # check if backup was successful
+ if self.last_backup_failed:
+ server_users = PermissionsServers.get_server_user_list(self.server_id)
+ for user in server_users:
+ self.helper.websocket_helper.broadcast_user(
+ user,
+ "notification",
+ "Backup failed for " + self.name + ". canceling update.",
+ )
+ return False
- self.stats_helper.set_update(False)
- if len(self.helper.websocket_helper.clients) > 0:
- # There are clients
- self.check_update()
- server_users = PermissionsServers.get_server_user_list(
- self.server_id
+ # lets download the files
+ if HelperServers.get_server_type_by_id(self.server_id) != "minecraft-bedrock":
+ # boolean returns true for false for success
+ downloaded = Helpers.download_file(
+ self.settings["executable_update_url"], current_executable
+ )
+ else:
+ # downloads zip from remote url
+ try:
+ bedrock_url = Helpers.get_latest_bedrock_url()
+ if bedrock_url.lower().startswith("https"):
+ urllib.request.urlretrieve(
+ bedrock_url,
+ os.path.join(self.settings["path"], "bedrock_server.zip"),
)
- for user in server_users:
- self.helper.websocket_helper.broadcast_user(
- user,
- "notification",
- "Executable update finished for " + self.name,
- )
- time.sleep(3)
- self.helper.websocket_helper.broadcast_page(
- "/panel/server_detail",
- "update_button_status",
- {
- "isUpdating": self.check_update(),
- "server_id": self.server_id,
- "wasRunning": was_started,
- },
- )
- self.helper.websocket_helper.broadcast_page(
- "/panel/dashboard", "send_start_reload", {}
+
+ unzip_path = os.path.join(self.settings["path"], "bedrock_server.zip")
+ unzip_path = self.helper.wtol_path(unzip_path)
+ # unzips archive that was downloaded.
+ FileHelpers.unzip_file(unzip_path)
+ # adjusts permissions for execution if os is not windows
+ if not self.helper.is_os_windows():
+ os.chmod(
+ os.path.join(self.settings["path"], "bedrock_server"), 0o0744
)
+
+ # we'll delete the zip we downloaded now
+ os.remove(os.path.join(self.settings["path"], "bedrock_server.zip"))
+ downloaded = True
+ except Exception as e:
+ logger.critical(
+ f"Failed to download bedrock executable for update \n{e}"
+ )
+
+ if downloaded:
+ logger.info("Executable updated successfully. Starting Server")
+
+ self.stats_helper.set_update(False)
+ if len(self.helper.websocket_helper.clients) > 0:
+ # There are clients
+ self.check_update()
server_users = PermissionsServers.get_server_user_list(self.server_id)
for user in server_users:
self.helper.websocket_helper.broadcast_user(
@@ -1145,29 +1171,52 @@ class ServerInstance:
"notification",
"Executable update finished for " + self.name,
)
-
- self.management_helper.add_to_audit_log_raw(
- "Alert",
- "-1",
- self.server_id,
- "Executable update finished for " + self.name,
- self.settings["server_ip"],
+ # sleep so first notif can completely run
+ time.sleep(3)
+ self.helper.websocket_helper.broadcast_page(
+ "/panel/server_detail",
+ "update_button_status",
+ {
+ "isUpdating": self.check_update(),
+ "server_id": self.server_id,
+ "wasRunning": was_started,
+ },
)
- if was_started:
- self.start_server()
- elif not downloaded and not self.is_backingup:
- time.sleep(5)
- server_users = PermissionsServers.get_server_user_list(self.server_id)
- for user in server_users:
- self.helper.websocket_helper.broadcast_user(
- user,
- "notification",
- "Executable update failed for "
- + self.name
- + ". Check log file for details.",
- )
- logger.error("Executable download failed.")
- self.stats_helper.set_update(False)
+ self.helper.websocket_helper.broadcast_page(
+ "/panel/dashboard", "send_start_reload", {}
+ )
+ self.helper.websocket_helper.broadcast_page(
+ "/panel/server_detail", "remove_spinner", {}
+ )
+ server_users = PermissionsServers.get_server_user_list(self.server_id)
+ for user in server_users:
+ self.helper.websocket_helper.broadcast_user(
+ user,
+ "notification",
+ "Executable update finished for " + self.name,
+ )
+
+ self.management_helper.add_to_audit_log_raw(
+ "Alert",
+ "-1",
+ self.server_id,
+ "Executable update finished for " + self.name,
+ self.settings["server_ip"],
+ )
+ if was_started:
+ self.start_server()
+ else:
+ server_users = PermissionsServers.get_server_user_list(self.server_id)
+ for user in server_users:
+ self.helper.websocket_helper.broadcast_user(
+ user,
+ "notification",
+ "Executable update failed for "
+ + self.name
+ + ". Check log file for details.",
+ )
+ logger.error("Executable download failed.")
+ self.stats_helper.set_update(False)
# **********************************************************************************
# Minecraft Servers Statistics
diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py
index a8bac6e2..c7708f39 100644
--- a/app/classes/web/panel_handler.py
+++ b/app/classes/web/panel_handler.py
@@ -756,8 +756,21 @@ class PanelHandler(BaseHandler):
page_data["backup_path"] = Helpers.wtol_path(server_info["backup_path"])
if subpage == "metrics":
+ try:
+ days = int(self.get_argument("days", "1"))
+ except ValueError as e:
+ self.redirect(
+ f"/panel/error?error=Type error: Argument must be an int {e}"
+ )
+ page_data["options"] = [1, 2, 3]
+ if not days in page_data["options"]:
+ page_data["options"].insert(0, days)
+ else:
+ page_data["options"].insert(
+ 0, page_data["options"].pop(page_data["options"].index(days))
+ )
page_data["history_stats"] = self.controller.servers.get_history_stats(
- server_id
+ server_id, days
)
def get_banned_players_html():
@@ -1500,17 +1513,18 @@ class PanelHandler(BaseHandler):
if Helpers.is_os_windows():
server_path.replace(" ", "^ ")
server_path = Helpers.wtol_path(server_path)
- log_path = self.get_argument("log_path", None)
- if Helpers.is_os_windows():
- log_path.replace(" ", "^ ")
- log_path = Helpers.wtol_path(log_path)
- if not self.helper.validate_traversal(server_obj.path, log_path):
- log_path = ""
+ log_path = self.get_argument("log_path", "")
+ if log_path:
+ if Helpers.is_os_windows():
+ log_path.replace(" ", "^ ")
+ log_path = Helpers.wtol_path(log_path)
+ if not self.helper.validate_traversal(server_obj.path, log_path):
+ log_path = ""
executable = self.get_argument("executable", None)
execution_command = self.get_argument("execution_command", None)
server_ip = self.get_argument("server_ip", None)
server_port = self.get_argument("server_port", None)
- executable_update_url = self.get_argument("executable_update_url", None)
+ executable_update_url = self.get_argument("executable_update_url", "")
show_status = int(float(self.get_argument("show_status", "0")))
else:
execution_command = server_obj.execution_command
diff --git a/app/classes/web/server_handler.py b/app/classes/web/server_handler.py
index c13198ed..1c1d6fc6 100644
--- a/app/classes/web/server_handler.py
+++ b/app/classes/web/server_handler.py
@@ -97,6 +97,7 @@ class ServerHandler(BaseHandler):
"version_data": self.helper.get_version_string(),
"user_data": exec_user,
"user_role": exec_user_role,
+ "online": Helpers.check_internet(),
"roles": list_roles,
"super_user": exec_user["superuser"],
"user_crafty_permissions": exec_user_crafty_permissions,
@@ -173,7 +174,6 @@ class ServerHandler(BaseHandler):
)
return
- page_data["online"] = Helpers.check_internet()
page_data["server_types"] = self.controller.server_jars.get_serverjar_data()
page_data["js_server_types"] = json.dumps(
self.controller.server_jars.get_serverjar_data()
@@ -548,25 +548,14 @@ class ServerHandler(BaseHandler):
self.get_remote_ip(),
)
else:
- if len(server_parts) != 2:
- self.redirect("/panel/error?error=Invalid server data")
- return
- server_type, server_version = server_parts
- # TODO: add server type check here and call the correct server
- # add functions if not a jar
- new_server_id = self.controller.create_jar_server(
- server_type,
- server_version,
+
+ new_server_id = self.controller.create_bedrock_server(
server_name,
- min_mem,
- max_mem,
- port,
exec_user["user_id"],
)
self.controller.management.add_to_audit_log(
exec_user["user_id"],
- f"created a {server_version} {str(server_type).capitalize()} "
- f'server named "{server_name}"',
+ "created a Bedrock " f'server named "{server_name}"',
# Example: Admin created a 1.16.5 Bukkit server named "survival"
new_server_id,
self.get_remote_ip(),
diff --git a/app/frontend/templates/base.html b/app/frontend/templates/base.html
index 1ab50da8..100ab16c 100755
--- a/app/frontend/templates/base.html
+++ b/app/frontend/templates/base.html
@@ -378,7 +378,7 @@
}
bootbox.confirm({
title: "{% raw translate('error', 'eulaTitle', data['lang']) %}",
- message: "{% raw translate('error', 'eulaMsg', data['lang']) %}
EULA
{% raw translate('error', 'eulaAgree', data['lang']) %}",
+ message: "{% raw translate('error', 'eulaMsg', data['lang']) %}Minecraft EULA",
buttons: {
confirm: {
label: 'Yes',
diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html
index 23ebf2c7..c89d00b2 100644
--- a/app/frontend/templates/panel/dashboard.html
+++ b/app/frontend/templates/panel/dashboard.html
@@ -186,7 +186,8 @@
{% elif server['stats']['updating']%}
- {{ translate('serverTerm', 'updating',
+ {{ translate('serverTerm', 'updating',
data['lang']) }}
{% elif server['stats']['waiting_start']%}
diff --git a/app/frontend/templates/panel/server_config.html b/app/frontend/templates/panel/server_config.html
index e1c38e1e..d6117683 100644
--- a/app/frontend/templates/panel/server_config.html
+++ b/app/frontend/templates/panel/server_config.html
@@ -67,7 +67,7 @@
placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" required>
-
+ {% if data['server_stats']['server_type'] != "minecraft-bedrock" %}