Skip to content

Commit

Permalink
Add feature to reset the server from a backup when it shuts down
Browse files Browse the repository at this point in the history
  • Loading branch information
Kamesuta committed Mar 26, 2024
1 parent 8d548cf commit 82585b4
Show file tree
Hide file tree
Showing 9 changed files with 294 additions and 67 deletions.
12 changes: 11 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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)

Expand Down Expand Up @@ -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

Expand Down Expand Up @@ -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

Expand Down
12 changes: 11 additions & 1 deletion README_ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,11 @@ https://github.com/Kamesuta/BungeePteroPower/assets/16362824/019fdfc5-f0fc-4532-
## 主な機能

- サーバーに一定時間プレイヤーがいない状態になった場合、PterodactylのAPIを使用して自動的にサーバーを停止します。
- 停止までの時間は、サーバーごとに設定可能です。
- サーバーにプレイヤーが参加した場合に、PterodactylのAPIを使用して自動的にサーバーを起動します。
- 停止までの時間は、サーバーごとに設定可能です。
- 権限設定により、手動起動可能なプレイヤー、及び、入ると自動的に起動が行われるプレイヤーを設定できます。
- サーバー終了時、バックアップからサーバーをリセットすることができます。
- 一度プレイしたらリセットされるミニゲームサーバーなどを作成したい場合に便利です。

## ダウンロード

Expand Down Expand Up @@ -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です。
- この設定を空、又は削除すると、サーバー停止時にバックアップからのリストアは行われません。
- 各ゲームの後にリセットする必要があるサーバーに便利です。

### パーミッション設定

Expand Down Expand Up @@ -189,9 +197,11 @@ BungeePteroPowerは、Pterodactyl以外をサポートするためのパワー
- サーバーをローカルで起動できるもの
- またはPterodactyl以外の管理ソフトウェアと互換性のあるもの。
例えば以下のようなものをサポートしたいです
- PufferPanel
- Minecraft Server Manager
- MCSManager
- MC Server Soft
- AMP

### アドオンを作成する

Expand Down
67 changes: 45 additions & 22 deletions src/main/java/com/kamesuta/bungeepteropower/Config.java
Original file line number Diff line number Diff line change
Expand Up @@ -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")
Expand Down Expand Up @@ -81,13 +89,35 @@ public class Config {
*/
public final String pterodactylApiKey;
/**
* Pterodactyl server ID
* Per-server configuration
*/
private final Map<String, String> serverIdMap;
private final Map<String, ServerConfig> serverMap;

/**
* The time in seconds to stop the server after the last player leaves.
* Per-server configuration
*/
private final Map<String, Integer> 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
Expand All @@ -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);

Expand All @@ -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) {
Expand All @@ -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);
}

/**
Expand All @@ -162,7 +185,7 @@ public int getServerTimeout(String serverName) {
* @return The Bungeecord server names
*/
public Set<String> getServerNames() {
return serverIdMap.keySet();
return serverMap.keySet();
}

private static File makeConfig() throws IOException {
Expand Down
12 changes: 6 additions & 6 deletions src/main/java/com/kamesuta/bungeepteropower/PlayerListener.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}

}
8 changes: 4 additions & 4 deletions src/main/java/com/kamesuta/bungeepteropower/PteroCommand.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand All @@ -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) {
Expand Down Expand Up @@ -147,7 +147,7 @@ public Iterable<String> 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());
}

Expand Down
32 changes: 22 additions & 10 deletions src/main/java/com/kamesuta/bungeepteropower/ServerController.java
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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<Void> 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));
Expand Down Expand Up @@ -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));
Expand All @@ -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
Expand All @@ -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);
Expand All @@ -125,7 +137,7 @@ public static void stopAfterWhile(CommandSender sender, String serverName, Strin
*/
private static CompletableFuture<Void> onceStarted(ServerInfo serverInfo) {
CompletableFuture<Void> future = new CompletableFuture<Void>().orTimeout(plugin.config.startupJoinTimeout, TimeUnit.SECONDS);
Callback<ServerPing> callback = new Callback<ServerPing>() {
Callback<ServerPing> callback = new Callback<>() {
@Override
public void done(ServerPing serverPing, Throwable throwable) {
// Do nothing if timeout or already completed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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<Void> 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<Void> sendRestoreSignal(String serverName, String serverId, String backupName);
}
Loading

0 comments on commit 82585b4

Please sign in to comment.