Backup Restore/Root Disable

This commit is contained in:
Andrew 2021-11-29 21:22:46 +00:00
parent 798dac02a5
commit a19ba7dbb6
16 changed files with 638 additions and 78 deletions

View File

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

View File

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

View File

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

View File

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

View File

@ -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,11 +289,32 @@ 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)
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:
#iterates list of files
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
#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]
@ -299,7 +324,7 @@ class Controller:
full_root_path = os.path.join(tempDir, root_path)
has_properties = False
for item in os.listdir(full_root_path):
if str(item) == 'server.properties':
has_properties = True
@ -328,10 +353,20 @@ 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:
@ -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:
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

View File

@ -243,6 +243,15 @@ 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:
#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:
@ -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):

View File

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

View File

@ -121,13 +121,22 @@ class PanelHandler(BaseHandler):
elif page == 'dashboard':
if exec_user['superuser'] == 1:
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:
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
@ -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)
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

View File

@ -45,8 +45,10 @@
<a href="/panel/backup_now?id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary" onclick="backup_started()">{{ translate('serverBackups', 'backupNow', data['lang']) }}</a>
</div>
<div class="form-group">
{% if data['super_user'] %}
<label for="server_name">{{ translate('serverBackups', 'storageLocation', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'storageLocationDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="backup_path" id="backup_path" value="{{ data['server_stats']['server_id']['backup_path'] }}" placeholder="{{ translate('serverBackups', 'storageLocation', data['lang']) }}" >
{% end %}
</div>
<div class="form-group">
@ -100,6 +102,10 @@
<i class="fas fa-trash" aria-hidden="true"></i>
{{ translate('serverBackups', 'delete', data['lang']) }}
</button>
<button data-file="{{ backup['path'] }}" class="btn btn-warning restore_button">
<i class="fas fa-undo-alt" aria-hidden="true"></i>
{{ translate('serverBackups', 'restore', data['lang']) }}
</button>
</td>
<td>{{ backup['path'] }}</td>
<td>{{ backup['size'] }}</td>
@ -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: '<i class="fa fa-spin fa-spinner"></i> {{ translate('serverBackups', 'restoring', data['lang']) }}',
closeButton: false
});
setTimeout(function(){
location.href=('/panel/dashboard');
}, 15000);
},
});
}
$( document ).ready(function() {
console.log( "ready!" );
$("#backup_config_box").hide();
@ -211,6 +242,30 @@
});
});
$( ".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: '<i class="fas fa-times"></i> {{ translate("serverBackups", "cancel", data['lang']) }}'
},
confirm: {
label: '<i class="fas fa-check"></i> {{ 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'] }} );
}
}
});
});
});

View File

@ -236,34 +236,16 @@ 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(result) {
if (!result){
deleteServerE()
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
closeButton: false
})
return;}
else{
callback: function(){
deleteServerFilesE();
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
@ -272,7 +254,34 @@ let server_id = '{{ data['server_stats']['server_id']['server_id'] }}';
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDeleteFiles", data['lang']) %} </div>',
closeButton: false
})
return;
}
},
noFiles: {
label: "{{ translate('serverConfig', 'noDeleteFiles', data['lang']) }}",
className: 'btn-outline-danger',
callback: function(){
deleteServerE()
setTimeout(function(){ window.location = '/panel/dashboard'; }, 5000);
bootbox.dialog({
backdrop: true,
title: '{% raw translate("serverConfig", "sendingDelete", data['lang']) %}',
message: '<div align="center"><i class="fas fa-spin fa-spinner"></i> &nbsp; {% raw translate("serverConfig", "bePatientDelete", data['lang']) %} </div>',
closeButton: false
})
return;
}
},
cancel: {
label: "{{ translate('serverConfig', 'cancel', data['lang']) }}",
className: 'btn-secondary',
callback: function(){
return;
}
}
},
callback: function(result) {
}
});

View File

@ -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? <br><br><strong>This includes server backups.</strong>",
"yesDeleteFiles": "Yes, delete files",
"noDeleteFiles": "No, just remove from panel",
"sendingDelete": "Deleting Server",

View File

@ -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.<br /> 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? <br><br><strong> Tämä sisältää palvelimen varmuuskopiot. <strong>",
"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",

View File

@ -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? <br><br><strong>Cela inclut les sauvegardes du serveur.</strong>",
"yesDeleteFiles": "Oui, Supprimer les fichier",
"noDeleteFiles": "Non, Supprimer uniquement du tabelau de bord",
"sendingDelete": "Suppression du Serveur",

346
app/translations/zh_CN.json Normal file
View File

@ -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": "请耐心等待,我们正在启动服务器。<br /> 稍后此页面会刷新",
"bePatientStop": "请耐心等待,我们正在停止服务器。<br /> 稍后此页面会刷新",
"bePatientRestart": "请耐心等待,我们正在重启服务器。<br /> 稍后此页面会刷新",
"bePatientClone": "请耐心等待,我们正在克隆服务器。<br /> 稍后此页面会刷新",
"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 + \"\\\"<br/><br/>此操作不可逆转,文件将永远遗失!",
"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": "请耐心等待,我们正在更新服务器。下载时长可能因您的网络速度而异。<br /> 稍后此页面会刷新",
"sendingRequest": "正在发送您的请求……",
"deleteServerQuestion": "删除服务器?",
"deleteServerQuestionMessage": "您确定要删除此服务器吗?在此之后将无法撤销……",
"yesDelete": "是,删除",
"noDelete": "否,返回",
"deleteFilesQuestion": "从设备上删除服务器文件?",
"deleteFilesQuestionMessage": "您想要 Crafty 从主机上删除所有的服务器文件吗?",
"yesDeleteFiles": "是,删除文件",
"noDeleteFiles": "否,只从面板中移除",
"sendingDelete": "正在删除服务器",
"bePatientDelete": "请耐心等待,我们正在从 Crafty 面板中移除服务器。稍后此页面会关闭。",
"bePatientDeleteFiles" : "请耐心等待,我们正在从 Crafty 面板中移除服务器并删除所有文件。稍后此页面会关闭。"
},
"serverConfigHelp": {
"title": "服务器配置区",
"desc": "您可以在这里更改您的服务器配置",
"perms": [
"我们<code>不推荐</code>更改由 Crafty 管理的服务器的路径。",
"更改路径<code>可能会</code>破坏一些东西,尤其是在 Linux 这类文件权限锁定得更加严格的操作系统上。",
"<br /><br/>",
"如果您认为您必须更改服务器存放的位置,你可能需要给予 \"crafty\" 用户对服务器路径的读取/写入权限。",
"<br />",
"<br />",
"在 Linux 上,最好通过执行如下命令来完成:<br />",
"<code>",
" sudo chown crafty:crafty /您的/服务器/路径 -R<br />",
" sudo chmod 2775 /您的/服务器/路径 -R<br />",
"</code>"
]
},
"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": "合集 <span class='ui-button-icon-primary ui-icon ui-icon-triangle-1-s'\/>",
"colvis": "列可见性",
"colvisRestore": "恢复可见性",
"copy": "复制",
"copyKeys": "按 ctrl 或 u2318 + C 以复制表中的数据到您的系统剪贴板。<br><br>点击这条消息或者按 escapeESC来取消。",
"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": "<strong>警告:</strong>Crafty 无法在没有 JavaScript 的情况下使用!"
}
}

11
main.py
View File

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

View File

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