From a19ba7dbb617e1d61a32e9b95e8c3c0ba4ccc1d3 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 29 Nov 2021 21:22:46 +0000 Subject: [PATCH] Backup Restore/Root Disable --- .../controllers/server_perms_controller.py | 11 + app/classes/models/management.py | 4 + app/classes/models/server_permissions.py | 10 +- app/classes/shared/helpers.py | 6 + app/classes/shared/main_controller.py | 119 ++++-- app/classes/shared/server.py | 15 +- app/classes/web/ajax_handler.py | 15 + app/classes/web/panel_handler.py | 22 +- .../templates/panel/server_backup.html | 57 ++- .../templates/panel/server_config.html | 51 +-- app/translations/en_EN.json | 10 +- app/translations/fi_FI.json | 26 +- app/translations/fr_FR.json | 10 +- app/translations/zh_CN.json | 346 ++++++++++++++++++ main.py | 11 +- requirements.txt | 3 +- 16 files changed, 638 insertions(+), 78 deletions(-) create mode 100644 app/translations/zh_CN.json diff --git a/app/classes/controllers/server_perms_controller.py b/app/classes/controllers/server_perms_controller.py index 1a15f3c3..314f9999 100644 --- a/app/classes/controllers/server_perms_controller.py +++ b/app/classes/controllers/server_perms_controller.py @@ -51,6 +51,17 @@ class Server_Perms_Controller: def add_role_server(server_id, role_id, rs_permissions="00000000"): return server_permissions.add_role_server(server_id, role_id, rs_permissions) + @staticmethod + def get_server_roles(server_id): + return server_permissions.get_server_roles(server_id) + + @staticmethod + def backup_role_swap(old_server_id, new_server_id): + role_list = server_permissions.get_server_roles(old_server_id) + for role in role_list: + server_permissions.add_role_server(new_server_id, role.role_id, server_permissions.get_permissions_mask(int(role.role_id), int(old_server_id))) + #server_permissions.add_role_server(new_server_id, role.role_id, '00001000') + #************************************************************************************************ # Servers Permissions Methods #************************************************************************************************ diff --git a/app/classes/models/management.py b/app/classes/models/management.py index 1146068b..d3d938d2 100644 --- a/app/classes/models/management.py +++ b/app/classes/models/management.py @@ -227,6 +227,10 @@ class helpers_management: def update_scheduled_task(schedule_id, updates): Schedules.update(updates).where(Schedules.schedule_id == schedule_id).execute() + @staticmethod + def delete_scheduled_task_by_server(server_id): + Schedules.delete().where(Schedules.server_id == int(server_id)).execute() + @staticmethod def get_scheduled_task(schedule_id): return model_to_dict(Schedules.get(Schedules.schedule_id == schedule_id)).execute() diff --git a/app/classes/models/server_permissions.py b/app/classes/models/server_permissions.py index 43f50b96..ccf81a38 100644 --- a/app/classes/models/server_permissions.py +++ b/app/classes/models/server_permissions.py @@ -118,10 +118,18 @@ class Permissions_Servers: @staticmethod def get_permissions_mask(role_id, server_id): permissions_mask = '' - role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).execute() + role_server = Role_Servers.select().where(Role_Servers.role_id == role_id).where(Role_Servers.server_id == server_id).get() permissions_mask = role_server.permissions return permissions_mask + @staticmethod + def get_server_roles(server_id): + role_list = [] + roles = Role_Servers.select().where(Role_Servers.server_id == server_id).execute() + for role in roles: + role_list.append(role.role_id) + return role_list + @staticmethod def get_role_permissions_list(role_id): permissions_mask = '00000000' diff --git a/app/classes/shared/helpers.py b/app/classes/shared/helpers.py index f9a1293e..cf3330b3 100644 --- a/app/classes/shared/helpers.py +++ b/app/classes/shared/helpers.py @@ -338,6 +338,12 @@ class Helpers: logger.critical("Unable to write to {} - Error: {}".format(path, e)) return False + def checkRoot(self): + if os.geteuid() == 0: + return True + else: + return False + def unzipFile(self, zip_path): new_dir_list = zip_path.split('/') new_dir = '' diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index 83165672..be9a526f 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -1,13 +1,17 @@ import os +import pathlib import time import logging import sys +from peewee import DoesNotExist +import schedule import yaml import asyncio import shutil import tempfile import zipfile from distutils import dir_util +from app.classes.models.management import helpers_management from app.classes.shared.helpers import helper from app.classes.shared.console import console @@ -285,34 +289,55 @@ class Controller: helper.ensure_dir_exists(new_server_dir) helper.ensure_dir_exists(backup_path) tempDir = tempfile.mkdtemp() + has_properties = False with zipfile.ZipFile(zip_path, 'r') as zip_ref: + #extracts archive to temp directory zip_ref.extractall(tempDir) - for i in range(len(zip_ref.filelist)): - if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].filename.endswith('/'): - test = zip_ref.filelist[i].filename - break - path_list = test.split('/') - root_path = path_list[0] - if len(path_list) > 1: - for i in range(len(path_list)-2): - root_path = os.path.join(root_path, path_list[i+1]) + if len(zip_ref.filelist) > 1: + for item in os.listdir(tempDir): + if str(item) == 'server.properties': + has_properties = True + try: + shutil.move(os.path.join(tempDir, item), os.path.join(new_server_dir, item)) + except Exception as ex: + logger.error('ERROR IN ZIP IMPORT: {}'.format(ex)) + if not has_properties: + logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port))) + with open(os.path.join(new_server_dir, "server.properties"), "w") as f: + f.write("server-port={}".format(port)) + f.close() + zip_ref.close() + else: - full_root_path = os.path.join(tempDir, root_path) + #iterates list of files + for i in range(len(zip_ref.filelist)): + #checks if the list of files inside of a dir is greater than 1 or if it's not a directory. + if len(zip_ref.filelist) > 1 or not zip_ref.filelist[i].is_dir(): + #sets local variable to be that filename and we break out of the loop since we found our root dir. + test = zip_ref.filelist[i-1].filename + break + path_list = test.split('/') + root_path = path_list[0] + if len(path_list) > 1: + for i in range(len(path_list)-2): + root_path = os.path.join(root_path, path_list[i+1]) - has_properties = False - for item in os.listdir(full_root_path): - if str(item) == 'server.properties': - has_properties = True - try: - shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item)) - except Exception as ex: - logger.error('ERROR IN ZIP IMPORT: {}'.format(ex)) - if not has_properties: - logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port))) - with open(os.path.join(new_server_dir, "server.properties"), "w") as f: - f.write("server-port={}".format(port)) - f.close() - zip_ref.close() + full_root_path = os.path.join(tempDir, root_path) + + + for item in os.listdir(full_root_path): + if str(item) == 'server.properties': + has_properties = True + try: + shutil.move(os.path.join(full_root_path, item), os.path.join(new_server_dir, item)) + except Exception as ex: + logger.error('ERROR IN ZIP IMPORT: {}'.format(ex)) + if not has_properties: + logger.info("No server.properties found on zip file import. Creating one with port selection of {}".format(str(port))) + with open(os.path.join(new_server_dir, "server.properties"), "w") as f: + f.write("server-port={}".format(port)) + f.close() + zip_ref.close() else: return "false" @@ -328,20 +353,30 @@ class Controller: server_log_file, server_stop, port) return new_id + 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) + backup_path = helper.validate_traversal(helper.backup_path, old_bu_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)) + 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): # 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_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') as f: + f.write( + "The server is managed by Crafty Controller.\n Leave this directory/files alone please") + f.close() - try: - # place a file in the dir saying it's owned by crafty - with open(os.path.join(server_dir, "crafty_managed.txt"), 'w') 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("Unable to create required server files due to :{}".format(e)) - return False + except Exception as e: + logger.error("Unable to create required server files due to :{}".format(e)) + return False # let's re-init all servers self.init_all_servers() @@ -356,6 +391,7 @@ class Controller: if int(s['server_id']) == int(server_id): server_data = self.get_server_data(server_id) server_name = server_data['server_name'] + backup_dir = self.servers.get_server_data_by_id(server_id)['backup_path'] logger.info("Deleting Server: ID {} | Name: {} ".format(server_id, server_name)) console.info("Deleting Server: ID {} | Name: {} ".format(server_id, server_name)) @@ -366,7 +402,19 @@ class Controller: if running: self.stop_server(server_id) if files: - shutil.rmtree(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['path'])) + try: + shutil.rmtree(helper.get_os_understandable_path(self.servers.get_server_data_by_id(server_id)['path'])) + except Exception as e: + logger.error("Unable to delete server files for server with ID: {} with error logged: {}".format(server_id, e)) + if helper.check_path_exists(self.servers.get_server_data_by_id(server_id)['backup_path']): + shutil.rmtree(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) @@ -374,3 +422,4 @@ class Controller: self.servers_list.pop(counter) counter += 1 + return diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 5193dd59..623f22d1 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -243,7 +243,16 @@ class Server: try: self.process = subprocess.Popen(self.server_command, cwd=self.server_path, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) except Exception as ex: - msg = "Server {} failed to start with error code: {}".format(self.name, ex) + #Checks for java on initial fail + if os.system("java -version") == 32512: + msg = "Server {} failed to start with error code: {}".format(self.name, "Java not found. Please install Java then try again.") + if user_id: + websocket_helper.broadcast_user(user_id, 'send_start_error',{ + 'error': translation.translate('error', 'noJava', user_lang).format(self.name) + }) + return False + else: + msg = "Server {} failed to start with error code: {}".format(self.name, ex) logger.error(msg) if user_id: websocket_helper.broadcast_user(user_id, 'send_start_error',{ @@ -512,14 +521,14 @@ class Server: return def list_backups(self): - conf = management_helper.get_backup_config(self.server_id) if self.settings['backup_path']: if helper.check_path_exists(helper.get_os_understandable_path(self.settings['backup_path'])): files = helper.get_human_readable_files_sizes(helper.list_dir_by_date(helper.get_os_understandable_path(self.settings['backup_path']))) - return [{"path": os.path.relpath(f['path'], start=helper.get_os_understandable_path(conf['backup_path'])), "size": f["size"]} for f in files] + return [{"path": os.path.relpath(f['path'], start=helper.get_os_understandable_path(self.settings['backup_path'])), "size": f["size"]} for f in files] else: return [] else: + logger.info("Error putting backup file list for server with ID: {}".format(self.server_id)) return[] def jar_update(self): diff --git a/app/classes/web/ajax_handler.py b/app/classes/web/ajax_handler.py index 0e4c5faa..a9247ace 100644 --- a/app/classes/web/ajax_handler.py +++ b/app/classes/web/ajax_handler.py @@ -212,6 +212,21 @@ class AjaxHandler(BaseHandler): svr = self.controller.get_server_obj(server_id) svr.agree_eula(user_data['user_id']) + elif page == "restore_backup": + server_id = bleach.clean(self.get_argument('id', None)) + zip_name = bleach.clean(self.get_argument('zip_file', None)) + svr_obj = self.controller.servers.get_server_obj(server_id) + server_data = self.controller.servers.get_server_data_by_id(server_id) + backup_path = svr_obj.backup_path + if helper.validate_traversal(backup_path, zip_name): + new_server = self.controller.import_zip_server(svr_obj.server_name, os.path.join(backup_path, zip_name), server_data['executable'], '1', '2', server_data['server_port']) + new_server_id = new_server + new_server = self.controller.get_server_data(new_server) + self.controller.rename_backup_dir(server_id, new_server_id, new_server['server_uuid']) + self.controller.remove_server(server_id, True) + self.redirect('/panel/dashboard') + + @tornado.web.authenticated def delete(self, page): if page == "del_file": diff --git a/app/classes/web/panel_handler.py b/app/classes/web/panel_handler.py index 210794e0..f1056efb 100644 --- a/app/classes/web/panel_handler.py +++ b/app/classes/web/panel_handler.py @@ -121,14 +121,23 @@ class PanelHandler(BaseHandler): elif page == 'dashboard': if exec_user['superuser'] == 1: - page_data['servers'] = self.controller.servers.get_all_servers_stats() + try: + page_data['servers'] = self.controller.servers.get_all_servers_stats() + except IndexError: + self.controller.stats.record_stats() + page_data['servers'] = self.controller.servers.get_all_servers_stats() + for data in page_data['servers']: try: data['stats']['waiting_start'] = self.controller.servers.get_waiting_start(int(data['stats']['server_id']['server_id'])) except: data['stats']['waiting_start'] = False else: - user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id) + try: + user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id) + except IndexError: + self.controller.stats.record_stats() + user_auth = self.controller.servers.get_authorized_servers_stats(exec_user_id) logger.debug("ASFR: {}".format(user_auth)) page_data['servers'] = user_auth page_data['server_stats']['running'] = 0 @@ -208,9 +217,12 @@ class PanelHandler(BaseHandler): if subpage == "backup": server_info = self.controller.servers.get_server_data_by_id(server_id) page_data['backup_config'] = self.controller.management.get_backup_config(server_id) - page_data['backup_list'] = server.list_backups() + self.controller.refresh_server_settings(server_id) + try: + page_data['backup_list'] = server.list_backups() + except: + page_data['backup_list'] = [] page_data['backup_path'] = helper.wtol_path(server_info["backup_path"]) - print(page_data['backup_path']) def get_banned_players_html(): banned_players = self.controller.servers.get_banned_players(server_id) @@ -416,8 +428,6 @@ class PanelHandler(BaseHandler): return elif Enum_Permissions_Crafty.User_Config not in exec_user_crafty_permissions: if str(user_id) != str(exec_user_id): - print("USER ID ", user_id) - print("EXEC ID ", exec_user_id) self.redirect("/panel/error?error=Unauthorized access: not a user editor") return diff --git a/app/frontend/templates/panel/server_backup.html b/app/frontend/templates/panel/server_backup.html index 8650aee9..11fb1acf 100644 --- a/app/frontend/templates/panel/server_backup.html +++ b/app/frontend/templates/panel/server_backup.html @@ -45,8 +45,10 @@ {{ translate('serverBackups', 'backupNow', data['lang']) }}
+ {% if data['super_user'] %} + {% end %}
@@ -96,10 +98,14 @@

