Compare commits

..

3 commits

Author SHA1 Message Date
ec59b3d293
Create a directory for docs
All checks were successful
/ build (push) Successful in 1m6s
since it's getting crowded
2025-01-01 15:37:43 +01:00
c8d394c31e
Add killswitch feature 2025-01-01 15:37:05 +01:00
d03f8e35fb
Move cache where it should be 2025-01-01 14:52:36 +01:00
11 changed files with 217 additions and 7 deletions

32
docs/KILLSWITCH.md Normal file
View file

@ -0,0 +1,32 @@
Killswitch immediately stops the server.
### Warning
This terminates the server process, meaning it's like you'd pull the power. \
So you will lose some progress (since the last auto save), or worst case your world (or other data) gets corrupted.
Terminal froze after kill? `reset`
### Over a command
No key is required. \
`/servkill` is the command. Permission: `tweaks724.servkill`. \
You must grant the permission manually, like with a permission plugin - it's not automatically assigned even to OPs.
### Over HTTP
HTTP is insecure, meaning others *could* intercept your request to the server and get your key. \
To encrypt, put this behind a proxy to get HTTPS or a VPN (directly to the server, not a commercial VPN) \
Or regenerate the key after every usage.
Make a GET request to `/key/<base64 encoded key>`
Example:
```
https://127.0.0.1:57932/key/lNwANMSZhLiTWhNxSoqQ5Q==
|_ server address _| |_ base64 encoded key _|
```
The response is a 404 no matter what. Either way, you will notice that the server has stopped.
The key is generated to `plugins/Tweaks724/storage/killswitch key` \
To use it with HTTP server, encode it to base64.
Rate limit is 1 request / 5 minutes

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
@ -57,7 +57,10 @@ public record TweaksConfig(
boolean redstoneEnabled, boolean redstoneEnabled,
String redstoneListen, String redstoneListen,
Map<String, Object> knockbackModifiers Map<String, Object> knockbackModifiers,
boolean killswitchEnabled,
String killswitchListen
) { ) {
public static final int CONFIG_VERSION = 2; public static final int CONFIG_VERSION = 2;
private static TweaksConfig config; private static TweaksConfig config;
@ -129,6 +132,9 @@ public record TweaksConfig(
// this is processed when initing // this is processed when initing
Map<String, Object> knockbackModifiers = config.getConfigurationSection("knockback").getValues(false); Map<String, Object> knockbackModifiers = config.getConfigurationSection("knockback").getValues(false);
boolean killswitchEnabled = config.getBoolean("killswitch.enabled");
String killswitchListen = config.getString("killswitch.listen");
TweaksConfig.config = new TweaksConfig( TweaksConfig.config = new TweaksConfig(
debug, metrics, locale, debug, metrics, locale,
worldborderExpand, worldborderHide, worldborderExpand, worldborderHide,
@ -143,7 +149,8 @@ public record TweaksConfig(
sleepEnabled, sleepInstant, sleepEnabled, sleepInstant,
authEnabled, authForce, authHostname, authEnabled, authForce, authHostname,
redstoneEnabled, redstoneListen, redstoneEnabled, redstoneListen,
knockbackModifiers knockbackModifiers,
killswitchEnabled, killswitchListen
); );
return TweaksConfig.config; return TweaksConfig.config;

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
@ -14,6 +14,7 @@ import eu.m724.tweaks.door.DoorKnockListener;
import eu.m724.tweaks.door.DoorOpenListener; import eu.m724.tweaks.door.DoorOpenListener;
import eu.m724.tweaks.full.FullListener; import eu.m724.tweaks.full.FullListener;
import eu.m724.tweaks.hardcore.HardcoreManager; import eu.m724.tweaks.hardcore.HardcoreManager;
import eu.m724.tweaks.killswitch.KillswitchManager;
import eu.m724.tweaks.knockback.KnockbackListener; import eu.m724.tweaks.knockback.KnockbackListener;
import eu.m724.tweaks.motd.MotdManager; import eu.m724.tweaks.motd.MotdManager;
import eu.m724.tweaks.ping.F3NameListener; import eu.m724.tweaks.ping.F3NameListener;
@ -129,6 +130,11 @@ public class TweaksPlugin extends MStatsPlugin {
DebugLogger.fine("Enabling Knockback"); DebugLogger.fine("Enabling Knockback");
new KnockbackListener(this); new KnockbackListener(this);
if (config.killswitchEnabled()) {
DebugLogger.fine("Enabling Killswitch");
new KillswitchManager(this).init(getCommand("servkill"));
}
/* end modules */ /* end modules */
if (config.metrics()) { if (config.metrics()) {

View file

@ -0,0 +1,125 @@
/*
* Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text.
*/
package eu.m724.tweaks.killswitch;
import com.sun.net.httpserver.HttpExchange;
import com.sun.net.httpserver.HttpHandler;
import com.sun.net.httpserver.HttpServer;
import eu.m724.tweaks.DebugLogger;
import eu.m724.tweaks.TweaksConfig;
import eu.m724.tweaks.TweaksPlugin;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.file.Files;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
public class KillswitchManager implements CommandExecutor, HttpHandler {
private final Plugin plugin;
private final Ratelimit ratelimit = new Ratelimit();
private byte[] secret;
private String secretEncoded;
public KillswitchManager(Plugin plugin) {
this.plugin = plugin;
}
private void loadKey(File file) {
if (file.exists()) {
try {
this.secret = Files.readAllBytes(file.toPath());
} catch (IOException e) {
throw new RuntimeException("Reading killswitch key", e);
}
DebugLogger.fine("Loaded key");
} else {
byte[] buf = new byte[16];
try {
SecureRandom.getInstanceStrong().nextBytes(buf);
Files.write(file.toPath(), buf);
} catch (IOException | NoSuchAlgorithmException e) {
throw new RuntimeException("Generating killswitch key", e);
}
this.secret = buf;
DebugLogger.info("Killswitch key generated and saved to " + file.getPath());
}
this.secretEncoded = Base64.getEncoder().encodeToString(secret);
}
public void init(PluginCommand serverKillCommand) {
serverKillCommand.setExecutor(this);
if (TweaksConfig.getConfig().killswitchListen() != null) {
loadKey(new File(plugin.getDataFolder(), "storage/killswitch key"));
ratelimit.runTaskTimerAsynchronously(plugin, 0, 20 * 300);
var listenAddress = TweaksConfig.getConfig().killswitchListen().split(":");
InetSocketAddress bindAddress;
if (listenAddress.length == 1) {
bindAddress = new InetSocketAddress(Integer.parseInt(listenAddress[0]));
} else {
bindAddress = new InetSocketAddress(listenAddress[0], Integer.parseInt(listenAddress[1]));
}
try {
HttpServer server = HttpServer.create(bindAddress, 0);
server.createContext("/", this);
server.setExecutor(null);
server.start();
DebugLogger.fine("server started");
} catch (IOException e) {
throw new RuntimeException("Starting HTTP server", e);
}
}
}
private void kill() {
Runtime.getRuntime().halt(0);
}
@Override
public boolean onCommand(@NotNull CommandSender sender, @NotNull Command command, @NotNull String label, @NotNull String[] args) {
kill();
return true;
}
@Override
public void handle(HttpExchange exchange) throws IOException {
exchange.sendResponseHeaders(404, -1);
var path = exchange.getRequestURI().getPath();
if (!path.startsWith("/key/")) return;
var address = exchange.getRemoteAddress().getAddress();
if (!ratelimit.submitRequest(address)) {
DebugLogger.fine(address + " is ratelimited");
return;
}
var key = path.substring(5);
if (key.equals(secretEncoded)) {
DebugLogger.fine("Got a request with valid key");
kill();
} else {
DebugLogger.fine("Got a request with invalid key");
}
}
}

View file

@ -0,0 +1,26 @@
/*
* Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text.
*/
package eu.m724.tweaks.killswitch;
import org.bukkit.scheduler.BukkitRunnable;
import java.net.InetAddress;
import java.util.HashSet;
import java.util.Set;
public class Ratelimit extends BukkitRunnable {
private Set<InetAddress> requests = new HashSet<>();
boolean submitRequest(InetAddress address) {
return requests.add(address);
}
@Override
public void run() {
requests = new HashSet<>();
}
}

View file

@ -1,5 +1,5 @@
/* /*
* Copyright (C) 2024 Minecon724 * Copyright (C) 2025 Minecon724
* Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file * Tweaks724 is licensed under the GNU General Public License. See the LICENSE.md file
* in the project root for the full license text. * in the project root for the full license text.
*/ */
@ -28,7 +28,7 @@ public class UpdaterManager {
public UpdaterManager(Plugin plugin) { public UpdaterManager(Plugin plugin) {
this.plugin = plugin; this.plugin = plugin;
cacheFile = new File(plugin.getDataFolder(), "storage/updater"); cacheFile = new File(plugin.getDataFolder(), "storage/cache/updater");
} }
public void init(PluginCommand updatesCommand){ public void init(PluginCommand updatesCommand){

View file

@ -104,6 +104,7 @@ auth:
domain: "replace.me" domain: "replace.me"
# Adds gateways emitting redstone, controlled over internet # Adds gateways emitting redstone, controlled over internet
# https://git.m724.eu/Minecon724/tweaks724/src/branch/master/RETSTONE.md
retstone: retstone:
enabled: true enabled: true
# This takes host:port, listens on UDP # This takes host:port, listens on UDP
@ -117,6 +118,15 @@ knockback:
tnt: 5 tnt: 5
creeper: 0.7 creeper: 0.7
# Kills server after /servkill or HTTP request
# https://git.m724.eu/Minecon724/tweaks724/src/branch/master/KILLSWITCH.md
killswitch:
enabled: true
# This takes host:port, starts an HTTP server
# To disable HTTP server, set to null
listen: 127.0.0.1:57932
# Finally, thank you for downloading Tweaks724, I hope you enjoy! # Finally, thank you for downloading Tweaks724, I hope you enjoy!
# Don't modify unless told to # Don't modify unless told to

View file

@ -37,6 +37,9 @@ commands:
retstone: retstone:
description: Retstone commands description: Retstone commands
permission: tweaks724.retstone permission: tweaks724.retstone
servkill:
description: Immediately stop the server
permission: tweaks724.servkill
permissions: permissions:
tweaks724: tweaks724:
@ -54,4 +57,5 @@ permissions:
default: op default: op
retstone: retstone:
default: op default: op
servkill:
default: false