From 82585b41825b9b8d5343b64e73345fd1ebe8cb48 Mon Sep 17 00:00:00 2001 From: Kamesuta Date: Wed, 27 Mar 2024 04:46:13 +0900 Subject: [PATCH] Add feature to reset the server from a backup when it shuts down --- README.md | 12 +- README_ja.md | 12 +- .../com/kamesuta/bungeepteropower/Config.java | 67 ++++--- .../bungeepteropower/PlayerListener.java | 12 +- .../bungeepteropower/PteroCommand.java | 8 +- .../bungeepteropower/ServerController.java | 32 ++- .../bungeepteropower/api/PowerController.java | 16 +- .../power/PterodactylController.java | 182 ++++++++++++++++-- src/main/resources/config.yml | 20 +- 9 files changed, 294 insertions(+), 67 deletions(-) diff --git a/README.md b/README.md index 6330b32..1917080 100644 --- a/README.md +++ b/README.md @@ -15,9 +15,11 @@ https://github.com/Kamesuta/BungeePteroPower/assets/16362824/019fdfc5-f0fc-4532- ## Key Features - Automatically stops servers using Pterodactyl's API when there are no players on the server for a certain period of time. + - The time until shutdown can be configured for each server. - Automatically starts servers using Pterodactyl's API when players join the server. -- The time until shutdown can be configured for each server. - Permissions settings allow for specifying players who can manually start servers and players for whom automatic startup is enabled upon joining. +- You can reset the server from a backup when it shuts down. + - This is useful when creating mini-game servers that reset once played. ![Overview](https://github.com/Kamesuta/BungeePteroPower/assets/16362824/3cece79e-b41a-4119-a6cd-4800dd4f705d) @@ -152,8 +154,14 @@ The `config.yml` file includes the following settings, but not all items need to - This is useful to wait for plugins like Luckperms to fully load - If you set it to 0, the player will be connected as soon as the server is pingable - `pingInterval`: Set the interval for checking the server's status. +- `restoreOnStop`: Configure settings for the feature to reset the server from a backup when it is stopped. + - `timeout`: Set the maximum waiting time after sending the stop signal for the server to stop. (The restore will be performed after the server stops) + - `pingInterval`: Set the interval for checking if the server is offline after sending the stop signal. - `servers`: Configure settings for each server. Set the server ID and the time until automatic shutdown. - `timeout`: When there are no players on the server, it will stop after a certain period. The unit is seconds. + - `backupId`: The UUID of the backup to restore when the server stops. + - If this setting is empty or removed, no restore from backup will be performed when the server stops. + - Useful for servers that need to be reset after each game. ### Permission Settings @@ -190,9 +198,11 @@ Ideally, we would like to support the following: - Power controllers that can start servers locally - Power controllers compatible with management software other than Pterodactyl. For example, we would like to support the following: + - PufferPanel - Minecraft Server Manager - MCSManager - MC Server Soft + - AMP ### Creating Add-ons diff --git a/README_ja.md b/README_ja.md index 1b082d3..c2ba846 100644 --- a/README_ja.md +++ b/README_ja.md @@ -15,9 +15,11 @@ https://github.com/Kamesuta/BungeePteroPower/assets/16362824/019fdfc5-f0fc-4532- ## 主な機能 - サーバーに一定時間プレイヤーがいない状態になった場合、PterodactylのAPIを使用して自動的にサーバーを停止します。 + - 停止までの時間は、サーバーごとに設定可能です。 - サーバーにプレイヤーが参加した場合に、PterodactylのAPIを使用して自動的にサーバーを起動します。 -- 停止までの時間は、サーバーごとに設定可能です。 - 権限設定により、手動起動可能なプレイヤー、及び、入ると自動的に起動が行われるプレイヤーを設定できます。 +- サーバー終了時、バックアップからサーバーをリセットすることができます。 + - 一度プレイしたらリセットされるミニゲームサーバーなどを作成したい場合に便利です。 ## ダウンロード @@ -151,8 +153,14 @@ https://github.com/Kamesuta/BungeePteroPower/assets/16362824/019fdfc5-f0fc-4532- - この遅延はLuckpermsなどのプラグインが完全に読み込まれるのを待つために役立ちます。 - 0に設定すると、サーバーがping可能になるとすぐにプレイヤーが接続されます。 - `pingInterval`: サーバーのステータスをチェックする間隔を設定します。 +- `restoreOnStop`: サーバーを停止したときにバックアップからサーバーをリセットする機能の設定を行います。 + - `timeout`: 停止シグナルを送信した後、サーバーが停止するまでの最大待機時間を設定します。(リストアはサーバーが停止した後に行われます) + - `pingInterval`: 停止シグナルを送信した後、サーバーがオフラインかどうかを確認する間隔を設定します。 - `servers`: サーバーごとの設定を行います。サーバーIDと自動停止までの時間を設定します。 - `timeout`: サーバーからプレイヤーがいなくなった際、一定時間プレイヤーがいない場合にサーバーを停止します。単位は秒です。 + - `backupId`: サーバーが停止したときに復元するバックアップのUUIDです。 + - この設定を空、又は削除すると、サーバー停止時にバックアップからのリストアは行われません。 + - 各ゲームの後にリセットする必要があるサーバーに便利です。 ### パーミッション設定 @@ -189,9 +197,11 @@ BungeePteroPowerは、Pterodactyl以外をサポートするためのパワー - サーバーをローカルで起動できるもの - またはPterodactyl以外の管理ソフトウェアと互換性のあるもの。 例えば以下のようなものをサポートしたいです + - PufferPanel - Minecraft Server Manager - MCSManager - MC Server Soft + - AMP ### アドオンを作成する diff --git a/src/main/java/com/kamesuta/bungeepteropower/Config.java b/src/main/java/com/kamesuta/bungeepteropower/Config.java index 326eff9..c42f5b6 100644 --- a/src/main/java/com/kamesuta/bungeepteropower/Config.java +++ b/src/main/java/com/kamesuta/bungeepteropower/Config.java @@ -48,6 +48,14 @@ public class Config { * the server will be stopped after this time has elapsed according to the timeout setting. */ public final int startTimeout; + /** + * The number of seconds to wait for the server to stop after sending the stop signal + */ + public final int restoreTimeout; + /** + * The interval in seconds to check if the server has stopped while waiting for the server to stop after sending the server stop signal + */ + public final int restorePingInterval; /** * The type of the power controller * (e.g. "pterodactyl") @@ -81,13 +89,35 @@ public class Config { */ public final String pterodactylApiKey; /** - * Pterodactyl server ID + * Per-server configuration */ - private final Map serverIdMap; + private final Map serverMap; + /** - * The time in seconds to stop the server after the last player leaves. + * Per-server configuration */ - private final Map serverTimeoutMap; + public static class ServerConfig { + /** + * The server ID in the Pterodactyl panel + */ + public final String id; + /** + * The number of seconds the plugin will try to connect the player to the desired server + * Set this to the maximum time the server can take to start + */ + public final int timeout; + /** + * The backup server ID in the Pterodactyl panel + * If this is set, the server will be deleted and restored from the backup after stopping + */ + public final @Nullable String backupId; + + public ServerConfig(String id, int timeout, String backupId) { + this.id = id; + this.timeout = timeout; + this.backupId = backupId; + } + } public Config() { // Create/Load config.yml @@ -108,6 +138,8 @@ public Config() { this.checkUpdate = configuration.getBoolean("checkUpdate", true); this.language = configuration.getString("language"); this.startTimeout = configuration.getInt("startTimeout"); + this.restoreTimeout = configuration.getInt("restoreOnStop.timeout", 120); + this.restorePingInterval = configuration.getInt("restoreOnStop.pingInterval", 5); this.powerControllerType = configuration.getString("powerControllerType"); this.useSynchronousPing = configuration.getBoolean("useSynchronousPing", false); @@ -121,13 +153,14 @@ public Config() { this.pterodactylApiKey = configuration.getString("pterodactyl.apiKey"); // Bungeecord server name -> Pterodactyl server ID list - serverIdMap = new HashMap<>(); - serverTimeoutMap = new HashMap<>(); + serverMap = new HashMap<>(); Configuration servers = configuration.getSection("servers"); for (String serverId : servers.getKeys()) { Configuration section = servers.getSection(serverId); - serverIdMap.put(serverId, section.getString("id")); - serverTimeoutMap.put(serverId, section.getInt("timeout")); + String id = section.getString("id"); + int timeout = section.getInt("timeout"); + String backupId = section.getString("backupId", null); + serverMap.put(serverId, new ServerConfig(id, timeout, backupId)); } } catch (Exception e) { @@ -137,23 +170,13 @@ public Config() { } /** - * Get the Pterodactyl server ID from the Bungeecord server name. + * Get per-server configuration from the Bungeecord server name. * * @param serverName The Bungeecord server name * @return The Pterodactyl server ID */ - public @Nullable String getServerId(String serverName) { - return serverIdMap.get(serverName); - } - - /** - * Get auto stop time from the Bungeecord server name. - * - * @param serverName The Bungeecord server name - * @return The auto stop time - */ - public int getServerTimeout(String serverName) { - return serverTimeoutMap.getOrDefault(serverName, 0); + public @Nullable ServerConfig getServerConfig(String serverName) { + return serverMap.get(serverName); } /** @@ -162,7 +185,7 @@ public int getServerTimeout(String serverName) { * @return The Bungeecord server names */ public Set getServerNames() { - return serverIdMap.keySet(); + return serverMap.keySet(); } private static File makeConfig() throws IOException { diff --git a/src/main/java/com/kamesuta/bungeepteropower/PlayerListener.java b/src/main/java/com/kamesuta/bungeepteropower/PlayerListener.java index 86abc02..f64ea87 100644 --- a/src/main/java/com/kamesuta/bungeepteropower/PlayerListener.java +++ b/src/main/java/com/kamesuta/bungeepteropower/PlayerListener.java @@ -79,8 +79,8 @@ public void onServerConnect(ServerConnectEvent event) { } // Get the Pterodactyl server ID - String serverId = plugin.config.getServerId(serverName); - if (serverId == null) { + Config.ServerConfig server = plugin.config.getServerConfig(serverName); + if (server == null) { return; } @@ -110,7 +110,7 @@ public void onServerConnect(ServerConnectEvent event) { } // Send power signal - ServerController.sendPowerSignal(player, serverName, serverId, PowerSignal.START); + ServerController.sendPowerSignal(player, serverName, server, PowerSignal.START); // Record statistics plugin.statistics.actionCounter.increment(Statistics.ActionCounter.ActionType.START_SERVER_AUTOJOIN); @@ -196,13 +196,13 @@ private void onPlayerQuit(ProxiedPlayer player, ServerInfo targetServer) { // Get the auto stop time String serverName = targetServer.getName(); // Get the Pterodactyl server ID - String serverId = plugin.config.getServerId(serverName); - if (serverId == null) { + Config.ServerConfig server = plugin.config.getServerConfig(serverName); + if (server == null) { return; } // Stop the server when everyone leaves - ServerController.stopAfterWhile(player, serverName, serverId, PowerSignal.STOP); + ServerController.stopAfterWhile(player, serverName, server, PowerSignal.STOP); } } diff --git a/src/main/java/com/kamesuta/bungeepteropower/PteroCommand.java b/src/main/java/com/kamesuta/bungeepteropower/PteroCommand.java index 603b1e7..30517e7 100644 --- a/src/main/java/com/kamesuta/bungeepteropower/PteroCommand.java +++ b/src/main/java/com/kamesuta/bungeepteropower/PteroCommand.java @@ -74,8 +74,8 @@ public void execute(CommandSender sender, String[] args) { } // Stop server - String serverId = plugin.config.getServerId(serverName); - if (serverId == null) { + Config.ServerConfig server = plugin.config.getServerConfig(serverName); + if (server == null) { sender.sendMessage(plugin.messages.error("command_server_not_configured", serverName)); return; } @@ -91,7 +91,7 @@ public void execute(CommandSender sender, String[] args) { } // Send signal and auto join - ServerController.sendPowerSignal(sender, serverName, serverId, signal); + ServerController.sendPowerSignal(sender, serverName, server, signal); // Record statistics if (signal == PowerSignal.START) { @@ -147,7 +147,7 @@ public Iterable onTabComplete(CommandSender sender, String[] args) { return plugin.config.getServerNames().stream() .filter(name -> name.startsWith(args[1])) .filter(name -> sender.hasPermission("ptero." + subCommand + "." + name)) - .filter(name -> plugin.config.getServerId(name) != null) + .filter(name -> plugin.config.getServerConfig(name) != null) .collect(Collectors.toList()); } diff --git a/src/main/java/com/kamesuta/bungeepteropower/ServerController.java b/src/main/java/com/kamesuta/bungeepteropower/ServerController.java index 90746fd..13d2dc9 100644 --- a/src/main/java/com/kamesuta/bungeepteropower/ServerController.java +++ b/src/main/java/com/kamesuta/bungeepteropower/ServerController.java @@ -1,5 +1,6 @@ package com.kamesuta.bungeepteropower; +import com.kamesuta.bungeepteropower.api.PowerController; import com.kamesuta.bungeepteropower.api.PowerSignal; import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.CommandSender; @@ -22,15 +23,26 @@ public class ServerController { * * @param sender The command sender * @param serverName The name of the server to send the signal - * @param serverId The server ID to send the signal to + * @param server The server configuration to send the signal to * @param signalType The power signal to send */ - public static void sendPowerSignal(CommandSender sender, String serverName, String serverId, PowerSignal signalType) { + public static void sendPowerSignal(CommandSender sender, String serverName, Config.ServerConfig server, PowerSignal signalType) { // Get signal String signal = signalType.getSignal(); - // Send signal - plugin.config.getPowerController().sendPowerSignal(serverName, serverId, signalType).thenRun(() -> { + // Send power signal + CompletableFuture future; + PowerController powerController = plugin.config.getPowerController(); + if (signalType == PowerSignal.STOP && server.backupId != null && !server.backupId.isEmpty()) { + // Restore from backup if the backup ID is specified + future = powerController.sendRestoreSignal(serverName, server.id, server.backupId); + } else { + // Otherwise, send power signal + future = powerController.sendPowerSignal(serverName, server.id, signalType); + } + + // After the power signal is sent + future.thenRun(() -> { if (signalType == PowerSignal.STOP) { // When stopping the server sender.sendMessage(plugin.messages.success("server_stop", serverName)); @@ -73,7 +85,7 @@ public static void sendPowerSignal(CommandSender sender, String serverName, Stri } // Stop the server if nobody joins after a while - stopAfterWhile(sender, serverName, serverId, signalType); + stopAfterWhile(sender, serverName, server, signalType); }).exceptionally(e -> { sender.sendMessage(plugin.messages.error("server_" + signal + "_failed", serverName)); @@ -87,15 +99,15 @@ public static void sendPowerSignal(CommandSender sender, String serverName, Stri * * @param sender The command sender * @param serverName The name of the server to stop - * @param serverId The server ID to stop + * @param server The server configuration to stop * @param signalType Is this executed while stopping or starting? */ - public static void stopAfterWhile(CommandSender sender, String serverName, String serverId, PowerSignal signalType) { + public static void stopAfterWhile(CommandSender sender, String serverName, Config.ServerConfig server, PowerSignal signalType) { // Get signal String signal = signalType.getSignal(); // Get the auto stop time - int serverTimeout = plugin.config.getServerTimeout(serverName); + int serverTimeout = server.timeout; if (serverTimeout == 0) return; // When on starting, use the start timeout additionally @@ -106,7 +118,7 @@ public static void stopAfterWhile(CommandSender sender, String serverName, Strin // Stop the server after a while plugin.delay.stopAfterWhile(serverName, serverTimeout, () -> { // Stop the server - sendPowerSignal(sender, serverName, serverId, PowerSignal.STOP); + sendPowerSignal(sender, serverName, server, PowerSignal.STOP); // Record statistics plugin.statistics.actionCounter.increment(Statistics.ActionCounter.ActionType.STOP_SERVER_NOBODY); @@ -125,7 +137,7 @@ public static void stopAfterWhile(CommandSender sender, String serverName, Strin */ private static CompletableFuture onceStarted(ServerInfo serverInfo) { CompletableFuture future = new CompletableFuture().orTimeout(plugin.config.startupJoinTimeout, TimeUnit.SECONDS); - Callback callback = new Callback() { + Callback callback = new Callback<>() { @Override public void done(ServerPing serverPing, Throwable throwable) { // Do nothing if timeout or already completed diff --git a/src/main/java/com/kamesuta/bungeepteropower/api/PowerController.java b/src/main/java/com/kamesuta/bungeepteropower/api/PowerController.java index 95088ae..858dd1d 100644 --- a/src/main/java/com/kamesuta/bungeepteropower/api/PowerController.java +++ b/src/main/java/com/kamesuta/bungeepteropower/api/PowerController.java @@ -14,10 +14,20 @@ public interface PowerController { /** * Send a power signal to the Pterodactyl server. * - * @param serverName The name of the server to start - * @param serverId The server ID to send the signal to - * @param signalType The power signal to send + * @param serverName The name of the server to start + * @param serverId The server ID to send the signal to + * @param signalType The power signal to send * @return A future that completes when the request is finished */ CompletableFuture sendPowerSignal(String serverName, String serverId, PowerSignal signalType); + + /** + * Restore from a backup. + * + * @param serverName The name of the server to restore + * @param serverId The server ID to restore + * @param backupName The name of the backup to restore + * @return A future that completes when the request is finished + */ + CompletableFuture sendRestoreSignal(String serverName, String serverId, String backupName); } diff --git a/src/main/java/com/kamesuta/bungeepteropower/power/PterodactylController.java b/src/main/java/com/kamesuta/bungeepteropower/power/PterodactylController.java index 715d401..c3fd38f 100644 --- a/src/main/java/com/kamesuta/bungeepteropower/power/PterodactylController.java +++ b/src/main/java/com/kamesuta/bungeepteropower/power/PterodactylController.java @@ -1,5 +1,7 @@ package com.kamesuta.bungeepteropower.power; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import com.kamesuta.bungeepteropower.api.PowerController; import com.kamesuta.bungeepteropower.api.PowerSignal; @@ -8,6 +10,9 @@ import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CompletionException; +import java.util.concurrent.TimeUnit; +import java.util.function.Consumer; import java.util.logging.Level; import static com.kamesuta.bungeepteropower.BungeePteroPower.logger; @@ -20,23 +25,23 @@ public class PterodactylController implements PowerController { /** * Send a power signal to the Pterodactyl server. * - * @param serverName The name of the server to start - * @param pterodactylServerId The Pterodactyl server ID - * @param signal The power signal to send + * @param serverName The name of the server to start + * @param serverId The Pterodactyl server ID + * @param signalType The power signal to send * @return A future that completes when the request is finished */ @Override - public CompletableFuture sendPowerSignal(String serverName, String pterodactylServerId, PowerSignal signalType) { + public CompletableFuture sendPowerSignal(String serverName, String serverId, PowerSignal signalType) { String signal = signalType.getSignal(); String doing = signalType == PowerSignal.START ? "Starting" : "Stopping"; - logger.info(String.format("%s server: %s (Pterodactyl server ID: %s)", doing, serverName, pterodactylServerId)); + logger.info(String.format("%s server: %s (Pterodactyl server ID: %s)", doing, serverName, serverId)); // Create a path - String path = "/api/client/servers/" + pterodactylServerId + "/power"; + String path = "/api/client/servers/" + serverId + "/power"; HttpClient client = HttpClient.newHttpClient(); - // Create a form body to send power signal + // Create a JSON body to send power signal String jsonBody = "{\"signal\": \"" + signal + "\"}"; // Create a request @@ -48,26 +53,167 @@ public CompletableFuture sendPowerSignal(String serverName, String pteroda .build(); // Execute request and register a callback - CompletableFuture future = new CompletableFuture<>(); - client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) - .thenAccept(status -> { + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(status -> { int code = status.statusCode(); - if (code >= 200 && code < 300) { - logger.info("Successfully " + signal + " server: " + serverName); - future.complete(null); + if (code == 204) { + logger.info("Successfully sent " + signal + " signal to the server: " + serverName); + return (Void) null; } else { - String message = "Failed to " + signal + " server: " + serverName + ". Response code: " + code; + String message = "Failed to send " + signal + " signal to the server: " + serverName + ". Response code: " + code; logger.warning(message); logger.info("Request: " + request + ", Response: " + code + " " + status.body()); - future.completeExceptionally(new RuntimeException(message)); + throw new RuntimeException(message); } }) .exceptionally(e -> { - logger.log(Level.WARNING, "Failed to " + signal + " server: " + serverName, e); - future.completeExceptionally(e); - return null; + logger.log(Level.WARNING, "Failed to send " + signal + " signal to the server: " + serverName, e); + throw new CompletionException(e); }); + } + + /** + * Restore from a backup. + * Send a stop signal to the server, wait until the server is offline, and then restore from a backup. + * + * @param serverName The name of the server + * @param serverId The Pterodactyl server ID + * @param backupName The name of the backup + * @return A future that completes when the request to restore from the backup is sent after the server becomes offline + */ + @Override + public CompletableFuture sendRestoreSignal(String serverName, String serverId, String backupName) { + // First, stop the server + sendPowerSignal(serverName, serverId, PowerSignal.STOP); + + // Wait until the power status becomes offline + logger.info(String.format("Waiting server to stop: %s (Pterodactyl server ID: %s)", serverName, serverId)); + return waitUntilOffline(serverName, serverId) + .thenCompose((v) -> { + // Restore the backup + logger.info(String.format("Successfully stopped server: %s", serverName)); + return restoreBackup(serverName, serverId, backupName); + }); + } + + /** + * Restore from a backup. + * + * @param serverName The name of the server + * @param serverId The Pterodactyl server ID + * @param backupUuid The UUID of the backup + * @return A future that completes when the request is finished + */ + private CompletableFuture restoreBackup(String serverName, String serverId, String backupUuid) { + logger.info(String.format("Restoring from backup: %s to server: %s (Pterodactyl server ID: %s)", backupUuid, serverName, serverId)); + + // Create a path + String path = "/api/client/servers/" + serverId + "/backups/" + backupUuid + "/restore"; + + HttpClient client = HttpClient.newHttpClient(); + + // Create a JSON body to delete all files + String jsonBody = "{\"truncate\":true}"; + + // Create a request + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(plugin.config.pterodactylUrl.resolve(path).toString())) + .header("Content-Type", "application/json") + .header("Authorization", "Bearer " + plugin.config.pterodactylApiKey) + .POST(HttpRequest.BodyPublishers.ofString(jsonBody)) + .build(); + + // Execute request and register a callback + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(status -> { + int code = status.statusCode(); + if (code == 204) { + logger.info("Successfully restored backup: " + backupUuid + " to server: " + serverName); + return (Void) null; + } else { + String message = "Failed to restore backup: " + backupUuid + " to server: " + serverName + ". Response code: " + code; + logger.warning(message); + logger.info("Request: " + request + ", Response: " + code + " " + status.body()); + throw new RuntimeException(message); + } + }) + .exceptionally(e -> { + logger.log(Level.WARNING, "Failed to restore backup: " + backupUuid + " to server: " + serverName, e); + throw new CompletionException(e); + }); + } + + /** + * Wait until the power status becomes offline. + * + * @param serverName The name of the server + * @param serverId The Pterodactyl server ID + * @return A future that waits until the server becomes offline + */ + private CompletableFuture waitUntilOffline(String serverName, String serverId) { + CompletableFuture future = new CompletableFuture().orTimeout(plugin.config.restoreTimeout, TimeUnit.SECONDS); + // Wait until the server becomes offline + Consumer callback = new Consumer<>() { + @Override + public void accept(String powerStatus) { + // Do nothing if timeout or already completed + if (future.isDone()) { + return; + } + // Complete if the server is offline + if (powerStatus.equals("offline")) { + future.complete(null); + return; + } + // Otherwise schedule another ping + logger.fine("Server is still " + powerStatus + ". Waiting for it to be offline: " + serverName); + plugin.getProxy().getScheduler().schedule(plugin, () -> getPowerStatus(serverName, serverId).thenAccept(this), plugin.config.restorePingInterval, TimeUnit.SECONDS); + } + }; + // Initial check + getPowerStatus(serverName, serverId).thenAccept(callback); return future; } + + /** + * Get the power status of the server. + * + * @param serverName The name of the server + * @param serverId The Pterodactyl server ID + * @return A future that completes with the power status + */ + private CompletableFuture getPowerStatus(String serverName, String serverId) { + // Create a path + String path = "/api/client/servers/" + serverId + "/resources"; + + HttpClient client = HttpClient.newHttpClient(); + + // Create a request + HttpRequest request = HttpRequest.newBuilder() + .uri(URI.create(plugin.config.pterodactylUrl.resolve(path).toString())) + .header("Authorization", "Bearer " + plugin.config.pterodactylApiKey) + .GET() + .build(); + + // Execute request and register a callback + return client.sendAsync(request, HttpResponse.BodyHandlers.ofString()) + .thenApply(status -> { + int code = status.statusCode(); + if (code == 200) { + // Parse JSON (attributes.current_state) + JsonObject root = JsonParser.parseString(status.body()).getAsJsonObject(); + return root.getAsJsonObject("attributes").get("current_state").getAsString(); + } else { + String message = "Failed to get power status of server: " + serverName + ". Response code: " + code; + logger.warning(message); + logger.info("Request: " + request + ", Response: " + code + " " + status.body()); + throw new RuntimeException(message); + } + }) + .exceptionally(e -> { + logger.log(Level.WARNING, "Failed to get power status of server: " + serverName, e); + throw new CompletionException(e); + }); + } } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index bbc9541..8eb1132 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -5,7 +5,7 @@ # Version of the configuration file # Do not edit this value until the "/ptero check" asks you to do so. -version: 1 +version: 2 # Check for updates # If true, the plugin will check for updates on startup. @@ -32,6 +32,14 @@ powerControllerType: pterodactyl # The default value is `false`. Enabling this can be useful if you want to set servers (such as lobby servers) to a suspended state in BungeePteroPower immediately after login. useSynchronousPing: false +# Configure settings for the feature to reset the server from a backup when it is stopped +restoreOnStop: + # Set the maximum waiting time after sending the stop signal for the server to stop. (The restore will be performed after the server stops) + timeout: 120 + + # Set the interval for checking if the server is offline after sending the stop signal. + pingInterval: 5 + # This is used to check the server status to transfer players after the server starts startupJoin: # The number of seconds the plugin will try to connect the player to the desired server @@ -40,7 +48,7 @@ startupJoin: timeout: 60 # Once the server is pingable, wait the specified amount of seconds before sending the player to the server - # This is useful to wait for plugins like Luckperms to fully load + # This is useful to wait for plugins like LuckPerms to fully load # If you set it to 0, the player will be connected as soon as the server is pingable joinDelay: 5 @@ -71,3 +79,11 @@ servers: hub: id: abcd1234 timeout: -1 + + minigame: + id: 1a2b3c4d + timeout: 30 + # The UUID of the backup to restore when the server stops. + # If this setting is empty or removed, no restore from backup will be performed when the server stops. + # Useful for servers that need to be reset after each game. + backupId: 00000000-0000-0000-0000-000000000000 \ No newline at end of file