Merge branch 'enhancement/bedrock-update-support' into 'dev'

Fix executable backups for Bedrock

See merge request crafty-controller/crafty-4!441
This commit is contained in:
Iain Powrie 2022-09-04 20:09:04 +00:00
commit cf5e4f61bf
8 changed files with 199 additions and 76 deletions

View File

@ -5,6 +5,7 @@
### 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 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

View File

@ -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),
)

View File

@ -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,

View File

@ -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
@ -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

View File

@ -1500,17 +1500,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

View File

@ -186,7 +186,8 @@
{% elif server['stats']['updating']%}
<!-- WHAT HAPPENED HERE -->
<a data-id="{{server['server_data']['server_id']}}" class="">{{ translate('serverTerm', 'updating',
<a data-id="{{server['server_data']['server_id']}}" class=""><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'updating',
data['lang']) }}</i></a>
{% elif server['stats']['waiting_start']%}
<!-- WHAT HAPPENED HERE -->

View File

@ -67,7 +67,7 @@
placeholder="{{ translate('serverConfig', 'serverPath', data['lang']) }}" required>
</div>
{% if data['server_stats']['server_type'] != "minecraft-bedrock" %}
<div class="form-group">
<label for="log_path">{{ translate('serverConfig', 'serverLogLocation', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverLogLocationDesc', data['lang'])
@ -76,6 +76,7 @@
value="{{ data['server_stats']['server_id']['log_path'] }}"
placeholder="{{ translate('serverConfig', 'serverLogLocation', data['lang']) }}" required>
</div>
{% end %}
<div class="form-group">
<label for="executable">{{ translate('serverConfig', 'serverExecutable', data['lang']) }} <small
@ -138,6 +139,7 @@
</div>
{% if data['super_user'] %}
{% if data['server_stats']['server_type'] != "minecraft-bedrock" %}
<div class="form-group">
<label for="executable_update_url">{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'exeUpdateURLDesc', data['lang'])
@ -146,6 +148,7 @@
value="{{ data['server_stats']['server_id']['executable_update_url'] }}"
placeholder="{{ translate('serverConfig', 'exeUpdateURL', data['lang']) }}">
</div>
{% end %}
<div class="form-group">
<label for="server_ip">{{ translate('serverConfig', 'serverIP', data['lang']) }} <small
@ -250,14 +253,31 @@
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}</button>
{% if data['server_stats']['updating'] %}
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}&nbsp;<i id="update-spinner" class="fa fa-spinner fa-spin"></i></button>
{% else %}
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverConfig',
'update', data['lang']) }}&nbsp;<i style="visibility: hidden;" id="update-spinner"
class="fa fa-spinner fa-spin"></i></button>
{% end %}
<a class="btn btn-sm btn-danger disabled">{{ translate('serverConfig', 'deleteServer', data['lang'])
}}</a><br />
<small>{{ translate('serverConfig', 'stopBeforeDeleting', data['lang']) }}</small>
{% else %}
{% if not data['failed'] %}
{% if data['server_stats']['updating'] %}
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}</button>
'update', data['lang']) }}&nbsp;<i id="update-spinner" class="fa fa-spinner fa-spin"></i></button>
{% else %}
<button onclick="send_command(serverId, 'update_executable');" id="update_executable"
style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1">{{ translate('serverConfig',
'update', data['lang']) }}&nbsp;<i style="visibility: hidden;" id="update-spinner"
class="fa fa-spinner fa-spin"></i></button>
{% end %}
{% end %}
{% if not data['failed'] %}
<button onclick="deleteConfirm()" class="btn btn-sm btn-danger">{{ translate('serverConfig',
@ -353,6 +373,9 @@
function send_command(serverId, command) {
//<!-- this getCookie function is in base.html-->
var token = getCookie("_xsrf");
if (command == "update_executable") {
document.getElementById("update-spinner").style.visibility = "visible";
}
$.ajax({
type: "POST",
@ -505,6 +528,12 @@
});
}
$(document).ready(function () {
webSocket.on('remove_spinner', function () {
document.getElementById("update-spinner").style.visibility = "hidden";
});
});
</script>
{% end %}

View File

@ -55,7 +55,8 @@
{% if data['permissions']['Commands'] in data['user_permissions'] %}
{% if data['server_stats']['updating']%}
<div id="update_control_buttons" class="mt-4 flex-wrap d-flex justify-content-between justify-content-md-center align-items-center px-5 px-md-0" style="visibility: visible">
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'updating', data['lang']) }}</button>
<button onclick="" id="start-btn" style="max-width: 7rem;" class="btn btn-warning m-1 flex-grow-1 disabled"><i
class="fa fa-spinner fa-spin"></i>&nbsp;{{ translate('serverTerm', 'updating', data['lang']) }}</button>
<button onclick="" id="restart-btn" style="max-width: 7rem;" class="btn btn-outline-primary m-1 flex-grow-1 disabled">{% raw translate('serverTerm', 'restart', data['lang']) %}</button>
<button onclick="" id="stop-btn" style="max-width: 7rem;" class="btn btn-danger m-1 flex-grow-1 disabled">{{ translate('serverTerm', 'stop', data['lang']) }}</button>
</div>