diff --git a/CHANGELOG.md b/CHANGELOG.md index d294c66f..3a4e1917 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,7 +1,7 @@ # Changelog ## --- [4.0.17] - 2022/TBD ### New features -TBD +- Automate forge install process through Crafty server creation for Forge server version 1.16 and greater. ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/495)) ### Bug fixes - Fix no port on bedrock server creation ([Merge Request](https://gitlab.com/crafty-controller/crafty-4/-/merge_requests/493)) ### Tweaks diff --git a/app/classes/controllers/servers_controller.py b/app/classes/controllers/servers_controller.py index b0a9044a..4c97a6c7 100644 --- a/app/classes/controllers/servers_controller.py +++ b/app/classes/controllers/servers_controller.py @@ -119,9 +119,15 @@ class ServersController(metaclass=Singleton): return srv.stats_helper.set_import() @staticmethod - def finish_import(server_id): + def finish_import(server_id, forge=False): srv = ServersController().get_server_instance_by_id(server_id) - return srv.stats_helper.finish_import() + # This is where we start the forge installerr + if forge: + srv.run_threaded_server( + HelperUsers.get_user_id_by_name("system"), forge_install=True + ) + else: + srv.stats_helper.finish_import() @staticmethod def get_import_status(server_id): diff --git a/app/classes/minecraft/serverjars.py b/app/classes/minecraft/serverjars.py index 1ecfc0f1..a656944b 100644 --- a/app/classes/minecraft/serverjars.py +++ b/app/classes/minecraft/serverjars.py @@ -187,31 +187,28 @@ class ServerJars: # open a file stream with requests.get(fetch_url, timeout=2, stream=True) as r: + success = False try: with open(path, "wb") as output: shutil.copyfileobj(r.raw, output) - ServersController.finish_import(server_id) + # If this is the newer forge version we will run the installer + if server == "forge" and int(version.split(".")[1]) > 15: + ServersController.finish_import(server_id, True) + else: + ServersController.finish_import(server_id) - for user in server_users: - self.helper.websocket_helper.broadcast_user( - user, "notification", "Executable download finished" - ) - time.sleep(3) - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) - return True + success = True except Exception as e: logger.error(f"Unable to save jar to {path} due to error:{e}") ServersController.finish_import(server_id) server_users = PermissionsServers.get_server_user_list(server_id) - for user in server_users: - self.helper.websocket_helper.broadcast_user( - user, "notification", "Executable download finished" - ) - time.sleep(3) - self.helper.websocket_helper.broadcast_user( - user, "send_start_reload", {} - ) - return False + for user in server_users: + self.helper.websocket_helper.broadcast_user( + user, "notification", "Executable download finished" + ) + time.sleep(3) + self.helper.websocket_helper.broadcast_user( + user, "send_start_reload", {} + ) + return success diff --git a/app/classes/shared/main_controller.py b/app/classes/shared/main_controller.py index eb8c3219..03c640af 100644 --- a/app/classes/shared/main_controller.py +++ b/app/classes/shared/main_controller.py @@ -484,17 +484,32 @@ class Controller: return False if Helpers.is_os_windows(): - server_command = ( - f"java -Xms{Helpers.float_to_string(min_mem)}M " - f"-Xmx{Helpers.float_to_string(max_mem)}M " - f'-jar "{server_file}" nogui' - ) + # Let's check for and setup for install server commands + if server == "forge": + server_command = ( + f"java -Xms{Helpers.float_to_string(min_mem)}M " + f"-Xmx{Helpers.float_to_string(max_mem)}M " + f'-jar "{server_file}" --installServer' + ) + else: + server_command = ( + f"java -Xms{Helpers.float_to_string(min_mem)}M " + f"-Xmx{Helpers.float_to_string(max_mem)}M " + f'-jar "{server_file}" nogui' + ) else: - server_command = ( - f"java -Xms{Helpers.float_to_string(min_mem)}M " - f"-Xmx{Helpers.float_to_string(max_mem)}M " - f"-jar {server_file} nogui" - ) + if server == "forge": + server_command = ( + f"java -Xms{Helpers.float_to_string(min_mem)}M " + f"-Xmx{Helpers.float_to_string(max_mem)}M " + f"-jar {server_file} --installServer" + ) + else: + server_command = ( + f"java -Xms{Helpers.float_to_string(min_mem)}M " + f"-Xmx{Helpers.float_to_string(max_mem)}M " + f"-jar {server_file} nogui" + ) server_log_file = "./logs/latest.log" server_stop = "stop" diff --git a/app/classes/shared/server.py b/app/classes/shared/server.py index 257bf64f..ccc50f70 100644 --- a/app/classes/shared/server.py +++ b/app/classes/shared/server.py @@ -202,12 +202,15 @@ class ServerInstance: # remove the scheduled job since it's ran return self.server_scheduler.remove_job(str(self.server_id)) - def run_threaded_server(self, user_id): + def run_threaded_server(self, user_id, forge_install=False): # start the server self.server_thread = threading.Thread( target=self.start_server, daemon=True, - args=(user_id,), + args=( + user_id, + forge_install, + ), name=f"{self.server_id}_server_thread", ) self.server_thread.start() @@ -292,13 +295,13 @@ class ServerInstance: logger.critical(f"Unable to write/access {self.server_path}") Console.critical(f"Unable to write/access {self.server_path}") - def start_server(self, user_id): + def start_server(self, user_id, forge_install=False): if not user_id: user_lang = self.helper.get_setting("language") else: user_lang = HelperUsers.get_user_lang_by_id(user_id) - if self.stats_helper.get_import_status(): + if self.stats_helper.get_import_status() and not forge_install: if user_id: self.helper.websocket_helper.broadcast_user( user_id, @@ -341,7 +344,9 @@ class ServerInstance: "eula= true", "eula =true", ] - + # If this is a forge installer we're running we can bypass the eula checks. + if forge_install is True: + e_flag = True if not e_flag and self.settings["type"] == "minecraft-java": if user_id: self.helper.websocket_helper.broadcast_user( @@ -422,6 +427,9 @@ class ServerInstance: ).format(self.name, ex) }, ) + if forge_install: + # Reset import status if failed while forge installing + self.stats_helper.finish_import() return False else: @@ -460,6 +468,9 @@ class ServerInstance: ).format(self.name, ex) }, ) + if forge_install: + # Reset import status if failed while forge installing + self.stats_helper.finish_import() return False out_buf = ServerOutBuf(self.helper, self.process, self.server_id) @@ -540,6 +551,10 @@ class ServerInstance: self.detect_crash, "interval", seconds=30, id=f"c_{self.server_id}" ) + # If this is a forge install we'll call the watcher to do the things + if forge_install: + self.forge_install_watcher() + def check_internet_thread(self, user_id, user_lang): if user_id: if not Helpers.check_internet(): @@ -553,6 +568,78 @@ class ServerInstance: }, ) + def forge_install_watcher(self): + # Enter for install if that parameter is true + while True: + # We'll watch the process + if self.process.poll() is None: + # IF process still has not exited we'll keep looping + time.sleep(5) + Console.debug("Installing Forge...") + else: + # Process has exited. Lets do some work to setup the new + # run command. + # Let's grab the server object we're going to update. + server_obj = HelperServers.get_server_obj(self.server_id) + + # The forge install is done so we can delete that install file. + os.remove(os.path.join(server_obj.path, server_obj.executable)) + + # We need to grab the exact forge version number. + # We know we can find it here in the run.sh/bat script. + run_file_path = "" + if self.helper.is_os_windows(): + run_file_path = os.path.join(server_obj.path, "run.bat") + else: + run_file_path = os.path.join(server_obj.path, "run.sh") + + if Helpers.check_file_perms(run_file_path) and os.path.isfile( + run_file_path + ): + run_file = open(run_file_path, "r", encoding="utf-8") + run_file_text = run_file.read() + else: + Console.error( + "ERROR ! Forge install can't read the scripts files." + " Aborting ..." + ) + return + + # We get the server command parameters from forge script + server_command = re.findall( + r"java @([a-zA-Z0-9_\.]+)" + r" @([a-z.\/\-]+)([0-9.\-]+)\/\b([a-z_0-9]+\.txt)\b( .{2,4})?", + run_file_text, + )[0] + + version = server_command[2] + executable_path = f"{server_command[1]}{server_command[2]}/" + + # Let's set the proper server executable + server_obj.executable = os.path.join( + f"{executable_path}forge-{version}-server.jar" + ) + # Now lets set up the new run command. + # This is based off the run.sh/bat that + # Forge uses in 1.16 and < + execution_command = ( + f"java @{server_command[0]}" + f" @{executable_path}{server_command[3]} nogui {server_command[4]}" + ) + server_obj.execution_command = execution_command + Console.debug("SUCCESS! Forge install completed") + + # We'll update the server with the new information now. + HelperServers.update_server(server_obj) + self.stats_helper.finish_import() + server_users = PermissionsServers.get_server_user_list(self.server_id) + + for user in server_users: + self.helper.websocket_helper.broadcast_user( + user, "send_start_reload", {} + ) + break + def stop_crash_detection(self): # This is only used if the crash detection settings change # while the server is running. diff --git a/app/frontend/templates/panel/dashboard.html b/app/frontend/templates/panel/dashboard.html index c89d00b2..9d91e156 100644 --- a/app/frontend/templates/panel/dashboard.html +++ b/app/frontend/templates/panel/dashboard.html @@ -168,22 +168,11 @@