2020-08-12 00:36:09 +00:00
|
|
|
import os
|
|
|
|
import sys
|
|
|
|
import json
|
2022-05-15 22:33:31 +00:00
|
|
|
from threading import Thread
|
2020-08-12 00:36:09 +00:00
|
|
|
import time
|
|
|
|
import argparse
|
|
|
|
import logging.config
|
2021-06-02 18:30:12 +00:00
|
|
|
import signal
|
2022-04-11 05:23:55 +00:00
|
|
|
import peewee
|
2022-07-20 23:39:29 +00:00
|
|
|
from packaging import version as pkg_version
|
2022-06-05 20:17:23 +00:00
|
|
|
|
2022-07-20 22:56:09 +00:00
|
|
|
from app.classes.shared.file_helpers import FileHelpers
|
2022-06-05 20:08:58 +00:00
|
|
|
from app.classes.shared.import3 import Import3
|
2022-04-11 05:23:55 +00:00
|
|
|
from app.classes.shared.console import Console
|
|
|
|
from app.classes.shared.helpers import Helpers
|
2022-06-05 20:17:23 +00:00
|
|
|
from app.classes.models.users import HelperUsers
|
2023-01-29 21:54:02 +00:00
|
|
|
from app.classes.models.management import HelpersManagement
|
2022-08-17 21:22:03 +00:00
|
|
|
from app.classes.shared.import_helper import ImportHelpers
|
2022-03-23 02:50:12 +00:00
|
|
|
|
2022-04-11 05:23:55 +00:00
|
|
|
console = Console()
|
2022-04-12 01:34:46 +00:00
|
|
|
helper = Helpers()
|
2022-04-14 02:10:25 +00:00
|
|
|
if helper.check_root():
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.critical(
|
2022-03-23 06:16:22 +00:00
|
|
|
"Root detected. Root/Admin access denied. "
|
|
|
|
"Run Crafty again with non-elevated permissions."
|
2022-03-23 02:50:12 +00:00
|
|
|
)
|
2022-03-02 13:12:03 +00:00
|
|
|
time.sleep(5)
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.critical("Crafty shutting down. Root/Admin access denied.")
|
2022-03-02 13:12:03 +00:00
|
|
|
sys.exit(0)
|
2022-01-26 01:45:30 +00:00
|
|
|
# pylint: disable=wrong-import-position
|
2022-04-11 05:23:55 +00:00
|
|
|
try:
|
|
|
|
from app.classes.models.base_model import database_proxy
|
2022-04-14 02:10:25 +00:00
|
|
|
from app.classes.shared.main_models import DatabaseBuilder
|
2022-04-11 05:23:55 +00:00
|
|
|
from app.classes.shared.tasks import TasksManager
|
|
|
|
from app.classes.shared.main_controller import Controller
|
|
|
|
from app.classes.shared.migration import MigrationManager
|
|
|
|
from app.classes.shared.command import MainPrompt
|
|
|
|
except ModuleNotFoundError as err:
|
|
|
|
helper.auto_installer_fix(err)
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2020-08-19 01:04:43 +00:00
|
|
|
|
2020-08-12 00:36:09 +00:00
|
|
|
def do_intro():
|
2020-08-17 02:47:53 +00:00
|
|
|
logger.info("***** Crafty Controller Started *****")
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2022-07-20 23:39:29 +00:00
|
|
|
version = helper.get_version_string()
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2021-11-02 14:08:27 +00:00
|
|
|
intro = f"""
|
|
|
|
{'/' * 75}
|
2022-07-20 23:39:29 +00:00
|
|
|
#{("Welcome to Crafty Controller - v." + version).center(73, " ")}#
|
2021-11-02 14:08:27 +00:00
|
|
|
{'/' * 75}
|
|
|
|
#{"Server Manager / Web Portal for your Minecraft server".center(73, " ")}#
|
|
|
|
#{"Homepage: www.craftycontrol.com".center(73, " ")}#
|
|
|
|
{'/' * 75}
|
|
|
|
"""
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.magenta(intro)
|
2023-01-29 19:54:25 +00:00
|
|
|
if not helper.check_file_exists(helper.settings_file):
|
|
|
|
Console.debug("No settings file detected. Creating one.")
|
|
|
|
helper.set_settings(Helpers.get_master_config())
|
2020-08-12 00:36:09 +00:00
|
|
|
|
|
|
|
|
2021-03-09 03:01:42 +00:00
|
|
|
def setup_logging(debug=True):
|
2022-03-23 02:50:12 +00:00
|
|
|
logging_config_file = os.path.join(os.path.curdir, "app", "config", "logging.json")
|
2020-08-12 00:36:09 +00:00
|
|
|
|
|
|
|
if os.path.exists(logging_config_file):
|
|
|
|
# open our logging config file
|
2022-03-23 02:50:12 +00:00
|
|
|
with open(logging_config_file, "rt", encoding="utf-8") as f:
|
2020-08-12 00:36:09 +00:00
|
|
|
logging_config = json.load(f)
|
|
|
|
if debug:
|
2022-03-23 02:50:12 +00:00
|
|
|
logging_config["loggers"][""]["level"] = "DEBUG"
|
2021-01-19 13:56:00 +00:00
|
|
|
|
2020-08-12 00:36:09 +00:00
|
|
|
logging.config.dictConfig(logging_config)
|
2021-01-19 13:56:00 +00:00
|
|
|
|
2020-08-12 00:36:09 +00:00
|
|
|
else:
|
|
|
|
logging.basicConfig(level=logging.DEBUG)
|
2022-01-26 01:45:30 +00:00
|
|
|
logging.warning(f"Unable to read logging config from {logging_config_file}")
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.critical(f"Unable to read logging config from {logging_config_file}")
|
2020-08-12 00:36:09 +00:00
|
|
|
|
|
|
|
|
2022-01-26 01:45:30 +00:00
|
|
|
# Our Main Starter
|
2022-03-23 02:50:12 +00:00
|
|
|
if __name__ == "__main__":
|
2020-08-31 17:46:25 +00:00
|
|
|
parser = argparse.ArgumentParser("Crafty Controller - A Server Management System")
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"-i", "--ignore", action="store_true", help="Ignore session.lock files"
|
|
|
|
)
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"-v", "--verbose", action="store_true", help="Sets logging level to debug."
|
|
|
|
)
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2022-03-23 02:50:12 +00:00
|
|
|
parser.add_argument(
|
|
|
|
"-d",
|
|
|
|
"--daemon",
|
|
|
|
action="store_true",
|
|
|
|
help="Runs Crafty in daemon mode (no prompt)",
|
|
|
|
)
|
2021-03-06 20:48:02 +00:00
|
|
|
|
2020-08-12 00:36:09 +00:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
2022-01-26 02:00:40 +00:00
|
|
|
helper.ensure_logging_setup()
|
|
|
|
|
2020-08-12 00:36:09 +00:00
|
|
|
setup_logging(debug=args.verbose)
|
|
|
|
|
2023-02-14 19:44:53 +00:00
|
|
|
if args.verbose:
|
|
|
|
Console.level = "debug"
|
|
|
|
|
2020-08-12 00:36:09 +00:00
|
|
|
# setting up the logger object
|
|
|
|
logger = logging.getLogger(__name__)
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.cyan(f"Logging set to: {logger.level}")
|
2022-04-11 05:23:55 +00:00
|
|
|
peewee_logger = logging.getLogger("peewee")
|
|
|
|
peewee_logger.setLevel(logging.INFO)
|
2020-08-12 00:36:09 +00:00
|
|
|
|
|
|
|
# print our pretty start message
|
|
|
|
do_intro()
|
|
|
|
|
2020-08-31 17:46:25 +00:00
|
|
|
# our session file, helps prevent multiple controller agents on the same machine.
|
2020-08-12 00:36:09 +00:00
|
|
|
helper.create_session_file(ignore=args.ignore)
|
|
|
|
|
2022-04-11 05:23:55 +00:00
|
|
|
# start the database
|
|
|
|
database = peewee.SqliteDatabase(
|
2022-04-11 10:08:36 +00:00
|
|
|
helper.db_path, pragmas={"journal_mode": "wal", "cache_size": -1024 * 10}
|
2022-04-11 05:23:55 +00:00
|
|
|
)
|
|
|
|
database_proxy.initialize(database)
|
|
|
|
|
|
|
|
migration_manager = MigrationManager(database, helper)
|
2022-03-23 02:50:12 +00:00
|
|
|
migration_manager.up() # Automatically runs migrations
|
2022-01-26 01:45:30 +00:00
|
|
|
|
2020-08-31 17:46:25 +00:00
|
|
|
# do our installer stuff
|
2022-04-14 02:10:25 +00:00
|
|
|
user_helper = HelperUsers(database, helper)
|
2023-01-29 21:54:02 +00:00
|
|
|
management_helper = HelpersManagement(database, helper)
|
|
|
|
installer = DatabaseBuilder(database, helper, user_helper, management_helper)
|
2022-04-14 02:10:25 +00:00
|
|
|
FRESH_INSTALL = installer.is_fresh_install()
|
2020-09-22 19:00:05 +00:00
|
|
|
|
2022-04-14 02:10:25 +00:00
|
|
|
if FRESH_INSTALL:
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.debug("Fresh install detected")
|
|
|
|
Console.warning(
|
2022-03-23 06:16:22 +00:00
|
|
|
f"We have detected a fresh install. Please be sure to forward "
|
|
|
|
f"Crafty's port, {helper.get_setting('https_port')}, "
|
|
|
|
f"through your router/firewall if you would like to be able "
|
|
|
|
f"to access Crafty remotely."
|
2022-03-23 02:50:12 +00:00
|
|
|
)
|
2020-08-31 17:46:25 +00:00
|
|
|
installer.default_settings()
|
2021-03-22 04:02:18 +00:00
|
|
|
else:
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.debug("Existing install detected")
|
2023-01-29 23:21:53 +00:00
|
|
|
Console.info("Checking for reset secret flag")
|
|
|
|
if helper.get_setting("reset_secrets_on_next_boot"):
|
|
|
|
Console.info("Found Reset")
|
|
|
|
management_helper.set_secret_api_key(str(helper.random_string_generator(64)))
|
|
|
|
management_helper.set_cookie_secret(str(helper.random_string_generator(32)))
|
|
|
|
helper.set_setting("reset_secrets_on_next_boot", False)
|
|
|
|
else:
|
|
|
|
Console.info("No flag found. Secrets are staying")
|
2022-06-20 19:26:21 +00:00
|
|
|
file_helper = FileHelpers(helper)
|
2022-08-17 21:22:03 +00:00
|
|
|
import_helper = ImportHelpers(helper, file_helper)
|
2022-04-27 19:07:34 +00:00
|
|
|
# now the tables are created, we can load the tasks_manager and server controller
|
2022-08-17 21:22:03 +00:00
|
|
|
controller = Controller(database, helper, file_helper, import_helper)
|
2023-01-29 19:54:25 +00:00
|
|
|
Console.info("Checking for remote changes to config.json")
|
|
|
|
controller.get_config_diff()
|
|
|
|
Console.info("Remote change complete.")
|
2023-01-29 21:54:02 +00:00
|
|
|
|
2022-06-05 20:02:52 +00:00
|
|
|
import3 = Import3(helper, controller)
|
2022-04-11 05:23:55 +00:00
|
|
|
tasks_manager = TasksManager(helper, controller)
|
2020-08-12 00:36:09 +00:00
|
|
|
tasks_manager.start_webserver()
|
2020-08-17 02:47:53 +00:00
|
|
|
|
2022-05-20 22:53:17 +00:00
|
|
|
def signal_handler(signum, _frame):
|
|
|
|
if not args.daemon:
|
|
|
|
print() # for newline after prompt
|
|
|
|
signame = signal.Signals(signum).name
|
|
|
|
logger.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
|
|
|
|
Console.info(f"Recieved signal {signame} [{signum}], stopping Crafty...")
|
2022-05-15 22:33:31 +00:00
|
|
|
tasks_manager._main_graceful_exit()
|
2022-05-20 22:53:17 +00:00
|
|
|
crafty_prompt.universal_exit()
|
2022-05-15 22:33:31 +00:00
|
|
|
|
2022-05-20 22:53:17 +00:00
|
|
|
signal.signal(signal.SIGTERM, signal_handler)
|
|
|
|
signal.signal(signal.SIGINT, signal_handler)
|
2020-08-12 00:36:09 +00:00
|
|
|
|
2020-08-24 23:16:33 +00:00
|
|
|
# init servers
|
|
|
|
logger.info("Initializing all servers defined")
|
2022-04-12 01:34:46 +00:00
|
|
|
Console.info("Initializing all servers defined")
|
2022-05-26 12:50:20 +00:00
|
|
|
controller.servers.init_all_servers()
|
2020-08-24 23:16:33 +00:00
|
|
|
|
2022-05-15 22:33:31 +00:00
|
|
|
def tasks_starter():
|
|
|
|
# start stats logging
|
|
|
|
tasks_manager.start_stats_recording()
|
2020-08-19 01:04:43 +00:00
|
|
|
|
2022-05-15 22:33:31 +00:00
|
|
|
# once the controller is up and stats are logging, we can kick off
|
|
|
|
# the scheduler officially
|
|
|
|
tasks_manager.start_scheduler()
|
2021-03-22 04:02:18 +00:00
|
|
|
|
2022-05-15 22:33:31 +00:00
|
|
|
# refresh our cache and schedule for every 12 hoursour cache refresh
|
|
|
|
# for serverjars.com
|
|
|
|
tasks_manager.serverjar_cache_refresher()
|
2020-08-23 22:43:28 +00:00
|
|
|
|
2022-05-15 22:33:31 +00:00
|
|
|
tasks_starter_thread = Thread(target=tasks_starter, name="tasks_starter")
|
2021-09-13 21:08:02 +00:00
|
|
|
|
2022-05-15 22:33:31 +00:00
|
|
|
def internet_check():
|
2022-06-13 00:13:05 +00:00
|
|
|
print()
|
2022-05-15 22:33:31 +00:00
|
|
|
logger.info("Checking Internet. This may take a minute.")
|
2022-05-20 22:53:17 +00:00
|
|
|
Console.info("Checking Internet. This may take a minute.")
|
2022-05-15 22:33:31 +00:00
|
|
|
|
|
|
|
if not helper.check_internet():
|
|
|
|
logger.warning(
|
|
|
|
"We have detected the machine running Crafty has no "
|
|
|
|
"connection to the internet. Client connections to "
|
|
|
|
"the server may be limited."
|
|
|
|
)
|
|
|
|
Console.warning(
|
|
|
|
"We have detected the machine running Crafty has no "
|
|
|
|
"connection to the internet. Client connections to "
|
|
|
|
"the server may be limited."
|
|
|
|
)
|
|
|
|
|
|
|
|
internet_check_thread = Thread(target=internet_check, name="internet_check")
|
2021-09-13 17:10:34 +00:00
|
|
|
|
2022-05-20 22:53:17 +00:00
|
|
|
def controller_setup():
|
|
|
|
if not controller.check_system_user():
|
|
|
|
controller.add_system_user()
|
|
|
|
|
2022-10-22 19:12:02 +00:00
|
|
|
if getattr(sys, "frozen", False):
|
2022-10-23 17:48:23 +00:00
|
|
|
application_path = os.path.dirname(sys.executable)
|
|
|
|
running_mode = "Frozen/executable"
|
2022-10-22 19:12:02 +00:00
|
|
|
else:
|
2022-10-23 17:48:23 +00:00
|
|
|
try:
|
|
|
|
app_full_path = os.path.realpath(__file__)
|
|
|
|
application_path = os.path.dirname(app_full_path)
|
|
|
|
running_mode = "Non-interactive (e.g. 'python main.py')"
|
|
|
|
except NameError:
|
|
|
|
application_path = os.getcwd()
|
|
|
|
running_mode = "Interactive"
|
2022-10-22 19:12:02 +00:00
|
|
|
|
|
|
|
controller.set_project_root(application_path)
|
2023-01-27 00:21:39 +00:00
|
|
|
master_server_dir = controller.management.get_master_server_dir()
|
|
|
|
if master_server_dir == "":
|
2023-02-13 19:08:02 +00:00
|
|
|
logger.debug("Could not find master server path. Setting default")
|
2023-01-27 00:21:39 +00:00
|
|
|
controller.set_master_server_dir(
|
|
|
|
os.path.join(controller.project_root, "servers")
|
|
|
|
)
|
|
|
|
else:
|
|
|
|
helper.servers_dir = master_server_dir
|
|
|
|
|
2022-10-23 18:02:24 +00:00
|
|
|
Console.debug(f"Execution Mode: {running_mode}")
|
|
|
|
Console.debug(f"Application path : '{application_path}'")
|
2022-10-23 17:48:23 +00:00
|
|
|
|
2022-05-20 22:53:17 +00:00
|
|
|
controller.clear_support_status()
|
|
|
|
|
2022-06-05 20:02:52 +00:00
|
|
|
crafty_prompt = MainPrompt(
|
|
|
|
helper, tasks_manager, migration_manager, controller, import3
|
|
|
|
)
|
2022-05-20 22:53:17 +00:00
|
|
|
|
|
|
|
controller_setup_thread = Thread(target=controller_setup, name="controller_setup")
|
|
|
|
|
|
|
|
def setup_starter():
|
|
|
|
if not args.daemon:
|
|
|
|
time.sleep(0.01) # Wait for the prompt to start
|
|
|
|
print() # Make a newline after the prompt so logs are on an empty line
|
|
|
|
else:
|
|
|
|
time.sleep(0.01) # Wait for the daemon info message
|
|
|
|
|
|
|
|
Console.info("Setting up Crafty's internal components...")
|
|
|
|
|
|
|
|
# Start the setup threads
|
|
|
|
tasks_starter_thread.start()
|
|
|
|
internet_check_thread.start()
|
|
|
|
controller_setup_thread.start()
|
|
|
|
|
|
|
|
# Wait for the setup threads to finish
|
|
|
|
tasks_starter_thread.join()
|
|
|
|
internet_check_thread.join()
|
|
|
|
controller_setup_thread.join()
|
|
|
|
|
|
|
|
Console.info("Crafty has fully started and is now ready for use!")
|
2022-07-20 22:56:09 +00:00
|
|
|
|
|
|
|
# Check if new version available
|
|
|
|
remote_ver = helper.check_remote_version()
|
|
|
|
if remote_ver:
|
|
|
|
notice = f"""
|
|
|
|
A new version of Crafty is available!
|
|
|
|
{'/' * 37}
|
|
|
|
New version available: {remote_ver}
|
2022-07-20 23:39:29 +00:00
|
|
|
Current version: {pkg_version.parse(helper.get_version_string())}
|
2022-07-20 22:56:09 +00:00
|
|
|
{'/' * 37}
|
|
|
|
"""
|
|
|
|
Console.yellow(notice)
|
|
|
|
|
2022-06-13 00:11:56 +00:00
|
|
|
crafty_prompt.prompt = f"Crafty Controller v{helper.get_version_string()} > "
|
2022-07-06 01:35:43 +00:00
|
|
|
try:
|
|
|
|
logger.info("Removing old temp dirs")
|
|
|
|
FileHelpers.del_dirs(os.path.join(controller.project_root, "temp"))
|
|
|
|
except:
|
|
|
|
logger.info("Did not find old temp dir.")
|
|
|
|
os.mkdir(os.path.join(controller.project_root, "temp"))
|
2021-12-21 01:17:28 +00:00
|
|
|
|
2022-05-20 22:53:17 +00:00
|
|
|
if not args.daemon:
|
|
|
|
# Put the prompt under the cursor
|
|
|
|
crafty_prompt.print_prompt()
|
2021-06-02 18:30:12 +00:00
|
|
|
|
2022-05-20 22:53:17 +00:00
|
|
|
Thread(target=setup_starter, name="setup_starter").start()
|
2022-01-14 04:01:18 +00:00
|
|
|
|
2021-03-06 20:48:02 +00:00
|
|
|
if not args.daemon:
|
2022-05-20 22:53:17 +00:00
|
|
|
# Start the Crafty prompt
|
|
|
|
crafty_prompt.cmdloop()
|
2021-03-06 20:48:02 +00:00
|
|
|
else:
|
2022-05-20 22:53:17 +00:00
|
|
|
Console.info("Crafty started in daemon mode, no shell will be printed")
|
|
|
|
print()
|
2021-03-06 20:48:02 +00:00
|
|
|
while True:
|
2022-05-20 22:53:17 +00:00
|
|
|
if tasks_manager.get_main_thread_run_status():
|
2021-03-06 20:48:02 +00:00
|
|
|
break
|
2022-05-20 22:53:17 +00:00
|
|
|
time.sleep(1)
|
2021-12-09 23:35:00 +00:00
|
|
|
tasks_manager._main_graceful_exit()
|
2022-05-20 22:53:17 +00:00
|
|
|
crafty_prompt.universal_exit()
|