- + {{ backup['path'] }} {{ backup['size'] }} @@ -162,6 +168,31 @@ }); } + function restore_backup(filename, id){ + var token = getCookie("_xsrf") + + console.log('Sending Command to restore backup: ' + filename) + $.ajax({ + type: "POST", + headers: {'X-XSRFToken': token}, + url: '/ajax/restore_backup?server_id='+id, + data: { + zip_file: filename, + id: id + }, + success: function(data) { + var dialog = bootbox.dialog({ + message: ' {{ translate('serverBackups', 'restoring', data['lang']) }}', + closeButton: false + }); + setTimeout(function(){ + location.href=('/panel/dashboard'); + }, 15000); + }, + }); + } + + $( document ).ready(function() { console.log( "ready!" ); $("#backup_config_box").hide(); @@ -210,9 +241,33 @@ } }); }); + + $( ".restore_button" ).click(function() { + var file_to_restore = $(this).data("file"); + + bootbox.confirm({ + title: "{{ translate('serverBackups', 'restore', data['lang']) }} "+file_to_restore, + message: "{{ translate('serverBackups', 'confirmRestore', data['lang']) }}", + buttons: { + cancel: { + label: ' {{ translate("serverBackups", "cancel", data['lang']) }}' + }, + confirm: { + label: ' {{ translate("serverBackups", "restore", data['lang']) }}' + } + }, + callback: function (result) { + console.log(result); + if (result == true) { + restore_backup(file_to_restore, {{ data['server_stats']['server_id']['server_id'] }} ); + } + } + }); }); + }); + diff --git a/app/frontend/templates/panel/server_config.html b/app/frontend/templates/panel/server_config.html index 477fbb41..468aeccc 100644 --- a/app/frontend/templates/panel/server_config.html +++ b/app/frontend/templates/panel/server_config.html @@ -236,23 +236,32 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}'; function deleteServer (){ path = "{{data['server_stats']['server_id']['path']}}"; name = "{{data['server_stats']['server_id']['server_name']}}"; - bootbox.confirm({ + bootbox.dialog({ size: "", title: "{% raw translate('serverConfig', 'deleteFilesQuestion', data['lang']) %}", closeButton: false, message: "{% raw translate('serverConfig', 'deleteFilesQuestionMessage', data['lang']) %}", buttons: { - confirm: { + files: { label: "{{ translate('serverConfig', 'yesDeleteFiles', data['lang']) }}", className: 'btn-danger', - }, - cancel: { - label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}", - className: 'btn-link', + callback: function(){ + deleteServerFilesE(); + setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000); + bootbox.dialog({ + backdrop: true, + title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}', + message: '
  {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %}
