diff --git a/README.md b/README.md index d28ed3a..28386f3 100644 --- a/README.md +++ b/README.md @@ -369,7 +369,9 @@ Please sign in with this key: Open the printed URL in a browser and sign in with the temporary key. -The wizard provides guided sections for: +The wizard uses a dark control-center UI. It detects whether it is running on Paper/Spigot or BungeeCord/Waterfall and only shows the setup sections that apply to that server type. + +Depending on the server type, it provides guided sections for: - server mode and node name - proxy listener settings @@ -379,6 +381,8 @@ The wizard provides guided sections for: - heartbeat and reconnect settings - advanced raw config keys +You can add or remove proxy endpoints, backend routes, and advanced config keys directly from the web UI. + The web UI includes: - `Save Config` diff --git a/common/target/proxylink-common-0.1.0-SNAPSHOT.jar b/common/target/proxylink-common-0.1.0-SNAPSHOT.jar index 8389f62..9f45ac8 100644 Binary files a/common/target/proxylink-common-0.1.0-SNAPSHOT.jar and b/common/target/proxylink-common-0.1.0-SNAPSHOT.jar differ diff --git a/plugin/src/main/java/me/proxylink/plugin/ConfigFileEditor.java b/plugin/src/main/java/me/proxylink/plugin/ConfigFileEditor.java index 05dbbce..3754b3e 100644 --- a/plugin/src/main/java/me/proxylink/plugin/ConfigFileEditor.java +++ b/plugin/src/main/java/me/proxylink/plugin/ConfigFileEditor.java @@ -36,4 +36,18 @@ public final class ConfigFileEditor { Files.write(path, lines, StandardCharsets.UTF_8); } + + public static void removeProperty(Path path, String key) throws IOException { + if (!Files.exists(path)) { + return; + } + + String prefix = key + "="; + List lines = new ArrayList<>(Files.readAllLines(path, StandardCharsets.UTF_8)); + lines.removeIf(line -> { + String trimmed = line.trim(); + return !trimmed.startsWith("#") && trimmed.startsWith(prefix); + }); + Files.write(path, lines, StandardCharsets.UTF_8); + } } diff --git a/plugin/src/main/java/me/proxylink/plugin/PluginRuntime.java b/plugin/src/main/java/me/proxylink/plugin/PluginRuntime.java index 48e508c..7c9ada2 100644 --- a/plugin/src/main/java/me/proxylink/plugin/PluginRuntime.java +++ b/plugin/src/main/java/me/proxylink/plugin/PluginRuntime.java @@ -149,7 +149,7 @@ public final class PluginRuntime { } try { - webWizard = WebWizardServer.start(configPath, logger, this::reloadFromConfig); + webWizard = WebWizardServer.start(configPath, logger, this::reloadFromConfig, requiredRole); lines.add("Web wizard: running"); lines.add("URL: " + webWizard.uri()); lines.add("Login key: printed in the server console"); diff --git a/plugin/src/main/java/me/proxylink/plugin/web/WebWizardServer.java b/plugin/src/main/java/me/proxylink/plugin/web/WebWizardServer.java index 1b0b408..9d87f6d 100644 --- a/plugin/src/main/java/me/proxylink/plugin/web/WebWizardServer.java +++ b/plugin/src/main/java/me/proxylink/plugin/web/WebWizardServer.java @@ -2,6 +2,7 @@ package me.proxylink.plugin.web; import com.sun.net.httpserver.HttpExchange; import com.sun.net.httpserver.HttpServer; +import me.proxylink.common.AgentConfig; import me.proxylink.plugin.ConfigFileEditor; import java.io.IOException; @@ -42,6 +43,7 @@ public final class WebWizardServer implements AutoCloseable { private final Path configPath; private final Logger logger; private final Supplier reloadAction; + private final AgentConfig.Role runtimeRole; private final HttpServer server; private final ExecutorService executor; private final String loginKey; @@ -53,6 +55,7 @@ public final class WebWizardServer implements AutoCloseable { Path configPath, Logger logger, Supplier reloadAction, + AgentConfig.Role runtimeRole, HttpServer server, ExecutorService executor, String loginKey, @@ -62,6 +65,7 @@ public final class WebWizardServer implements AutoCloseable { this.configPath = configPath; this.logger = logger; this.reloadAction = reloadAction; + this.runtimeRole = runtimeRole; this.server = server; this.executor = executor; this.loginKey = loginKey; @@ -69,7 +73,12 @@ public final class WebWizardServer implements AutoCloseable { this.uri = uri; } - public static WebWizardServer start(Path configPath, Logger logger, Supplier reloadAction) throws IOException { + public static WebWizardServer start( + Path configPath, + Logger logger, + Supplier reloadAction, + AgentConfig.Role runtimeRole + ) throws IOException { WizardSettings settings = WizardSettings.load(configPath); IOException lastFailure = null; int firstPort = settings.port(); @@ -78,7 +87,7 @@ public final class WebWizardServer implements AutoCloseable { for (int attempt = 0; attempt < attempts; attempt++) { int port = firstPort == 0 ? 0 : firstPort + attempt; try { - return bind(configPath, logger, reloadAction, settings.bindHost(), port); + return bind(configPath, logger, reloadAction, runtimeRole, settings.bindHost(), port); } catch (BindException e) { lastFailure = e; } @@ -109,6 +118,7 @@ public final class WebWizardServer implements AutoCloseable { Path configPath, Logger logger, Supplier reloadAction, + AgentConfig.Role runtimeRole, String bindHost, int port ) throws IOException { @@ -118,11 +128,12 @@ public final class WebWizardServer implements AutoCloseable { ); String loginKey = randomHex(18); String sessionToken = randomHex(32); - URI uri = URI.create("http://" + uriHost(bindHost) + ":" + server.getAddress().getPort() + "/"); + URI uri = URI.create("http://" + displayUriHost(bindHost) + ":" + server.getAddress().getPort() + "/"); WebWizardServer webWizard = new WebWizardServer( configPath, logger, reloadAction, + runtimeRole, server, executor, loginKey, @@ -138,6 +149,9 @@ public final class WebWizardServer implements AutoCloseable { server.start(); logger.warning("DirtSimpleP2P Web Wizard started at " + uri); + if (isWildcardHost(bindHost)) { + logger.warning("Web Wizard is bound to " + bindHost + ". Use your server IP instead of 127.0.0.1 when connecting from another computer."); + } logger.warning("Please sign in with this key: " + loginKey); logger.warning("The Web Wizard can edit config.properties. Stop it with /dsp2p webwizard when finished."); return webWizard; @@ -214,7 +228,7 @@ public final class WebWizardServer implements AutoCloseable { if ("reloadOnly".equals(action)) { message = reloadAction.get(); } else { - saveWizardForm(form); + saveWizardForm(form, action); if ("generateToken".equals(action)) { String token = randomHex(32); ConfigFileEditor.setProperty(configPath, "tunnel.authToken", token); @@ -234,15 +248,14 @@ public final class WebWizardServer implements AutoCloseable { } } - private void saveWizardForm(Map> form) throws IOException { - List frontendNames = namesFrom(first(form, "frontends")); - String newFrontend = first(form, "newFrontendName").trim(); - if (!newFrontend.isBlank()) { - validateConfigKey(newFrontend); - if (!frontendNames.contains(newFrontend)) { - frontendNames.add(newFrontend); - } - } + private void saveWizardForm(Map> form, String action) throws IOException { + boolean frontendMode = runtimeRole == AgentConfig.Role.FRONTEND; + boolean backendMode = runtimeRole == AgentConfig.Role.BACKEND; + String removeFrontend = actionValue(action, "removeFrontend:"); + String removeRoute = actionValue(action, "removeRoute:"); + String removeRaw = actionValue(action, "removeRaw:"); + boolean removedFrontend = false; + boolean removedRoute = false; List routeNames = namesFrom(first(form, "routes")); String newRoute = first(form, "newRouteName").trim(); @@ -252,11 +265,12 @@ public final class WebWizardServer implements AutoCloseable { routeNames.add(newRoute); } } + if (!removeRoute.isBlank() && routeNames.size() > 1) { + removedRoute = routeNames.remove(removeRoute); + } - saveKey(form, "role"); + ConfigFileEditor.setProperty(configPath, "role", runtimeRole.name().toLowerCase(java.util.Locale.ROOT)); saveKey(form, "node.name"); - saveKey(form, "tunnel.listenHost"); - saveKey(form, "tunnel.listenPort"); saveKey(form, "tunnel.authToken"); saveKey(form, "tunnel.connectTimeoutMillis"); saveKey(form, "tunnel.heartbeatIntervalMillis"); @@ -266,33 +280,74 @@ public final class WebWizardServer implements AutoCloseable { saveKey(form, "tunnel.reconnectMaxMillis"); saveBoolean(form, "tunnel.tls.enabled"); saveBoolean(form, "tunnel.tls.allowInsecure"); - saveBoolean(form, "tunnel.tls.autoGenerate"); - saveKey(form, "tunnel.tls.keyStore"); - saveKey(form, "tunnel.tls.keyStorePassword"); - saveBoolean(form, "tunnel.tls.requireClientAuth"); - saveBoolean(form, "tunnel.tls.trustOnFirstUse"); saveKey(form, "webwizard.bindHost"); saveKey(form, "webwizard.port"); - ConfigFileEditor.setProperty(configPath, "frontends", String.join(",", frontendNames)); - for (String frontend : frontendNames) { - saveNamed(form, "frontend." + frontend + ".connectHost"); - saveNamed(form, "frontend." + frontend + ".connectPort"); - saveNamed(form, "frontend." + frontend + ".tls.pinnedCertificateSha256"); + if (frontendMode) { + saveKey(form, "tunnel.listenHost"); + saveKey(form, "tunnel.listenPort"); + saveBoolean(form, "tunnel.tls.autoGenerate"); + saveKey(form, "tunnel.tls.keyStore"); + saveKey(form, "tunnel.tls.keyStorePassword"); + saveBoolean(form, "tunnel.tls.requireClientAuth"); + } + + if (backendMode) { + saveBoolean(form, "tunnel.tls.trustOnFirstUse"); + List frontendNames = namesFrom(first(form, "frontends")); + String newFrontend = first(form, "newFrontendName").trim(); + if (!newFrontend.isBlank()) { + validateConfigKey(newFrontend); + if (!frontendNames.contains(newFrontend)) { + frontendNames.add(newFrontend); + } + } + if (!removeFrontend.isBlank() && frontendNames.size() > 1) { + removedFrontend = frontendNames.remove(removeFrontend); + } + + ConfigFileEditor.setProperty(configPath, "frontends", String.join(",", frontendNames)); + for (String frontend : frontendNames) { + saveNamed(form, "frontend." + frontend + ".connectHost"); + saveNamed(form, "frontend." + frontend + ".connectPort"); + saveNamed(form, "frontend." + frontend + ".tls.pinnedCertificateSha256"); + } + if (removedFrontend) { + ConfigFileEditor.removeProperty(configPath, "frontend." + removeFrontend + ".connectHost"); + ConfigFileEditor.removeProperty(configPath, "frontend." + removeFrontend + ".connectPort"); + ConfigFileEditor.removeProperty(configPath, "frontend." + removeFrontend + ".tls.pinnedCertificateSha256"); + } } ConfigFileEditor.setProperty(configPath, "routes", String.join(",", routeNames)); for (String route : routeNames) { - saveNamed(form, "route." + route + ".frontendBindHost"); - saveNamed(form, "route." + route + ".frontendBindPort"); - saveNamed(form, "route." + route + ".backendNode"); - saveNamed(form, "route." + route + ".backendTargetHost"); - saveNamed(form, "route." + route + ".backendTargetPort"); + if (frontendMode) { + saveNamed(form, "route." + route + ".frontendBindHost"); + saveNamed(form, "route." + route + ".frontendBindPort"); + saveNamed(form, "route." + route + ".backendNode"); + } + if (backendMode) { + saveNamed(form, "route." + route + ".backendTargetHost"); + saveNamed(form, "route." + route + ".backendTargetPort"); + } + } + if (removedRoute) { + ConfigFileEditor.removeProperty(configPath, "route." + removeRoute + ".frontendBindHost"); + ConfigFileEditor.removeProperty(configPath, "route." + removeRoute + ".frontendBindPort"); + ConfigFileEditor.removeProperty(configPath, "route." + removeRoute + ".backendNode"); + ConfigFileEditor.removeProperty(configPath, "route." + removeRoute + ".backendTargetHost"); + ConfigFileEditor.removeProperty(configPath, "route." + removeRoute + ".backendTargetPort"); } for (String key : form.getOrDefault("rawKeys", List.of())) { validateConfigKey(key); - ConfigFileEditor.setProperty(configPath, key, first(form, "raw." + key)); + if (!key.equals(removeRaw)) { + ConfigFileEditor.setProperty(configPath, key, first(form, "raw." + key)); + } + } + if (!removeRaw.isBlank()) { + validateConfigKey(removeRaw); + ConfigFileEditor.removeProperty(configPath, removeRaw); } String newRawKey = first(form, "newRawKey").trim(); @@ -314,6 +369,17 @@ public final class WebWizardServer implements AutoCloseable { ConfigFileEditor.setProperty(configPath, key, Boolean.toString(form.containsKey(key))); } + private static String actionValue(String action, String prefix) { + if (action == null || !action.startsWith(prefix)) { + return ""; + } + String value = action.substring(prefix.length()).trim(); + if (!value.isBlank()) { + validateConfigKey(value); + } + return value; + } + private String loginPage(String message) { return page("Sign In", """ """.formatted( - html(route), - textInput(properties, "route." + route + ".frontendBindHost", "Proxy local bind host", "127.0.0.1"), - textInput(properties, "route." + route + ".frontendBindPort", "Proxy local bind port", "25566"), - textInput(properties, "route." + route + ".backendNode", "Backend node name", "paper-backend"), - textInput(properties, "route." + route + ".backendTargetHost", "Backend target host", "127.0.0.1"), - textInput(properties, "route." + route + ".backendTargetPort", "Backend Minecraft port", "25565") + textInput(properties, "tunnel.listenHost", "Tunnel listen host", "0.0.0.0"), + textInput(properties, "tunnel.listenPort", "Tunnel listen port", "24445") )); } + if (backendMode) { + cards.append(""" +
+
2

Backend Connections

Add every public proxy this backend should connect to.

+ %s +
+ + +
+ %s +
+ """.formatted(hiddenText("frontends", String.join(",", frontendNames)), frontends)); + } + + cards.append(""" +
+
3

Minecraft Routes

%s

+ %s +
+ + +
+ %s +
+ """.formatted( + frontendMode + ? "Expose local Bungee backend addresses and map them to named backend nodes." + : "Point tunnel routes at the local Minecraft server ports on this backend.", + hiddenText("routes", String.join(",", routeNames)), + frontendMode ? "New backend route name" : "New local route name", + frontendMode ? "Add Backend" : "Add Route", + routes + )); + + String frontendSecurity = frontendMode ? """ + %s + %s + %s + %s + """.formatted( + checkbox(properties, "tunnel.tls.autoGenerate", "Auto-generate frontend certificate"), + textInput(properties, "tunnel.tls.keyStore", "Frontend TLS keystore", "certs/frontend.p12"), + textInput(properties, "tunnel.tls.keyStorePassword", "Keystore password", ""), + checkbox(properties, "tunnel.tls.requireClientAuth", "Require TLS client certificates") + ) : ""; + String backendSecurity = backendMode + ? checkbox(properties, "tunnel.tls.trustOnFirstUse", "Trust first TLS certificate") + : ""; + + cards.append(""" +
+
4

Security

Use one shared secret token across the same private tunnel group.

+
+ %s + %s + %s + %s + %s +
+
+ """.formatted( + textInput(properties, "tunnel.authToken", "Shared tunnel token", "copy the same token to every node"), + checkbox(properties, "tunnel.tls.enabled", "Enable TLS"), + checkbox(properties, "tunnel.tls.allowInsecure", "Allow insecure mode"), + frontendSecurity, + backendSecurity + )); + + cards.append(""" +
+
5

Reliability

Fast reconnect defaults are already tuned for most Minecraft networks.

+
+ %s + %s + %s + %s + %s + %s +
+
+ """.formatted( + textInput(properties, "tunnel.connectTimeoutMillis", "Connect timeout", "5000"), + textInput(properties, "tunnel.heartbeatIntervalMillis", "Heartbeat interval", "2000"), + textInput(properties, "tunnel.heartbeatTimeoutMillis", "Heartbeat timeout", "8000"), + textInput(properties, "tunnel.heartbeatMissesBeforeDisconnect", "Missed heartbeats before disconnect", "4"), + textInput(properties, "tunnel.reconnectInitialMillis", "First reconnect delay", "250"), + textInput(properties, "tunnel.reconnectMaxMillis", "Max reconnect delay", "10000") + )); + + cards.append(""" +
+
6

Web Wizard Access

Keep this bound to localhost unless you are putting it behind your own secure tunnel.

+
+ %s + %s +
+
+
+
7

Advanced Raw Settings

Extra custom config keys are preserved here.

+ %s +
+ + +
+
+
+ + + + + Sign out +
+ """.formatted( + textInput(properties, "webwizard.bindHost", "Wizard bind host", "127.0.0.1"), + textInput(properties, "webwizard.port", "Wizard port", "8765"), + advancedRows + )); + + return page("Setup Wizard", """ +
+
+
Configuration Wizard
+

%s Control Center

+

Configure only the settings that apply to this server type, save safely, then reload DirtSimpleP2P without restarting Minecraft.

+
+
+ Detected mode + %s + Config file + %s + Wizard URL: %s +
+
+ %s +
+
+ +
+ %s +
+
+
+ """.formatted( + html(frontendMode ? "Proxy" : "Backend"), + html(roleLabel), + html(configPath.toString()), + html(uri.toString()), + alert(message), + nav, + cards + )); + } + + private static void addKnownGuidedKeys(Properties properties, Set guidedKeys) { guidedKeys.addAll(List.of( "role", "node.name", "tunnel.listenHost", "tunnel.listenPort", + "tunnel.connectHost", + "tunnel.connectPort", "tunnel.authToken", "tunnel.connectTimeoutMillis", "tunnel.heartbeatIntervalMillis", @@ -405,158 +690,27 @@ public final class WebWizardServer implements AutoCloseable { "tunnel.tls.keyStorePassword", "tunnel.tls.requireClientAuth", "tunnel.tls.trustOnFirstUse", + "tunnel.tls.pinnedCertificateSha256", "webwizard.bindHost", "webwizard.port", "frontends", "routes" )); - String advancedRows = advancedRows(properties, guidedKeys); - - return page("Setup Wizard", """ -
-
-
Configuration Wizard
-

DirtSimpleP2P Control Center

-

Configure the tunnel with guided fields, save safely, then reload DirtSimpleP2P without restarting the Minecraft server.

-
-
- Config file - %s - Wizard URL: %s -
-
- %s -
-
- -
-
-
1

Server Identity

Choose which side this plugin is running on.

-
- %s - %s -
-
-
-
2

Proxy Listener

Used on BungeeCord or Waterfall. Backend nodes connect to this public tunnel port.

-
- %s - %s -
-
-
-
3

Backend Connections

Used on Paper or Spigot. Add every public proxy this backend should connect to.

- %s -
- -
- %s -
-
-
4

Minecraft Routes

Map Bungee local ports to named backend nodes and backend Minecraft ports.

- %s -
- -
- %s -
-
-
5

Security

Use a shared secret token. Enable TLS for real deployments.

-
- %s - %s - %s - %s - %s - %s - %s - %s -
-
-
-
6

Reliability

Fast reconnect defaults are already tuned for most Minecraft networks.

-
- %s - %s - %s - %s - %s -
-
-
-
7

Web Wizard Access

Keep this bound to localhost unless you are putting it behind your own secure tunnel.

-
- %s - %s -
-
-
-
8

Advanced Raw Settings

Extra config keys are preserved here for power users.

- %s -
- - -
-
-
- - - - - Sign out -
-
-
-
- """.formatted( - html(configPath.toString()), - html(uri.toString()), - alert(message), - roleSelect(role), - textInput(properties, "node.name", "Node name", role.equals("frontend") ? "bungee-frontend" : "paper-backend"), - textInput(properties, "tunnel.listenHost", "Tunnel listen host", "0.0.0.0"), - textInput(properties, "tunnel.listenPort", "Tunnel listen port", "24445"), - hiddenText("frontends", String.join(",", frontendNames)), - frontends, - hiddenText("routes", String.join(",", routeNames)), - routes, - textInput(properties, "tunnel.authToken", "Shared tunnel token", "copy the same token to every node"), - checkbox(properties, "tunnel.tls.enabled", "Enable TLS"), - checkbox(properties, "tunnel.tls.allowInsecure", "Allow insecure mode"), - checkbox(properties, "tunnel.tls.autoGenerate", "Auto-generate frontend certificate"), - textInput(properties, "tunnel.tls.keyStore", "Frontend TLS keystore", "certs/frontend.p12"), - textInput(properties, "tunnel.tls.keyStorePassword", "Keystore password", ""), - checkbox(properties, "tunnel.tls.requireClientAuth", "Require TLS client certificates"), - checkbox(properties, "tunnel.tls.trustOnFirstUse", "Trust first TLS certificate"), - textInput(properties, "tunnel.connectTimeoutMillis", "Connect timeout", "5000"), - textInput(properties, "tunnel.heartbeatIntervalMillis", "Heartbeat interval", "2000"), - textInput(properties, "tunnel.heartbeatTimeoutMillis", "Heartbeat timeout", "8000"), - textInput(properties, "tunnel.reconnectInitialMillis", "First reconnect delay", "250"), - textInput(properties, "tunnel.reconnectMaxMillis", "Max reconnect delay", "10000"), - textInput(properties, "webwizard.bindHost", "Wizard bind host", "127.0.0.1"), - textInput(properties, "webwizard.port", "Wizard port", "8765"), - advancedRows - )); + for (String key : properties.stringPropertyNames()) { + if (key.startsWith("frontend.") || key.startsWith("route.")) { + guidedKeys.add(key); + } + } } - private static String roleSelect(String role) { + private static String roleBadge(String role, String label) { return """ - """.formatted(selected(role, "frontend"), selected(role, "backend")); + """.formatted(attr(role), attr(label)); } private static String textInput(Properties properties, String key, String label, String placeholder) { @@ -586,10 +740,12 @@ public final class WebWizardServer implements AutoCloseable { StringBuilder rows = new StringBuilder("
"); for (String key : keys) { - rows.append("
"); return rows.toString(); @@ -602,64 +758,75 @@ public final class WebWizardServer implements AutoCloseable { - DirtSimpleP2P - %s + DirtSimpleP2P - __TITLE__ -
%s
+
__BODY__
- """.formatted(html(title), body); + """.replace("__TITLE__", html(title)).replace("__BODY__", body); } private boolean isAuthenticated(HttpExchange exchange) { @@ -843,6 +1010,22 @@ public final class WebWizardServer implements AutoCloseable { return host; } + private static String displayUriHost(String host) { + if ("0.0.0.0".equals(host)) { + return "127.0.0.1"; + } + if ("::".equals(host) || "0:0:0:0:0:0:0:0".equals(host)) { + return "[::1]"; + } + return uriHost(host); + } + + private static boolean isWildcardHost(String host) { + return "0.0.0.0".equals(host) + || "::".equals(host) + || "0:0:0:0:0:0:0:0".equals(host); + } + private record WizardSettings(String bindHost, int port) { private static WizardSettings load(Path configPath) throws IOException { Properties properties = loadProperties(configPath); diff --git a/plugin/target/DirtSimpleP2P-0.1.0-SNAPSHOT.jar b/plugin/target/DirtSimpleP2P-0.1.0-SNAPSHOT.jar index e7e9c50..a9a6177 100644 Binary files a/plugin/target/DirtSimpleP2P-0.1.0-SNAPSHOT.jar and b/plugin/target/DirtSimpleP2P-0.1.0-SNAPSHOT.jar differ diff --git a/plugin/target/classes/me/proxylink/plugin/ConfigFileEditor.class b/plugin/target/classes/me/proxylink/plugin/ConfigFileEditor.class index dacc19c..9c9cb4c 100644 Binary files a/plugin/target/classes/me/proxylink/plugin/ConfigFileEditor.class and b/plugin/target/classes/me/proxylink/plugin/ConfigFileEditor.class differ diff --git a/plugin/target/classes/me/proxylink/plugin/PluginRuntime.class b/plugin/target/classes/me/proxylink/plugin/PluginRuntime.class index b3060c2..c61f419 100644 Binary files a/plugin/target/classes/me/proxylink/plugin/PluginRuntime.class and b/plugin/target/classes/me/proxylink/plugin/PluginRuntime.class differ diff --git a/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer$WizardSettings.class b/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer$WizardSettings.class index 9b2b7cc..2ba2f5a 100644 Binary files a/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer$WizardSettings.class and b/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer$WizardSettings.class differ diff --git a/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer.class b/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer.class index f0ef7cf..04d3a61 100644 Binary files a/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer.class and b/plugin/target/classes/me/proxylink/plugin/web/WebWizardServer.class differ diff --git a/plugin/target/original-DirtSimpleP2P-0.1.0-SNAPSHOT.jar b/plugin/target/original-DirtSimpleP2P-0.1.0-SNAPSHOT.jar index 5cd7662..ee6ba35 100644 Binary files a/plugin/target/original-DirtSimpleP2P-0.1.0-SNAPSHOT.jar and b/plugin/target/original-DirtSimpleP2P-0.1.0-SNAPSHOT.jar differ