Merge branch 'dev' into dev-ImprovingMobileDisplay

This commit is contained in:
Zedifus 2022-06-21 20:23:48 +01:00
commit 70e9290627
24 changed files with 586 additions and 183 deletions

View File

@ -49,6 +49,7 @@ win-prod-build:
paths:
- .venv/
rules:
- if: "$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH"
- if: $CI_COMMIT_TAG
environment:
name: production

View File

@ -1,15 +1,18 @@
# Changelog
## --- [4.0.4] - 2022/TBD
## --- [4.0.4] - 2022/06/21
### New features
None
- Add shutdown on backup feature ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/373))
- Add detection and dropdown of java versions ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/375))
### Bug fixes
None
- Backup/Config.json rework for API key hardening ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/369))
- Fix stack on ping result being falsy ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/371))
- Fix sec bug with server creation roles ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/376))
### Tweaks
Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/370))
- Fixing Display on server wizard when used on small screens
- Rework server list on dashboard display for use on small screens
- Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/370))
- Backup failure warning (Tab text goes red) ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/373))
- Rework server list on dashboard display for use on small screens ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/372))
<br><br>
## --- [4.0.3] - 2022/06/18
@ -33,10 +36,6 @@ Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/c
### Bug fixes
- Fix winreg import pass on non-NT systems ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/344))
- Make the WebSocket automatically reconnect. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/345))
- Fix an error when there are no servers ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/346))
- Use relative paths for the jarfile and logs ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/347))
- Flatten all instances of username creation or editing, usernames should be lower case.
- - ([Merge Request 1](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
- - ([Merge Request 2](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/351))
- Add version inheretence & config check ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/353))
- Fix support log temp file deletion issue/hang ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/354))
@ -49,6 +48,3 @@ Spelling mistake fixed in German lang file ([Merge Request](https://gitlab.com/c
- Remove session.lock warning ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/338))
- Correct Dutch Spacing Issue ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/340))
- Remove no-else-* pylint exemptions and tidy code. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/342))
- Make unRAID more readable, and flatten path to lower, to fit standard practice. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/337))
- Fix Java Pathing issues on windows ([Commit](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/343/diffs?commit_id=cda2120579083d447db5dbeb5489822880f4cae7))

View File

