diff --git a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java index 02c085241..52aa7468a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/command/builtin/VelocityCommand.java @@ -3,19 +3,27 @@ package com.velocitypowered.proxy.command.builtin; import com.google.common.base.Joiner; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; + +import com.google.common.collect.ImmutableSet; +import com.google.gson.JsonObject; import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.SimpleCommand; import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.util.ProxyVersion; import com.velocitypowered.proxy.VelocityServer; +import com.velocitypowered.proxy.util.InformationUtils; + import java.util.Arrays; +import java.util.Collection; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.stream.Collectors; + import net.kyori.adventure.identity.Identity; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.TextComponent; @@ -52,6 +60,7 @@ public class VelocityCommand implements SimpleCommand { .put("version", new Info(server)) .put("plugins", new Plugins(server)) .put("reload", new Reload(server)) + .put("dump", new Dump(server)) .build(); } @@ -289,4 +298,44 @@ public class VelocityCommand implements SimpleCommand { return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; } } + + private static class Dump implements SubCommand { + + private final ProxyServer server; + + private Dump(ProxyServer server) { + this.server = server; + } + + @Override + public void execute(CommandSource source, String @NonNull [] args) { + if (args.length != 0) { + source.sendMessage(Identity.nil(), Component.text("/velocity dump", NamedTextColor.RED)); + return; + } + + Collection allServers = ImmutableSet.copyOf(server.getAllServers()); + JsonObject servers = new JsonObject(); + for (RegisteredServer iter : allServers) { + servers.add(iter.getServerInfo().getName(), + InformationUtils.collectServerInfo(iter)); + } + + JsonObject proxyConfig = InformationUtils.collectProxyConfig(server.getConfiguration()); + proxyConfig.add("servers", servers); + + JsonObject dump = new JsonObject(); + dump.add("versionInfo", InformationUtils.collectProxyInfo(server.getVersion())); + dump.add("platform", InformationUtils.collectEnvironmentInfo()); + dump.add("config", proxyConfig); + dump.add("plugins", InformationUtils.collectPluginInfo(server)); + + // TODO: Finish + } + + @Override + public boolean hasPermission(final CommandSource source, final String @NonNull [] args) { + return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; + } + } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java index a3f7537f2..5660b913e 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -7,6 +7,7 @@ import com.electronwill.nightconfig.toml.TomlFormat; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import com.google.gson.annotations.Expose; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; import com.velocitypowered.proxy.util.AddressUtil; @@ -42,20 +43,20 @@ public class VelocityConfiguration implements ProxyConfig { private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class); - private String bind = "0.0.0.0:25577"; - private String motd = "&3A Velocity Server"; - private int showMaxPlayers = 500; - private boolean onlineMode = true; - private boolean preventClientProxyConnections = false; - private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; + @Expose private String bind = "0.0.0.0:25577"; + @Expose private String motd = "&3A Velocity Server"; + @Expose private int showMaxPlayers = 500; + @Expose private boolean onlineMode = true; + @Expose private boolean preventClientProxyConnections = false; + @Expose private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); - private boolean announceForge = false; - private boolean onlineModeKickExistingPlayers = false; - private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; + @Expose private boolean announceForge = false; + @Expose private boolean onlineModeKickExistingPlayers = false; + @Expose private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; private final Servers servers; private final ForcedHosts forcedHosts; - private final Advanced advanced; - private final Query query; + @Expose private final Advanced advanced; + @Expose private final Query query; private final Metrics metrics; private final Messages messages; private net.kyori.adventure.text.@MonotonicNonNull Component motdAsComponent; @@ -622,18 +623,18 @@ public class VelocityConfiguration implements ProxyConfig { private static class Advanced { - private int compressionThreshold = 256; - private int compressionLevel = -1; - private int loginRatelimit = 3000; - private int connectionTimeout = 5000; - private int readTimeout = 30000; - private boolean proxyProtocol = false; - private boolean tcpFastOpen = false; - private boolean bungeePluginMessageChannel = true; - private boolean showPingRequests = false; - private boolean failoverOnUnexpectedServerDisconnect = true; - private boolean announceProxyCommands = true; - private boolean logCommandExecutions = false; + @Expose private int compressionThreshold = 256; + @Expose private int compressionLevel = -1; + @Expose private int loginRatelimit = 3000; + @Expose private int connectionTimeout = 5000; + @Expose private int readTimeout = 30000; + @Expose private boolean proxyProtocol = false; + @Expose private boolean tcpFastOpen = false; + @Expose private boolean bungeePluginMessageChannel = true; + @Expose private boolean showPingRequests = false; + @Expose private boolean failoverOnUnexpectedServerDisconnect = true; + @Expose private boolean announceProxyCommands = true; + @Expose private boolean logCommandExecutions = false; private Advanced() { } @@ -725,10 +726,10 @@ public class VelocityConfiguration implements ProxyConfig { private static class Query { - private boolean queryEnabled = false; - private int queryPort = 25577; - private String queryMap = "Velocity"; - private boolean showPlugins = false; + @Expose private boolean queryEnabled = false; + @Expose private int queryPort = 25577; + @Expose private String queryMap = "Velocity"; + @Expose private boolean showPlugins = false; private Query() { } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java new file mode 100644 index 000000000..ebc1dd015 --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/util/InformationUtils.java @@ -0,0 +1,154 @@ +package com.velocitypowered.proxy.util; + +import com.google.common.collect.ImmutableList; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; +import com.velocitypowered.api.plugin.PluginContainer; +import com.velocitypowered.api.plugin.PluginDescription; +import com.velocitypowered.api.plugin.meta.PluginDependency; +import com.velocitypowered.api.proxy.ProxyServer; +import com.velocitypowered.api.proxy.config.ProxyConfig; +import com.velocitypowered.api.proxy.server.RegisteredServer; +import com.velocitypowered.api.util.ProxyVersion; + +import io.netty.channel.unix.DomainSocketAddress; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.List; +import joptsimple.internal.Strings; + +public enum InformationUtils { + ; + + /** + * Retrieves a {@link JsonArray} containing basic information about all + * running plugins on the {@link ProxyServer} instance. + * + * @param proxy the proxy instance to retrieve from + * @return {@link JsonArray} containing zero or more {@link JsonObject} + */ + public static JsonArray collectPluginInfo(ProxyServer proxy) { + List allPlugins = ImmutableList.copyOf( + proxy.getPluginManager().getPlugins()); + JsonArray plugins = new JsonArray(); + + for (PluginContainer plugin : allPlugins) { + PluginDescription desc = plugin.getDescription(); + JsonObject current = new JsonObject(); + current.addProperty("id", desc.getId()); + if (desc.getName().isPresent()) { + current.addProperty("name", desc.getName().get()); + } + if (desc.getVersion().isPresent()) { + current.addProperty("version", desc.getVersion().get()); + } + if (!desc.getAuthors().isEmpty()) { + current.addProperty("authors", + Strings.join(desc.getAuthors(), ",")); + } + if (desc.getDescription().isPresent()) { + current.addProperty("description", desc.getDescription().get()); + } + if (desc.getUrl().isPresent()) { + current.addProperty("url", desc.getUrl().get()); + } + if (!desc.getDependencies().isEmpty()) { + JsonArray dependencies = new JsonArray(); + for (PluginDependency dependency : desc.getDependencies()) { + dependencies.add(dependency.getId()); + } + current.add("dependencies", dependencies); + } + plugins.add(current); + } + return plugins; + } + + /** + * Creates a {@link JsonObject} containing information about the + * current environment the project is run under. + * + * @return {@link JsonObject} containing environment info + */ + public static JsonObject collectEnvironmentInfo() { + JsonObject envInfo = new JsonObject(); + envInfo.addProperty("operatingSystemType", System.getProperty("os.name")); + envInfo.addProperty("operatingSystemVersion", System.getProperty("os.version")); + envInfo.addProperty("operatingSystemArchitecture", System.getProperty("os.arch")); + envInfo.addProperty("javaVersion", System.getProperty("java.version")); + envInfo.addProperty("javaVendor", System.getProperty("java.vendor")); + return envInfo; + } + + /** + * Creates a {@link JsonObject} containing most relevant + * information of the {@link RegisteredServer} for diagnosis. + * + * @param server the server to evaluate + * @return {@link JsonObject} containing server and diagnostic info + */ + public static JsonObject collectServerInfo(RegisteredServer server) { + JsonObject info = new JsonObject(); + info.addProperty("currentPlayers", server.getPlayersConnected().size()); + SocketAddress address = server.getServerInfo().getAddress(); + if (address instanceof InetSocketAddress) { + InetSocketAddress iaddr = (InetSocketAddress) address; + info.addProperty("socketType", "EventLoop"); + info.addProperty("unresolved", iaddr.isUnresolved()); + // Greetings form Netty 4aa10db9 + info.addProperty("host", iaddr.getHostString()); + info.addProperty("port", iaddr.getPort()); + } else if (address instanceof DomainSocketAddress) { + DomainSocketAddress daddr = (DomainSocketAddress) address; + info.addProperty("socketType", "Unix/Epoll"); + info.addProperty("host", daddr.path()); + } else { + info.addProperty("socketType", "Unknown/Generic"); + info.addProperty("host", address.toString()); + } + return info; + } + + /** + * Creates a {@link JsonObject} containing information about the + * current environment the project is run under. + * + * @param version the proxy instance to retrieve from + * @return {@link JsonObject} containing environment info + */ + public static JsonObject collectProxyInfo(ProxyVersion version) { + return (JsonObject) serializeObject(version, false); + } + + /** + * Creates a {@link JsonObject} containing most relevant + * information of the {@link ProxyConfig} for diagnosis. + * + * @param config the config instance to retrieve from + * @return {@link JsonObject} containing select config values + */ + public static JsonObject collectProxyConfig(ProxyConfig config) { + return (JsonObject) serializeObject(config, true); + } + + private static JsonElement serializeObject(Object toSerialize, boolean withExcludes) { + return JsonParser.parseString( + withExcludes ? GSON_WITH_EXCLUDES.toJson(toSerialize) : + GSON_WITHOUT_EXCLUDES.toJson(toSerialize)); + } + + private static final Gson GSON_WITH_EXCLUDES = new GsonBuilder() + .setPrettyPrinting() + .excludeFieldsWithoutExposeAnnotation() + .create(); + + private static final Gson GSON_WITHOUT_EXCLUDES = new GsonBuilder() + .setPrettyPrinting() + .create(); + + +}