', + closeButton: false + }) + + return; } - }, - callback: function(result) { - if (!result){ + }, + noFiles: { + label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}", + className: 'btn-outline-danger', + callback: function(){ deleteServerE() setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000); bootbox.dialog({ @@ -261,18 +270,18 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}'; message: '
  {% raw translate("serverConfig", "bePatientDelete", data['lang']) %}
', closeButton: false }) - - return;} - else{ - deleteServerFilesE(); - setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000); - bootbox.dialog({ - backdrop: true, - title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}', - message: '
  {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %}
', - closeButton: false - }) - } + return; + } + }, + cancel: { + label: "{{ translate('serverConfig', 'cancel', data['lang']) }}", + className: 'btn-secondary', + callback: function(){ + return; + } + } + }, + callback: function(result) { } }); diff --git a/app/translations/en_EN.json b/app/translations/en_EN.json index 62250952..cb4d141b 100644 --- a/app/translations/en_EN.json +++ b/app/translations/en_EN.json @@ -16,7 +16,8 @@ "internet": "We have detected the machine running Crafty has no connection to the internet. Client connections to the server may be limited.", "eulaTitle": "Agree To EULA", "eulaMsg": "You must agree to the EULA. A copy of the Mojang EULA is linked under this message.", - "eulaAgree": "Do you agree?" + "eulaAgree": "Do you agree?", + "noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server." }, "404": { "contact": "Contact Crafty Control Support via Discord", @@ -178,7 +179,10 @@ "destroyBackup": "Destroy backup \" + file_to_del + \"?", "confirmDelete": "Do you want to delete this backup? This cannot be undone.", "confirm": "Confirm", - "options": "Options" + "options": "Options", + "restoring": "Restoring Backup. This may take a while. Please be patient.", + "restore": "Restore", + "confirmRestore": "Are you sure you want to restore from this backup. All current server files will changed to backup state and will be unrecoverable." }, "serverFiles": { "noscript": "The file manager does not work without JavaScript", @@ -247,7 +251,7 @@ "yesDelete": "Yes, delete", "noDelete": "No, go back", "deleteFilesQuestion": "Delete server files from machine?", - "deleteFilesQuestionMessage": "Would you like Crafty to delete all server files from the host machine?", + "deleteFilesQuestionMessage": "Would you like Crafty to delete all server files from the host machine?