@ -2,11 +2,11 @@
[![Code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
[![Supported Python Versions](https://shields.io/badge/python-3.8%20%7C%203.9%20%7C%203.10%20-blue)](https://www.python.org)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.3--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
[![Version(temp-hardcoded)](https://img.shields.io/badge/release-v4.0.4--beta-orange)](https://gitlab.com/crafty-controller/crafty-4/-/releases)
[![Code Quality(temp-hardcoded)](https://img.shields.io/badge/code%20quality-10-brightgreen)](https://gitlab.com/crafty-controller/crafty-4)
[![Build Status](https://gitlab.com/crafty-controller/crafty-4/badges/master/pipeline.svg)](https://gitlab.com/crafty-controller/crafty-4/-/commits/master)
# Crafty Controller 4.0.3-beta
# Crafty Controller 4.0.4-beta
> Python based Control Panel for your Minecraft Server
## What is Crafty Controller?

View File

@ -17,6 +17,14 @@ class ManagementController:
def get_latest_hosts_stats():
return HelpersManagement.get_latest_hosts_stats()
@staticmethod
def set_crafty_api_key(key):
HelpersManagement.set_secret_api_key(key)
@staticmethod
def get_crafty_api_key():
return HelpersManagement.get_secret_api_key()
# **********************************************************************************
# Commands Methods
# **********************************************************************************
@ -128,9 +136,10 @@ class ManagementController:
max_backups: int = None,
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
):
return self.management_helper.set_backup_config(
server_id, backup_path, max_backups, excluded_dirs, compress
server_id, backup_path, max_backups, excluded_dirs, compress, shutdown
)
@staticmethod

View File

@ -5,6 +5,7 @@ import json
import typing as t
from app.classes.controllers.roles_controller import RolesController
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.singleton import Singleton
from app.classes.shared.server import ServerInstance
@ -28,8 +29,9 @@ logger = logging.getLogger(__name__)
class ServersController(metaclass=Singleton):
servers_list: ServerInstance
def __init__(self, helper, servers_helper, management_helper):
def __init__(self, helper, servers_helper, management_helper, file_helper):
self.helper: Helpers = helper
self.file_helper: FileHelpers = file_helper
self.servers_helper: HelperServers = servers_helper
self.management_helper = management_helper
self.servers_list = []
@ -189,6 +191,7 @@ class ServersController(metaclass=Singleton):
self.helper,
self.management_helper,
self.stats,
self.file_helper,
),
"server_settings": settings.props,
}

View File

@ -218,7 +218,7 @@ class Stats:
return level_total_size
def get_server_players(self, server_id): # pylint: disable=no-self-use
def get_server_players(self, server_id):
server = HelperServers.get_server_data_by_id(server_id)
@ -265,7 +265,7 @@ class Stats:
logger.info(
"Unable to read the server icon due to the following error:", exc_info=e
)
if ping_obj:
ping_data = {
"online": online_stats.get("online", 0),
"max": online_stats.get("max", 0),
@ -274,6 +274,15 @@ class Stats:
"server_version": ping_obj.version,
"server_icon": server_icon,
}
else:
ping_data = {
"online": online_stats.get("online", 0),
"max": online_stats.get("max", 0),
"players": online_stats.get("players", 0),
"server_description": "",
"server_version": "",
"server_icon": server_icon,
}
return ping_data

View File

@ -38,6 +38,16 @@ class AuditLog(BaseModel):
table_name = "audit_log"
# **********************************************************************************
# Crafty Settings Class
# **********************************************************************************
class CraftySettings(BaseModel):
secret_api_key = CharField(default="")
class Meta:
table_name = "crafty_settings"
# **********************************************************************************
# Host_Stats Class
# **********************************************************************************
@ -118,6 +128,7 @@ class Backups(BaseModel):
max_backups = IntegerField()
server_id = ForeignKeyField(Servers, backref="backups_server")
compress = BooleanField(default=False)
shutdown = BooleanField(default=False)
class Meta:
table_name = "backups"
@ -231,6 +242,17 @@ class HelpersManagement:
else:
return
@staticmethod
def set_secret_api_key(key):
CraftySettings.insert(secret_api_key=key).execute()
@staticmethod
def get_secret_api_key():
settings = CraftySettings.select(CraftySettings.secret_api_key).where(
CraftySettings.id == 1
)
return settings[0].secret_api_key
# **********************************************************************************
# Schedules Methods
# **********************************************************************************
@ -330,6 +352,7 @@ class HelpersManagement:
"max_backups": row.max_backups,
"server_id": row.server_id_id,
"compress": row.compress,
"shutdown": row.shutdown,
}
except IndexError:
conf = {
@ -338,6 +361,7 @@ class HelpersManagement:
"max_backups": 0,
"server_id": server_id,
"compress": False,
"shutdown": False,
}
return conf
@ -348,6 +372,7 @@ class HelpersManagement:
max_backups: int = None,
excluded_dirs: list = None,
compress: bool = False,
shutdown: bool = False,
):
logger.debug(f"Updating server {server_id} backup config with {locals()}")
if Backups.select().where(Backups.server_id == server_id).exists():
@ -359,6 +384,7 @@ class HelpersManagement:
"max_backups": 0,
"server_id": server_id,
"compress": False,
"shutdown": False,
}
new_row = True
if max_backups is not None:
@ -367,6 +393,7 @@ class HelpersManagement:
dirs_to_exclude = ",".join(excluded_dirs)
conf["excluded_dirs"] = dirs_to_exclude
conf["compress"] = compress
conf["shutdown"] = shutdown
if not new_row:
with self.database.atomic():
if backup_path is not None:

View File

@ -5,6 +5,7 @@ import jwt
from jwt import PyJWTError
from app.classes.models.users import HelperUsers, ApiKeys
from app.classes.controllers.management_controller import ManagementController
logger = logging.getLogger(__name__)
@ -13,11 +14,14 @@ class Authentication:
def __init__(self, helper):
self.helper = helper
self.secret = "my secret"
self.secret = self.helper.get_setting("apikey_secret", None)
if self.secret is None or self.secret == "random":
try:
self.secret = ManagementController.get_crafty_api_key()
if self.secret == "":
self.secret = self.helper.random_string_generator(64)
self.helper.set_setting("apikey_secret", self.secret)
ManagementController.set_crafty_api_key(str(self.secret))
except:
self.secret = self.helper.random_string_generator(64)
ManagementController.set_crafty_api_key(str(self.secret))
def generate(self, user_id, extra=None):
if extra is None:

View File

@ -2,14 +2,22 @@ import os
import shutil
import logging
import pathlib
import tempfile
import zipfile
from zipfile import ZipFile, ZIP_DEFLATED
from app.classes.shared.helpers import Helpers
from app.classes.shared.console import Console
logger = logging.getLogger(__name__)
class FileHelpers:
allowed_quotes = ['"', "'", "`"]
def __init__(self, helper):
self.helper: Helpers = helper
@staticmethod
def del_dirs(path):
path = pathlib.Path(path)
@ -82,7 +90,6 @@ class FileHelpers:
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
return True
@staticmethod
@ -113,3 +120,173 @@ class FileHelpers:
)
return True
def make_compressed_backup(
self, path_to_destination, path_to_zip, excluded_dirs, server_id
):
# create a ZipFile object
path_to_destination += ".zip"
ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
total_bytes = 0
dir_bytes = Helpers.get_dir_size(path_to_zip)
results = {
"percent": 0,
"total_files": self.helper.human_readable_file_size(dir_bytes),
}
self.helper.websocket_helper.broadcast_page_params(
"/panel/server_detail",
{"id": str(server_id)},
"backup_status",
results,
)
with ZipFile(path_to_destination, "w", ZIP_DEFLATED) as zip_file:
for root, dirs, files in os.walk(path_to_zip, topdown=True):
for l_dir in dirs:
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
dirs.remove(l_dir)
ziproot = path_to_zip
for file in files:
if (
str(os.path.join(root, file)).replace("\\", "/")
not in ex_replace
and file != "crafty.sqlite"
):
try:
logger.info(f"backing up: {os.path.join(root, file)}")
if os.name == "nt":
zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, ""), file),
)
else:
zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, "/"), file),
)
except Exception as e:
logger.warning(
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
total_bytes += os.path.getsize(os.path.join(root, file))
percent = round((total_bytes / dir_bytes) * 100, 2)
results = {
"percent": percent,
"total_files": self.helper.human_readable_file_size(dir_bytes),
}
self.helper.websocket_helper.broadcast_page_params(
"/panel/server_detail",
{"id": str(server_id)},
"backup_status",
results,
)
return True
def make_backup(self, path_to_destination, path_to_zip, excluded_dirs, server_id):
# create a ZipFile object
path_to_destination += ".zip"
ex_replace = [p.replace("\\", "/") for p in excluded_dirs]
total_bytes = 0
dir_bytes = Helpers.get_dir_size(path_to_zip)
results = {
"percent": 0,
"total_files": self.helper.human_readable_file_size(dir_bytes),
}
self.helper.websocket_helper.broadcast_page_params(
"/panel/server_detail",
{"id": str(server_id)},
"backup_status",
results,
)
with ZipFile(path_to_destination, "w") as zip_file:
for root, dirs, files in os.walk(path_to_zip, topdown=True):
for l_dir in dirs:
if str(os.path.join(root, l_dir)).replace("\\", "/") in ex_replace:
dirs.remove(l_dir)
ziproot = path_to_zip
for file in files:
if (
str(os.path.join(root, file)).replace("\\", "/")
not in ex_replace
and file != "crafty.sqlite"
):
try:
logger.info(f"backing up: {os.path.join(root, file)}")
if os.name == "nt":
zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, ""), file),
)
else:
zip_file.write(
os.path.join(root, file),
os.path.join(root.replace(ziproot, "/"), file),
)
except Exception as e:
logger.warning(
f"Error backing up: {os.path.join(root, file)}!"
f" - Error was: {e}"
)
total_bytes += os.path.getsize(os.path.join(root, file))
percent = round((total_bytes / dir_bytes) * 100, 2)
results = {
"percent": percent,
"total_files": self.helper.human_readable_file_size(dir_bytes),
}
self.helper.websocket_helper.broadcast_page_params(
"/panel/server_detail",
{"id": str(server_id)},
"backup_status",
results,
)
return True
@staticmethod
def unzip_file(zip_path):
new_dir_list = zip_path.split("/")
new_dir = ""
for i in range(len(new_dir_list) - 1):
if i == 0:
new_dir += new_dir_list[i]
else:
new_dir += "/" + new_dir_list[i]
if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path):
Helpers.ensure_dir_exists(new_dir)
temp_dir = tempfile.mkdtemp()
try:
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(temp_dir)
for i in enumerate(zip_ref.filelist):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
i
].filename.endswith("/"):
break
full_root_path = temp_dir
for item in os.listdir(full_root_path):
if os.path.isdir(os.path.join(full_root_path, item)):
try:
FileHelpers.move_dir(
os.path.join(full_root_path, item),
os.path.join(new_dir, item),
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
else:
try:
FileHelpers.move_file(
os.path.join(full_root_path, item),
os.path.join(new_dir, item),
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
except Exception as ex:
Console.error(ex)
else:
return "false"
return

View File

@ -15,6 +15,8 @@ import html
import zipfile
import pathlib
import ctypes
import subprocess
import itertools
from datetime import datetime
from socket import gethostname
from contextlib import redirect_stderr, suppress
@ -22,7 +24,6 @@ from contextlib import redirect_stderr, suppress
from app.classes.shared.null_writer import NullWriter
from app.classes.shared.console import Console
from app.classes.shared.installer import installer
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.translation import Translation
from app.classes.web.websocket_helper import WebSocketHelper
@ -81,6 +82,60 @@ class Helpers:
print(f"Import Error: Unable to load {ex.name} module")
installer.do_install()
@staticmethod
def find_java_installs():
# If we're windows return oracle java versions,
# otherwise java vers need to be manual.
if os.name == "nt":
# Adapted from LeeKamentsky >>>
# https://github.com/LeeKamentsky/python-javabridge/blob/master/javabridge/locate.py
jdk_key_paths = (
"SOFTWARE\\JavaSoft\\JDK",
"SOFTWARE\\JavaSoft\\Java Development Kit",
)
java_paths = []
for jdk_key_path in jdk_key_paths:
try:
with suppress(OSError), winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE, jdk_key_path
) as kjdk:
for i in itertools.count():
version = winreg.EnumKey(kjdk, i)
kjdk_current = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
jdk_key_path,
)
kjdk_current = winreg.OpenKey(
winreg.HKEY_LOCAL_MACHINE,
jdk_key_path + "\\" + version,
)
kjdk_current_values = dict( # pylint: disable=consider-using-dict-comprehension
[
winreg.EnumValue(kjdk_current, i)[:2]
for i in range(winreg.QueryInfoKey(kjdk_current)[1])
]
)
java_paths.append(kjdk_current_values["JavaHome"])
except OSError as e:
if e.errno == 2:
continue
raise
return java_paths
# If we get here we're linux so we will use 'update-alternatives'
# (If distro does not have update-alternatives then manual input.)
try:
paths = subprocess.check_output(
["/usr/bin/update-alternatives", "--list", "java"], encoding="utf8"
)
if re.match("^(/[^/ ]*)+/?$", paths):
return paths.split("\n")
except Exception as e:
print("Java Detect Error: ", e)
logger.error(f"Java Detect Error: {e}")
@staticmethod
def float_to_string(gbs: float):
s = str(float(gbs) * 1000).rstrip("0").rstrip(".")
@ -441,53 +496,6 @@ class Helpers:
return ctypes.windll.shell32.IsUserAnAdmin() == 1
return os.geteuid() == 0
@staticmethod
def unzip_file(zip_path):
new_dir_list = zip_path.split("/")
new_dir = ""
for i in range(len(new_dir_list) - 1):
if i == 0:
new_dir += new_dir_list[i]
else:
new_dir += "/" + new_dir_list[i]
if Helpers.check_file_perms(zip_path) and os.path.isfile(zip_path):
Helpers.ensure_dir_exists(new_dir)
temp_dir = tempfile.mkdtemp()
try:
with zipfile.ZipFile(zip_path, "r") as zip_ref:
zip_ref.extractall(temp_dir)
for i in enumerate(zip_ref.filelist):
if len(zip_ref.filelist) > 1 or not zip_ref.filelist[
i
].filename.endswith("/"):
break
full_root_path = temp_dir
for item in os.listdir(full_root_path):
if os.path.isdir(os.path.join(full_root_path, item)):
try:
FileHelpers.move_dir(
os.path.join(full_root_path, item),
os.path.join(new_dir, item),
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
else:
try:
FileHelpers.move_file(
os.path.join(full_root_path, item),
os.path.join(new_dir, item),
)
except Exception as ex:
logger.error(f"ERROR IN ZIP IMPORT: {ex}")
except Exception as ex:
Console.error(ex)
else:
return "false"
return
def ensure_logging_setup(self):
log_file = os.path.join(os.path.curdir, "logs", "commander.log")
session_log_file = os.path.join(os.path.curdir, "logs", "session.log")
@ -832,7 +840,7 @@ class Helpers:
for item in file_list:
if os.path.isdir(os.path.join(folder, item)):
dir_list.append(item)
else:
elif str(item) != "crafty.sqlite":
unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold
@ -863,13 +871,14 @@ class Helpers:
@staticmethod
def generate_dir(folder, output=""):
dir_list = []
unsorted_files = []
file_list = os.listdir(folder)
for item in file_list:
if os.path.isdir(os.path.join(folder, item)):
dir_list.append(item)
else:
elif str(item) != "crafty.sqlite":
unsorted_files.append(item)
file_list = sorted(dir_list, key=str.casefold) + sorted(
unsorted_files, key=str.casefold
@ -986,14 +995,6 @@ class Helpers:
[parent_path, child_path]
)
@staticmethod
def copy_files(source, dest):
if os.path.isfile(source):
FileHelpers.copy_file(source, dest)
logger.info("Copying jar %s to %s", source, dest)
else:
logger.info("Source jar does not exist.")
@staticmethod
def download_file(executable_url, jar_path):
try:

View File

@ -34,8 +34,9 @@ logger = logging.getLogger(__name__)
class Controller:
def __init__(self, database, helper):
def __init__(self, database, helper, file_helper):
self.helper: Helpers = helper
self.file_helper: FileHelpers = file_helper
self.server_jars: ServerJars = ServerJars(helper)
self.users_helper: HelperUsers = HelperUsers(database, self.helper)
self.roles_helper: HelperRoles = HelperRoles(database)
@ -53,7 +54,7 @@ class Controller:
)
self.server_perms: ServerPermsController = ServerPermsController()
self.servers: ServersController = ServersController(
self.helper, self.servers_helper, self.management_helper
self.helper, self.servers_helper, self.management_helper, self.file_helper
)
self.users: UsersController = UsersController(
self.helper, self.users_helper, self.authentication

View File

@ -17,8 +17,6 @@ class DatabaseBuilder:
logger.info("Fresh Install Detected - Creating Default Settings")
Console.info("Fresh Install Detected - Creating Default Settings")
default_data = self.helper.find_default_password()
# Reset this value if the DB has been dumped
self.helper.set_setting("apikey_secret", "random")
username = default_data.get("username", "admin")
password = default_data.get("password", "crafty")

View File

@ -9,7 +9,6 @@ import threading
import logging.config
import subprocess
import html
import tempfile
# TZLocal is set as a hidden import on win pipeline
from tzlocal import get_localzone
@ -102,12 +101,14 @@ class ServerOutBuf:
class ServerInstance:
server_object: Servers
helper: Helpers
file_helper: FileHelpers
management_helper: HelpersManagement
stats: Stats
stats_helper: HelperServerStats
def __init__(self, server_id, helper, management_helper, stats):
def __init__(self, server_id, helper, management_helper, stats, file_helper):
self.helper = helper
self.file_helper = file_helper
self.management_helper = management_helper
# holders for our process
self.process = None
@ -126,6 +127,7 @@ class ServerInstance:
self.stats = stats
self.server_object = HelperServers.get_server_obj(self.server_id)
self.stats_helper = HelperServerStats(self.server_id)
self.last_backup_failed = False
try:
tz = get_localzone()
except ZoneInfoNotFoundError:
@ -846,6 +848,7 @@ class ServerInstance:
"backup_reload",
{"percent": 0, "total_files": 0},
)
was_server_running = None
logger.info(f"Starting server {self.name} (ID {self.server_id}) backup")
server_users = PermissionsServers.get_server_user_list(self.server_id)
for user in server_users:
@ -858,6 +861,15 @@ class ServerInstance:
)
time.sleep(3)
conf = HelpersManagement.get_backup_config(self.server_id)
if conf["shutdown"]:
logger.info(
"Found shutdown preference. Delaying"
+ "backup start. Shutting down server."
)
if self.check_running():
self.stop_server()
was_server_running = True
self.helper.ensure_dir_exists(self.settings["backup_path"])
try:
backup_filename = (
@ -869,62 +881,27 @@ class ServerInstance:
f" (ID#{self.server_id}, path={self.server_path}) "
f"at '{backup_filename}'"
)
temp_dir = tempfile.mkdtemp()
self.server_scheduler.add_job(
self.backup_status,
"interval",
seconds=1,
id="backup_" + str(self.server_id),
args=[temp_dir + "/", backup_filename + ".zip"],
)
# pylint: disable=unexpected-keyword-arg
try:
FileHelpers.copy_dir(self.server_path, temp_dir, dirs_exist_ok=True)
except shutil.Error as e:
logger.error(f"Failed to fully complete backup due to shutil error {e}")
excluded_dirs = HelpersManagement.get_excluded_backup_dirs(self.server_id)
server_dir = Helpers.get_os_understandable_path(self.settings["path"])
for my_dir in excluded_dirs:
# Take the full path of the excluded dir and replace the
# server path with the temp path, this is so that we're
# only deleting excluded dirs from the temp path
# and not the server path
excluded_dir = Helpers.get_os_understandable_path(my_dir).replace(
server_dir, Helpers.get_os_understandable_path(temp_dir)
)
# Next, check to see if it is a directory
if os.path.isdir(excluded_dir):
# If it is a directory,
# recursively delete the entire directory from the backup
try:
FileHelpers.del_dirs(excluded_dir)
except FileNotFoundError:
Console.error(
f"Excluded dir {excluded_dir} not found. Moving on..."
)
else:
# If not, just remove the file
try:
os.remove(excluded_dir)
except:
Console.error(
f"Excluded dir {excluded_dir} not found. Moving on..."
)
if conf["compress"]:
logger.debug(
"Found compress backup to be true. Calling compressed archive"
)
FileHelpers.make_compressed_archive(
Helpers.get_os_understandable_path(backup_filename), temp_dir
self.file_helper.make_compressed_backup(
Helpers.get_os_understandable_path(backup_filename),
server_dir,
excluded_dirs,
self.server_id,
)
else:
logger.debug(
"Found compress backup to be false. Calling NON-compressed archive"
)
FileHelpers.make_archive(
Helpers.get_os_understandable_path(backup_filename), temp_dir
self.file_helper.make_backup(
Helpers.get_os_understandable_path(backup_filename),
server_dir,
excluded_dirs,
self.server_id,
)
while (
@ -939,7 +916,6 @@ class ServerInstance:
self.is_backingup = False
logger.info(f"Backup of server: {self.name} completed")
self.server_scheduler.remove_job("backup_" + str(self.server_id))
results = {"percent": 100, "total_files": 0, "current_file": 0}
if len(self.helper.websocket_helper.clients) > 0:
self.helper.websocket_helper.broadcast_page_params(
@ -959,12 +935,17 @@ class ServerInstance:
HelperUsers.get_user_lang_by_id(user),
).format(self.name),
)
if was_server_running:
logger.info(
"Backup complete. User had shutdown preference. Starting server."
)
self.start_server(HelperUsers.get_user_id_by_name("system"))
time.sleep(3)
self.last_backup_failed = False
except:
logger.exception(
f"Failed to create backup of server {self.name} (ID {self.server_id})"
)
self.server_scheduler.remove_job("backup_" + str(self.server_id))
results = {"percent": 100, "total_files": 0, "current_file": 0}
if len(self.helper.websocket_helper.clients) > 0:
self.helper.websocket_helper.broadcast_page_params(
@ -974,8 +955,12 @@ class ServerInstance:
results,
)
self.is_backingup = False
finally:
FileHelpers.del_dirs(temp_dir)
if was_server_running:
logger.info(
"Backup complete. User had shutdown preference. Starting server."
)
self.start_server(HelperUsers.get_user_id_by_name("system"))
self.last_backup_failed = True
def backup_status(self, source_path, dest_path):
results = Helpers.calc_percent(source_path, dest_path)
@ -988,6 +973,9 @@ class ServerInstance:
results,
)
def last_backup_status(self):
return self.last_backup_failed
def send_backup_status(self):
try:
return self.backup_stats
@ -1093,7 +1081,7 @@ class ServerInstance:
)
# copies to backup dir
Helpers.copy_files(current_executable, backup_executable)
FileHelpers.copy_file(current_executable, backup_executable)
# boolean returns true for false for success
downloaded = Helpers.download_file(

View File

@ -220,7 +220,7 @@ class FileHandler(BaseHandler):
path = Helpers.get_os_understandable_path(self.get_argument("path", None))
if Helpers.is_os_windows():
path = Helpers.wtol_path(path)
Helpers.unzip_file(path)
FileHelpers.unzip_file(path)
self.redirect(f"/panel/server_detail?id={server_id}&subpage=files")
return

View File

@ -6,6 +6,7 @@ import typing as t
import json
import logging
import threading
import shlex
import bleach
import libgravatar
import requests
@ -497,6 +498,10 @@ class PanelHandler(BaseHandler):
if server_id is None:
return
server_obj = self.controller.servers.get_server_instance_by_id(server_id)
page_data["backup_failed"] = server_obj.last_backup_status()
server_obj = None
valid_subpages = [
"term",
"logs",
@ -627,6 +632,18 @@ class PanelHandler(BaseHandler):
"/panel/error?error=Unauthorized access Server Config"
)
return
page_data["java_versions"] = Helpers.find_java_installs()
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
page_java = []
page_data["java_versions"].append("java")
for version in page_data["java_versions"]:
if os.name == "nt":
page_java.append(version)
else:
if len(version) > 0:
page_java.append(version)
page_data["java_versions"] = page_java
if subpage == "files":
if (
@ -1342,6 +1359,8 @@ class PanelHandler(BaseHandler):
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)
@ -1355,11 +1374,46 @@ class PanelHandler(BaseHandler):
auto_start = int(float(self.get_argument("auto_start", "0")))
crash_detection = int(float(self.get_argument("crash_detection", "0")))
logs_delete_after = int(float(self.get_argument("logs_delete_after", "0")))
java_selection = self.get_argument("java_selection", None)
# subpage = self.get_argument('subpage', None)
server_id = self.check_server_id()
if server_id is None:
return
if java_selection:
try:
execution_list = shlex.split(execution_command)
except ValueError:
self.redirect(
"/panel/error?error=Invalid execution command. Java path"
" must be surrounded by quotes."
" (Are you missing a closing quote?)"
)
if not any(
java_selection in path for path in Helpers.find_java_installs()
):
self.redirect(
"/panel/error?error=Attack attempted."
+ " A copy of this report is being sent to server owner."
)
self.controller.management.add_to_audit_log_raw(
exec_user["username"],
exec_user["user_id"],
server_id,
f"Attempted to send bad java path for {server_id}."
+ " Possible attack. Act accordingly.",
self.get_remote_ip(),
)
if java_selection != "java":
if self.helper.is_os_windows():
execution_list[0] = '"' + java_selection + '/bin/java"'
else:
execution_list[0] = '"' + java_selection + '"'
else:
execution_list[0] = "java"
execution_command = ""
for item in execution_list:
execution_command += item + " "
server_obj: Servers = self.controller.servers.get_server_obj(server_id)
stale_executable = server_obj.executable
@ -1389,7 +1443,7 @@ class PanelHandler(BaseHandler):
server_obj.path = server_obj.path
server_obj.log_path = server_obj.log_path
server_obj.executable = server_obj.executable
server_obj.execution_command = server_obj.execution_command
server_obj.execution_command = execution_command
server_obj.server_ip = server_obj.server_ip
server_obj.server_port = server_obj.server_port
server_obj.executable_update_url = server_obj.executable_update_url
@ -1433,6 +1487,7 @@ class PanelHandler(BaseHandler):
server_obj = self.controller.servers.get_server_obj(server_id)
compress = self.get_argument("compress", False)
shutdown = self.get_argument("shutdown", False)
check_changed = self.get_argument("changed")
if str(check_changed) == str(1):
checked = self.get_body_arguments("root_path")
@ -1455,6 +1510,7 @@ class PanelHandler(BaseHandler):
max_backups=max_backups,
excluded_dirs=checked,
compress=bool(compress),
shutdown=bool(shutdown),
)
self.controller.management.add_to_audit_log(
@ -1941,7 +1997,10 @@ class PanelHandler(BaseHandler):
self.redirect("/panel/error?error=Invalid Key ID")
return
if key.user_id != exec_user["user_id"]:
if (
str(key.user_id) != str(exec_user["user_id"])
and not exec_user["superuser"]
):
self.redirect(
"/panel/error?error=You are not authorized to access this key."
)

View File

@ -17,6 +17,15 @@ logger = logging.getLogger(__name__)
class ServerHandler(BaseHandler):
def get_user_roles(self):
user_roles = {}
for user_id in self.controller.users.get_all_user_ids():
user_roles_list = self.controller.users.get_user_roles_names(user_id)
# user_servers =
# self.controller.servers.get_authorized_servers(user.user_id)
user_roles[user_id] = user_roles_list
return user_roles
@tornado.web.authenticated
def get(self, page):
(
@ -271,11 +280,19 @@ class ServerHandler(BaseHandler):
)
if page == "step1":
if not superuser and not self.controller.crafty_perms.can_create_server(
exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: "
"not a server creator or server limit reached"
)
return
if not superuser:
user_roles = self.controller.roles.get_all_roles()
else:
user_roles = self.controller.roles.get_all_roles()
user_roles = self.get_user_roles()
server = bleach.clean(self.get_argument("server", ""))
server_name = bleach.clean(self.get_argument("server_name", ""))
min_mem = bleach.clean(self.get_argument("min_memory", ""))
@ -396,6 +413,14 @@ class ServerHandler(BaseHandler):
self.redirect("/panel/dashboard")
if page == "bedrock_step1":
if not superuser and not self.controller.crafty_perms.can_create_server(
exec_user["user_id"]
):
self.redirect(
"/panel/error?error=Unauthorized access: "
"not a server creator or server limit reached"
)
return
if not superuser:
user_roles = self.controller.roles.get_all_roles()
else:

View File

@ -1,6 +1,6 @@
{
"major": 4,
"minor": 0,
"sub": 3,
"sub": 4,
"meta": "beta"
}

View File

@ -19,11 +19,18 @@
</li>
{% end %}
{% if data['permissions']['Backup'] in data['user_permissions'] %}
{% if data['backup_failed'] %}
<li class="nav-item term-nav-item">
<a style="color: red !important;" class="nav-link {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup', data['lang']) }}&nbsp; <i class="fas fa-exclamation-triangle"> </i></a>
</li>
{% else %}
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'backup' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=backup" role="tab" aria-selected="false">
<i class="fas fa-save"></i>{{ translate('serverDetails', 'backup', data['lang']) }}</a>
</li>
{% end %}
{% end %}
{% if data['permissions']['Files'] in data['user_permissions'] %}
<li class="nav-item term-nav-item">
<a class="nav-link {% if data['active_link'] == 'files' %}active{% end %}" href="/panel/server_detail?id={{ data['server_stats']['server_id']['server_id'] }}&subpage=files" role="tab" aria-selected="false">

View File

@ -14,7 +14,8 @@
<div class="col-12">
<div class="page-header">
<h4 class="page-title">
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{ data['server_stats']['server_id']['server_name'] }}
{{ translate('serverDetails', 'serverDetails', data['lang']) }} - {{
data['server_stats']['server_id']['server_name'] }}
<br />
<small>UUID: {{ data['server_stats']['server_id']['server_uuid'] }}</small>
</h4>
@ -51,55 +52,85 @@
{% if data['backing_up'] %}
<div class="progress" style="height: 15px;">
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar" role="progressbar" style="width:{{data['backup_stats']['percent']}}%;" aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{ data['backup_stats']['percent'] }}%</div>
<div class="progress-bar progress-bar-striped progress-bar-animated" id="backup_progress_bar"
role="progressbar" style="width:{{data['backup_stats']['percent']}}%;"
aria-valuenow="{{data['backup_stats']['percent']}}" aria-valuemin="0" aria-valuemax="100">{{
data['backup_stats']['percent'] }}%</div>
</div>
<p>Backing up <span id="total_files">{{data['backup_stats']['total_files']}}</span> Files</p>
<p>Backing up <i class="fas fa-spin fa-spinner"></i> <span
id="total_files">{{data['server_stats']['world_size']}}</span></p>
{% end %}
<br>
{% if not data['backing_up'] %}
<div id="backup_button" class="form-group">
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow', data['lang']) }}</button>
<button class="btn btn-primary" id="backup_now_button">{{ translate('serverBackups', 'backupNow',
data['lang']) }}</button>
</div>
{% end %}
<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']) }}">
<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">
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang']) }}</small> </label>
<input type="text" class="form-control" name="max_backups" id="max_backups" value="{{ data['backup_config']['max_backups'] }}" placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
<label for="server_path">{{ translate('serverBackups', 'maxBackups', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverBackups', 'maxBackupsDesc', data['lang'])
}}</small> </label>
<input type="text" class="form-control" name="max_backups" id="max_backups"
value="{{ data['backup_config']['max_backups'] }}"
placeholder="{{ translate('serverBackups', 'maxBackups', data['lang']) }}">
</div>
<div class="form-group">
<label for="compress" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['compress'] %}
<input type="checkbox" class="form-check-input" id="compress" name="compress"
checked="" value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="compress" name="compress"
<input type="checkbox" class="form-check-input" id="compress" name="compress" checked=""
value="True">{{ translate('serverBackups', 'compress', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="compress" name="compress" value="True">{{
translate('serverBackups', 'compress', data['lang']) }}
{% end %}
</div>
<div class="form-group">
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{ translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_files_button" data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{ translate('serverBackups', 'clickExclude', data['lang']) }}</button>
<label for="shutdown" class="form-check-label ml-4 mb-4"></label>
{% if data['backup_config']['shutdown'] %}
<input type="checkbox" class="form-check-input" id="shutdown" name="shutdown" checked=""
value="True">{{ translate('serverBackups', 'shutdown', data['lang']) }}
{% else %}
<input type="checkbox" class="form-check-input" id="shutdown" name="shutdown" value="True">{{
translate('serverBackups', 'shutdown', data['lang']) }}
{% end %}
</div>
<input type="number" class="form-control" name="changed" id="changed" value="0" style="visibility: hidden;"></input>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select" aria-hidden="true">
<div class="form-group">
<label for="server">{{ translate('serverBackups', 'exclusionsTitle', data['lang']) }} <small> - {{
translate('serverBackups', 'excludedChoose', data['lang']) }}</small></label>
<br>
<button class="btn btn-primary mr-2" id="root_files_button"
data-server_path="{{ data['server_stats']['server_id']['path']}}" type="button">{{
translate('serverBackups', 'clickExclude', data['lang']) }}</button>
</div>
<input type="number" class="form-control" name="changed" id="changed" value="0"
style="visibility: hidden;"></input>
<div class="modal fade" id="dir_select" tabindex="-1" role="dialog" aria-labelledby="dir_select"
aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups', 'excludedChoose', data['lang']) }}</h5>
<h5 class="modal-title" id="exampleModalLongTitle">{{ translate('serverBackups',
'excludedChoose', data['lang']) }}</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div class="tree-ctx-item" id="main-tree-div" data-path="" style="overflow: scroll; max-height:75%;">
<div class="tree-ctx-item" id="main-tree-div" data-path=""
style="overflow: scroll; max-height:75%;">
<input type="checkbox" id="main-tree-input" name="root_path" value="" disabled>
<span id="main-tree" class="files-tree-title tree-caret-down root-dir" data-path="">
<i class="far fa-folder"></i>
@ -110,15 +141,19 @@
</div>
</div>
<div class="modal-footer">
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{ translate('serverWizard', 'save', data['lang']) }}</button>
<button type="button" id="modal-cancel" class="btn btn-secondary" data-dismiss="modal">{{
translate('serverBackups', 'cancel', data['lang']) }}</button>
<button type="button" id="modal-okay" data-dismiss="modal" class="btn btn-primary">{{
translate('serverWizard', 'save', data['lang']) }}</button>
</div>
</div>
</div>
</div>
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang']) }}</button>
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang']) }}</button>
<button type="submit" class="btn btn-success mr-2">{{ translate('serverBackups', 'save', data['lang'])
}}</button>
<button type="reset" class="btn btn-light">{{ translate('serverBackups', 'cancel', data['lang'])
}}</button>
</form>
</div>
@ -138,13 +173,15 @@
{% for backup in data['backup_list'] %}
<tr>
<td>
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}" class="btn btn-primary">
<a href="/panel/download_backup?file={{ backup['path'] }}&id={{ data['server_stats']['server_id']['server_id'] }}"
class="btn btn-primary">
<i class="fas fa-download" aria-hidden="true"></i>
{{ translate('serverBackups', 'download', data['lang']) }}
</a>
<br>
<br>
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}" class="btn btn-danger del_button">
<button data-file="{{ backup['path'] }}" data-backup_path="{{ data['backup_path'] }}"
class="btn btn-danger del_button">
<i class="fas fa-trash" aria-hidden="true"></i>
{{ translate('serverBackups', 'delete', data['lang']) }}
</button>
@ -168,7 +205,8 @@
<br>
<br>
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups', data['lang']) }} <small class="text-muted ml-1"></small> </h4>
<h4 class="card-title"><i class="fas fa-server"></i> {{ translate('serverBackups', 'excludedBackups',
data['lang']) }} <small class="text-muted ml-1"></small> </h4>
</div>
<br>
<ul>

View File

@ -36,7 +36,7 @@
<div class="row">
<div class="col-md-6 col-sm-12">
<form class="forms-sample" method="post" action="/panel/server_detail">
<form class="forms-sample" method="post" id="config_form" action="/panel/server_detail">
{% raw xsrf_form_html() %}
<input type="hidden" name="id" value="{{ data['server_stats']['server_id']['server_id'] }}">
<input type="hidden" name="subpage" value="config">
@ -50,8 +50,8 @@
placeholder="{{ translate('serverConfig', 'serverName', data['lang']) }}" required>
</div>
<div class="form-group">
{% if data['super_user'] %}
<div class="form-group">
<label for="server_path">{{ translate('serverConfig', 'serverPath', data['lang']) }} <small
class="text-muted ml-1"> - {{ translate('serverConfig', 'serverPathDesc', data['lang']) }}</small>
</label>
@ -78,7 +78,24 @@
value="{{ data['server_stats']['server_id']['executable'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutable', data['lang']) }}" required>
</div>
{% end %}
{% if data['server_stats']['server_type'] == "minecraft-java" %}
<div class="form-group">
<label for="java_selection">{{ translate('serverConfig', 'javaVersion', data['lang']) }}
<small class="text-muted ml-1">{{ translate('serverConfig', 'javaVersionDesc', data['lang']) }}</small>
</label>
<select class="form-select form-control form-control-lg select-css" id="java_selection"
name="java_selection" form="config_form">
<option value="">{{ translate('serverConfig',
'javaNoChange', data['lang'])}}</option>
{% for path in data['java_versions'] %}
<option value="{{path}}">{{path}}</option>
{% end %}
</select>
</div>
{% end %}
{% if data['super_user'] %}
<div class="form-group">
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<small class="text-muted ml-1"> - {{ translate('serverConfig', 'serverExecutionCommandDesc',
@ -86,8 +103,14 @@
<input type="text" class="form-control" name="execution_command" id="execution_command"
value="{{ data['server_stats']['server_id']['execution_command'] }}"
placeholder="{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}" required>
{% end %}
</div>
{% else %}
<label for="execution_command">{{ translate('serverConfig', 'serverExecutionCommand', data['lang']) }}
<div class="card-header header-sm d-flex justify-content-between align-items-center">
<span style="color: gray;">{{ data['server_stats']['server_id']['execution_command'] }}</span> 🔒
</div>
<br>
{% end %}
<div class="form-group">
<label for="stop_command">{{ translate('serverConfig', 'serverStopCommand', data['lang']) }} <small

View File

@ -0,0 +1,16 @@
import peewee
import datetime
def migrate(migrator, db):
class CraftySettings(peewee.Model):
secret_api_key = peewee.CharField(default="")
class Meta:
table_name = "crafty_settings"
migrator.create_table(CraftySettings)
def rollback(migrator, db):
migrator.drop_table("crafty_settings")

View File

@ -0,0 +1,16 @@
# Generated by database migrator
import peewee
def migrate(migrator, database, **kwargs):
migrator.add_columns("backups", shutdown=peewee.BooleanField(default=False))
"""
Write your migrations here.
"""
def rollback(migrator, database, **kwargs):
migrator.drop_columns("backups", ["shutdown"])
"""
Write your rollback migrations here.
"""

View File

@ -271,7 +271,8 @@
"save": "Save",
"size": "Size",
"storageLocation": "Storage Location",
"storageLocationDesc": "Where do you want to store backups?"
"storageLocationDesc": "Where do you want to store backups?",
"shutdown": "Shutdown server for duration of backup"
},
"serverConfig": {
"bePatientDelete": "Please be patient while we remove your server from the Crafty panel. This screen will close in a few moments.",
@ -300,6 +301,9 @@
"serverCrashDetection": "Server Crash Detection",
"serverExecutable": "Server Executable",
"serverExecutableDesc": "The server's executable file",
"javaVersion": "Override current Java Version",
"javaVersionDesc": "If you're going to override java. Make sure your current java path in 'execution command' is wrapped in quotes (default 'java' variable excluded)",
"javaNoChange": "Do Not Override",
"serverExecutionCommand": "Server Execution Command",
"serverExecutionCommandDesc": "What will be launched in a hidden terminal",
"serverIP": "Server IP",

View File

@ -7,6 +7,7 @@ import argparse
import logging.config
import signal
import peewee
from app.classes.shared.file_helpers import FileHelpers
from app.classes.shared.import3 import Import3
from app.classes.shared.console import Console
@ -132,9 +133,9 @@ if __name__ == "__main__":
installer.default_settings()
else:
Console.debug("Existing install detected")
file_helper = FileHelpers(helper)
# now the tables are created, we can load the tasks_manager and server controller
controller = Controller(database, helper)
controller = Controller(database, helper, file_helper)
import3 = Import3(helper, controller)
tasks_manager = TasksManager(helper, controller)
tasks_manager.start_webserver()