Compare commits
No commits in common. "ec59b3d293d85fbc53e8be4d04984978bdebc6d6" and "a157943187996f8f05e35f163c4afb9973d3f372" have entirely different histories.
ec59b3d293
...
a157943187
11 changed files with 7 additions and 217 deletions
|
@ -1,32 +0,0 @@
|
||||||
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
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Minecon724
|
* Copyright (C) 2024 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,10 +57,7 @@ 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;
|
||||||
|
@ -132,9 +129,6 @@ 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,
|
||||||
|
@ -149,8 +143,7 @@ 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;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Minecon724
|
* Copyright (C) 2024 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,7 +14,6 @@ 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;
|
||||||
|
@ -130,11 +129,6 @@ 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()) {
|
||||||
|
|
|
@ -1,125 +0,0 @@
|
||||||
/*
|
|
||||||
* 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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,26 +0,0 @@
|
||||||
/*
|
|
||||||
* 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<>();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2025 Minecon724
|
* Copyright (C) 2024 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/cache/updater");
|
cacheFile = new File(plugin.getDataFolder(), "storage/updater");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void init(PluginCommand updatesCommand){
|
public void init(PluginCommand updatesCommand){
|
||||||
|
|
|
@ -104,7 +104,6 @@ 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
|
||||||
|
@ -118,15 +117,6 @@ 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
|
||||||
|
|
|
@ -37,9 +37,6 @@ 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:
|
||||||
|
@ -57,5 +54,4 @@ permissions:
|
||||||
default: op
|
default: op
|
||||||
retstone:
|
retstone:
|
||||||
default: op
|
default: op
|
||||||
servkill:
|
|
||||||
default: false
|
|
||||||
|
|
Loading…
Reference in a new issue