This includes server backups.", "yesDeleteFiles": "Yes, delete files", "noDeleteFiles": "No, just remove from panel", "sendingDelete": "Deleting Server", diff --git a/app/translations/fi_FI.json b/app/translations/fi_FI.json index 00720e36..edf15312 100644 --- a/app/translations/fi_FI.json +++ b/app/translations/fi_FI.json @@ -16,7 +16,8 @@ "internet": "Olemme havainneet, että Crafty -koneella ei ole Internet -yhteyttä. Asiakasyhteydet palvelimelle voivat olla rajalliset.", "eulaTitle": "Hyväksy EULA", "eulaMsg": "Sinun on hyväksyttävä EULA. Kopio Mojang EULA:sta on linkitetty tämän viestin alla.", - "eulaAgree": "Oletko samaa mieltä?" + "eulaAgree": "Oletko samaa mieltä?", + "noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server." }, "404": { "contact": "Ota yhteyttä Crafty Control -tukeen Discordin kautta", @@ -179,7 +180,10 @@ "destroyBackup": "Tuhotaanko varmuuskopio \" + file_to_del + \"?", "confirmDelete": "Haluatko poistaa tämän varmuuskopion? Tätä ei voi peruuttaa.", "confirm": "Vahvista", - "options": "Vaihtoehtoja" + "options": "Vaihtoehtoja", + "restoring": "Varmuuskopion palauttaminen. Tämä voi kestää hetken. Olkaa kärsivällisiä.", + "restore": "Palauttaa", + "confirmRestore": "Haluatko varmasti palauttaa tämän varmuuskopion. Kaikki nykyiset palvelintiedostot muutetaan varmuuskopiotilaan, eikä niitä voida palauttaa." }, "serverFiles": { "noscript": "Tiedostojenhallinta ei toimi ilman JavaScriptiä", @@ -237,7 +241,23 @@ "save": "Tallenna", "cancel": "Peruuta", "deleteServer": "Poista palvelin", - "stopBeforeDeleting": "Pysäytä palvelin ennen sen poistamista" + "stopBeforeDeleting": "Pysäytä palvelin ennen sen poistamista", + "exeUpdateURLDesc": "Direct Download URL for updates.", + "exeUpdateURL": "Palvelimen suoritettavan päivityksen URL-osoite", + "update": "Päivitä suoritettava", + "bePatientUpdate": "Ole kärsivällinen, kun päivitämme palvelinta. Latausajat voivat vaihdella Internet-nopeutesi mukaan.
Tämä näyttö päivittyy hetkessä", + "sendingRequest": "Pyyntöäsi lähetetään...", + "deleteServerQuestion": "Poistetaanko palvelin?", + "deleteServerQuestionMessage": "Haluatko varmasti poistaa tämän palvelimen? Tämän jälkeen ei ole paluuta...", + "yesDelete": "Kyllä, poista", + "noDelete": "Ei, mene takaisin", + "deleteFilesQuestion": "Poistetaanko palvelintiedostot koneelta?", + "deleteFilesQuestionMessage": "Haluatko Craftyn poistavan kaikki palvelintiedostot isäntäkoneelta?

Tämä sisältää palvelimen varmuuskopiot. ", + "yesDeleteFiles": "Kyllä, poista tiedostoja", + "noDeleteFiles": "Ei, poista vain paneelista", + "sendingDelete": "Poistetaan palvelinta", + "bePatientDelete": "Ole kärsivällinen, kun poistamme palvelimesi Crafty-paneelista. Tämä näyttö sulkeutuu hetken kuluttua.", + "bePatientDeleteFiles" : "Ole kärsivällinen, kun poistamme palvelimesi Crafty-paneelista ja poistamme kaikki tiedostot. Tämä näyttö sulkeutuu hetken kuluttua." }, "serverConfigHelp": { "title": "Palvelimen asetukset", diff --git a/app/translations/fr_FR.json b/app/translations/fr_FR.json index e708a9e4..2bc07866 100644 --- a/app/translations/fr_FR.json +++ b/app/translations/fr_FR.json @@ -16,7 +16,8 @@ "internet": "Nous avons détecté que la machine exécutant Crafty n'a pas de connexion à Internet. Les connexions client au serveur peuvent être limitées.", "eulaTitle": "Accepter le EULA", "eulaMsg": "Vous devez accepter le EULA. Une copie du CLUF de Mojang est liée sous ce message.", - "eulaAgree": "Êtes-vous d'accord?" + "eulaAgree": "Êtes-vous d'accord?", + "noJava": "Server {} failed to start with error code: We have detected Java is not installed. Please install java then start the server." }, "404": { "contact": "Contacter le Support de Crafty Control via Discord", @@ -178,7 +179,10 @@ "destroyBackup": "Supprimer la sauvegarde \" + file_to_del + \" ?", "confirmDelete": "Es-tu sûr de vouloir supprimer cette sauvegarde ? Tu ne pourras pas revenir en arrière.", "confirm": "Confirmer", - "options": "Options" + "options": "Options", + "restoring": "Restauration de la sauvegarde. Cela peut prendre un peu de temps. S'il vous plaît soyez patient.", + "restore": "Restaurer", + "restoreConfirm": "Êtes-vous sûr de vouloir restaurer à partir de cette sauvegarde. Tous les fichiers du serveur actuel passeront à l'état de sauvegarde et seront irrécupérables." }, "serverFiles": { "noscript": "Le gestionnaire de fichiers ne fonctionne pas sans JavaScript", @@ -247,7 +251,7 @@ "yesDelete": "Oui, Supprimer", "noDelete": "Non, revenir en arrière", "deleteFilesQuestion": "Supprimer les fichiers de la machine ?", - "deleteFilesQuestionMessage": "Veux-tu que Crafty supprime tous les fichiers du serveur de la machine hôte ?", + "deleteFilesQuestionMessage": "Veux-tu que Crafty supprime tous les fichiers du serveur de la machine hôte?

Cela inclut les sauvegardes du serveur.", "yesDeleteFiles": "Oui, Supprimer les fichier", "noDeleteFiles": "Non, Supprimer uniquement du tabelau de bord", "sendingDelete": "Suppression du Serveur", diff --git a/app/translations/zh_CN.json b/app/translations/zh_CN.json new file mode 100644 index 00000000..fddc76fe --- /dev/null +++ b/app/translations/zh_CN.json @@ -0,0 +1,346 @@ +{ + "login": { + "forgotPassword": "忘记密码", + "login": "登录", + "password": "密码", + "username": "用户名" + }, + "error": { + "hereIsTheError": "错误如下", + "contact": "通过 Discord 联系 Crafty Control 支持", + "terribleFailure": "多糟糕的错误!", + "embarassing": "哦,天哪,这太尴尬了。", + "error": "错误!", + "start-error": "服务器 {} 启动失败,错误代码为:{}", + "closedPort": "我们检测到端口 {} 在主机上可能没有打开,或者被防火墙阻断了。远程客户端到服务器的连接可能受限。", + "internet": "我们检测到运行 Crafty 的设备没有网络连接。客户端到服务器的连接可能受限。", + "eulaTitle": "同意最终用户许可协议(EULA)", + "eulaMsg": "你必须同意最终用户许可协议(EULA)。一份 Mojang EULA 副本的链接在此消息下方。", + "eulaAgree": "你同意吗?" + }, + "404": { + "contact": "通过 Discord 联系 Crafty Control 支持", + "unableToFind": "我们无法找到您想要查看的页面。请再试一次,或者返回上一页并刷新。", + "notFound": "页面未找到" + }, + "footer": { + "version": "版本", + "copyright": "版权", + "allRightsReserved": "保留所有权利" + }, + "sidebar": { + "dashboard": "仪表板", + "servers": "服务器", + "documentation": "文档", + "credits": "鸣谢", + "contribute": "贡献", + "newServer": "创建新服务器", + "navigation": "导航栏" + }, + "serverWizard": { + "newServer": "创建新服务器", + "importServer": "导入现有服务器", + "importZip": "从 Zip 文件导入", + "serverName": "服务器名称", + "serverPath": "服务器路径", + "serverType": "服务器类型", + "selectType": "选择一种类型", + "serverVersion": "服务器版本", + "selectVersion": "选择一个版本", + "absoluteServerPath": "您的服务器的绝对路径", + "serverJar": "服务器 Jar 核心", + "minMem": "最小内存", + "maxMem": "最大内存", + "serverPort": "服务器端口", + "defaultPort": "默认值为 25565", + "sizeInGB": "大小(以 GB 为单位)", + "zipPath": "服务器路径", + "absoluteZipPath": "您的服务器的绝对路径", + "resetForm": "重置表单", + "importServerButton": "导入服务器!", + "buildServer": "建立服务器!", + "quickSettings": "快捷设置", + "quickSettingsDescription": "别担心,你可以稍后再更改这些设置", + "myNewServer": "我的新服务器", + "bePatient": "请耐心等待,我们正在 ' + (importing ? '导入' : '下载') + ' 服务器", + "importing": "导入服务器中……", + "downloading": "下载服务器中……", + "addRole": "将服务器添加到现有角色", + "autoCreate": "如果没有选择任何角色,Crafty 将会为您创建一个!", + "selectRole": "选择角色" + }, + "dashboard": { + "dashboard": "仪表板", + "memUsage": "内存使用量", + "cpuUsage": "CPU 使用量", + "host": "主机", + "players": "玩家", + "backups": "备份", + "newServer": "创建新服务器", + "allServers": "所有服务器", + "server": "服务器", + "actions": "操作", + "world": "世界", + "motd": "今日消息(MOTD)", + "version": "版本", + "status": "状态", + "online": "在线", + "offline": "离线", + "lastBackup": "上次:", + "nextBackup": "下次:", + "servers": "服务器", + "cannotSeeOnMobile": "在移动设备上什么都看不到?", + "cannotSee": "什么都看不到?", + "cannotSeeOnMobile2": "尝试横向滚动表格。", + "max": "最大", + "avg": "平均", + "bePatientStart": "请耐心等待,我们正在启动服务器。
稍后此页面会刷新", + "bePatientStop": "请耐心等待,我们正在停止服务器。
稍后此页面会刷新", + "bePatientRestart": "请耐心等待,我们正在重启服务器。
稍后此页面会刷新", + "bePatientClone": "请耐心等待,我们正在克隆服务器。
稍后此页面会刷新", + "sendingCommand": "正在发送您的指令", + "cpuCurFreq": "当前 CPU 时钟", + "cpuMaxFreq": "最大 CPU 时钟", + "cpuCores": "CPU 核心", + "start": "启动", + "stop": "停止", + "clone": "克隆", + "kill": "杀死进程", + "restart": "重启", + "killing": "正在杀死进程……", + "starting": "延迟启动", + "delay-explained": "服务进程已经在刚才启动,并且正在延迟 Minecraft 服务器实例的启动", + "no-servers": "当前没有服务器。要开始,请点击", + "welcome": "欢迎来到 Crafty Controller" + }, + "accessDenied": { + "accessDenied": "拒绝访问", + "noAccess": "您没有权限访问此资源", + "contactAdmin": "联系您的服务器管理员来获得访问此资源的权限,或者如果您认为您应该有权限访问此资源,请联系支持。", + "contact": "通过 Discord 联系 Crafty Control 支持" + }, + "serverStats": { + "online": "运行中", + "offline": "已停止", + "serverStatus": "服务器状态", + "serverStarted": "服务器已启动", + "serverUptime": "服务器正常运行时间", + "players": "玩家", + "memUsage": "内存使用量", + "cpuUsage": "CPU 使用量", + "version": "版本", + "description": "简介", + "errorCalculatingUptime": "计算正常运行时间时发生错误", + "serverTime": "UTC 时间", + "unableToConnect": "无法连接" + }, + "serverDetails": { + "serverDetails": "服务器详情", + "terminal": "终端", + "logs": "日志", + "schedule": "计划", + "backup": "备份", + "files": "文件", + "config": "配置", + "playerControls": "玩家管理" + }, + "serverTerm": { + "stopScroll": "停止自动滚动", + "commandInput": "输入您的指令", + "sendCommand": "发送指令", + "start": "启动", + "restart": "重启", + "stop": "停止", + "updating": "更新中……", + "starting": "延迟启动", + "delay-explained": "服务进程已经在刚才启动,并且正在延迟 Minecraft 服务器实例的启动" + }, + "serverPlayerManagement": { + "players": "玩家", + "bannedPlayers": "已封禁的玩家", + "loadingBannedPlayers": "正在加载已封禁的玩家" + }, + "serverBackups": { + "backupNow": "现在备份!", + "backupAtMidnight": "午夜自动备份?", + "storageLocation": "存储位置", + "storageLocationDesc": "您想要在哪里存储备份?", + "maxBackups": "最大备份数量", + "maxBackupsDesc": "Crafty 不会存储多于 N 个备份,并且会删除最旧的备份(输入 0 以保留所有备份)", + "save": "保存", + "cancel": "取消", + "currentBackups": "现有备份", + "download": "下载", + "path": "路径", + "size": "大小", + "delete": "删除", + "backupTask": "一个备份任务已开始。", + "destroyBackup": "删除备份 \" + file_to_del + \"?", + "confirmDelete": "您想要删除这个备份吗?此操作不能撤销。", + "confirm": "确认", + "options": "选项" + }, + "serverFiles": { + "noscript": "文件管理器无法在没有 JavaScript 的情况下使用", + "error": "获取文件时发生错误", + "files": "文件", + "default": "默认", + "save": "保存", + "editingFile": "正在编辑文件", + "delete": "删除", + "createFile": "创建文件", + "createDir": "创建目录", + "rename": "重命名", + "createFileQuestion": "您希望新文件叫什么名字?", + "createDirQuestion": "您希望新目录叫什么名字?", + "renameItemQuestion": "新名称应当是什么?", + "deleteItemQuestion": "您确定要删除 \" + name + \" 吗?", + "deleteItemQuestionMessage": "您正在删除 \\\"\" + path + \"\\\"!

此操作不可逆转,文件将永远遗失!", + "yesDelete": "是,我知道结果", + "noDelete": "否", + "unsupportedLanguage": "警告:这不是一个受支持的文件类型", + "keybindings": "按键绑定", + "fileReadError": "文件读取错误", + "upload": "上传", + "unzip": "解压", + "clickUpload": "点击这里来选择您的文件", + "uploadTitle": "上传文件到:", + "waitUpload": "请等待,我们正在上传您的文件……这需要一点时间。", + "stayHere": "请不要离开此页面!", + "close": "关闭", + "download": "下载" + }, + "serverConfig": { + "serverName": "服务器名称", + "serverNameDesc": "您希望把这个服务器叫做什么", + "serverPath": "服务器运行目录", + "serverPathDesc": "完整绝对路径(不包含可执行文件)", + "serverLogLocation": "服务器日志路径", + "serverLogLocationDesc": "日志文件的完整绝对路径", + "serverExecutable": "服务器可执行文件", + "serverExecutableDesc": "服务器的可执行文件", + "serverExecutionCommand": "服务器运行命令", + "serverExecutionCommandDesc": "在隐藏的终端内要如何启动服务器", + "serverStopCommand": "服务器停止指令", + "serverStopCommandDesc": "要发送给程序以关闭它的指令", + "serverAutostartDelay": "服务器自动启动延迟", + "serverAutostartDelayDesc": "自动启动前的延迟(如果已在下方启用)", + "serverIP": "服务器 IP", + "serverIPDesc": "Crafty 要连接以获取状态的 IP(如果遇到问题,尝试使用真实 IP 而非 127.0.0.1)", + "serverPort": "服务器端口", + "serverPortDesc": "Crafty 要连接以获取状态的端口", + "removeOldLogsAfter": "此时间后删除旧日志", + "removeOldLogsAfterDesc": "日志文件要在多少天后视为旧文件被删除(0 为关闭)", + "serverAutoStart": "服务器自动启动", + "serverCrashDetection": "服务器崩溃检测", + "save": "保存", + "cancel": "取消", + "deleteServer": "删除服务器", + "stopBeforeDeleting": "请在删除之前停止服务器", + "exeUpdateURLDesc": "用于下载更新的直接链接。", + "exeUpdateURL": "服务器可执行文件更新地址", + "update": "更新可执行文件", + "bePatientUpdate": "请耐心等待,我们正在更新服务器。下载时长可能因您的网络速度而异。
稍后此页面会刷新", + "sendingRequest": "正在发送您的请求……", + "deleteServerQuestion": "删除服务器?", + "deleteServerQuestionMessage": "您确定要删除此服务器吗?在此之后将无法撤销……", + "yesDelete": "是,删除", + "noDelete": "否,返回", + "deleteFilesQuestion": "从设备上删除服务器文件?", + "deleteFilesQuestionMessage": "您想要 Crafty 从主机上删除所有的服务器文件吗?", + "yesDeleteFiles": "是,删除文件", + "noDeleteFiles": "否,只从面板中移除", + "sendingDelete": "正在删除服务器", + "bePatientDelete": "请耐心等待,我们正在从 Crafty 面板中移除服务器。稍后此页面会关闭。", + "bePatientDeleteFiles" : "请耐心等待,我们正在从 Crafty 面板中移除服务器并删除所有文件。稍后此页面会关闭。" + }, + "serverConfigHelp": { + "title": "服务器配置区", + "desc": "您可以在这里更改您的服务器配置", + "perms": [ + "我们不推荐更改由 Crafty 管理的服务器的路径。", + "更改路径可能会破坏一些东西,尤其是在 Linux 这类文件权限锁定得更加严格的操作系统上。", + "

", + "如果您认为您必须更改服务器存放的位置,你可能需要给予 \"crafty\" 用户对服务器路径的读取/写入权限。", + "
", + "
", + "在 Linux 上,最好通过执行如下命令来完成:
", + "", + " sudo chown crafty:crafty /您的/服务器/路径 -R
", + " sudo chmod 2775 /您的/服务器/路径 -R
", + "
" + ] + }, + "panelConfig": { + "save": "保存", + "cancel": "取消", + "delete": "删除" + }, + "datatables": { + "i18n": { + "decimal": "", + "emptyTable": "数据表中没有可用的数据", + "info": "正在显示从 _START_ 到 _END_ 的共 _TOTAL_ 个项目", + "infoEmpty": "正在显示从 0 到 0 的共 0 个项目", + "infoFiltered": "(从 _MAX_ 个项目中筛选出)", + "infoPostFix": "", + "thousands": ",", + "lengthMenu": "显示 _MENU_ 个项目", + "loadingRecords": "正在加载……", + "processing": "正在处理……", + "search": "搜索:", + "zeroRecords": "没有找到匹配的记录", + "paginate": { + "first": "首页", + "last": "末页", + "next": "下一页", + "previous": "上一页" + }, + "aria": { + "sortAscending": ":激活对队列的升序排列", + "sortDescending": ":激活对队列的降序排列" + }, + "buttons": { + "collection": "合集 ", + "colvis": "列可见性", + "colvisRestore": "恢复可见性", + "copy": "复制", + "copyKeys": "按 ctrl 或 u2318 + C 以复制表中的数据到您的系统剪贴板。

点击这条消息或者按 escape(ESC)来取消。", + "copySuccess": { + "1": "复制了 1 行到剪贴板", + "_": "复制了 %d 行到剪贴板" + }, + "copyTitle": "复制到剪贴板", + "csv": "CSV", + "excel": "Excel", + "pageLength": { + "-1": "显示所有行", + "1": "显示 1 行", + "_": "显示 %d 行" + }, + "pdf": "PDF", + "print": "打印" + }, + "select": { + "rows": { + "0": "点击某一行以选择", + "1": "%d 行已选中", + "_": "%d 行已选中" + }, + "cells": { + "0": "点击某个单元格以选择", + "1": "%d 个单元格已选中", + "_": "%d 个单元格已选中" + }, + "columns": { + "0": "点击某一列以选择", + "1": "%d 列已选中", + "_": "%d 列已选中" + } + } + } + }, + "base": { + "doesNotWorkWithoutJavascript": "警告:Crafty 无法在没有 JavaScript 的情况下使用!" + } +} \ No newline at end of file diff --git a/main.py b/main.py index 561ea12a..7d812ab4 100644 --- a/main.py +++ b/main.py @@ -1,3 +1,4 @@ +from cmd import Cmd import os import sys import json @@ -78,13 +79,21 @@ if __name__ == '__main__': args = parser.parse_args() + if helper.check_file_exists('/.dockerenv'): + console.cyan("Docker environment detected!") + else: + if helper.checkRoot(): + console.critical("Root detected. Root/Admin access denied. Run Crafty again with non-elevated permissions.") + time.sleep(5) + console.critical("Crafty shutting down. Root/Admin access denied.") + sys.exit(0) helper.ensure_logging_setup() setup_logging(debug=args.verbose) # setting up the logger object logger = logging.getLogger(__name__) - print("Logging set to: {} ".format(logger.level)) + console.cyan("Logging set to: {} ".format(logger.level)) # print our pretty start message do_intro() diff --git a/requirements.txt b/requirements.txt index 3fd218b9..2f479f49 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,7 @@ +cryptography~=3.4 argon2-cffi~=20.1 bleach~=3.1 colorama~=0.4 -cryptography~=3.4 peewee~=3.13 pexpect~=4.8 psutil~=5.7 @@ -11,3 +11,4 @@ requests~=2.26 schedule~=1.1.0 termcolor~=1.1 tornado~=6.0 +cached_property==1.5.2 \ No newline at end of file