diff --git a/proxy/build.gradle b/proxy/build.gradle index 266883969..9615bde53 100644 --- a/proxy/build.gradle +++ b/proxy/build.gradle @@ -71,6 +71,8 @@ dependencies { compile 'com.spotify:completable-futures:0.3.2' + compile 'com.electronwill.night-config:toml:3.6.3' + testCompile "org.junit.jupiter:junit-jupiter-api:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java index 21b08e3aa..e011085a5 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java @@ -25,7 +25,6 @@ import com.velocitypowered.proxy.command.ServerCommand; import com.velocitypowered.proxy.command.ShutdownCommand; import com.velocitypowered.proxy.command.VelocityCommand; import com.velocitypowered.proxy.command.VelocityCommandManager; -import com.velocitypowered.proxy.config.AnnotatedConfig; import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.console.VelocityConsole; @@ -80,7 +79,6 @@ import org.checkerframework.checker.nullness.qual.EnsuresNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.Nullable; -import org.checkerframework.checker.nullness.qual.RequiresNonNull; public class VelocityServer implements ProxyServer { @@ -185,9 +183,6 @@ public class VelocityServer implements ProxyServer { Path configPath = Paths.get("velocity.toml"); configuration = VelocityConfiguration.read(configPath); - // Resave config to add new values - AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath); - if (!configuration.validate()) { logger.error("Your configuration is invalid. Velocity will not start up until the errors " + "are resolved."); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java b/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java deleted file mode 100644 index 6b074c02d..000000000 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/AnnotatedConfig.java +++ /dev/null @@ -1,267 +0,0 @@ -package com.velocitypowered.proxy.config; - -import java.io.IOException; -import java.lang.annotation.ElementType; -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import java.lang.annotation.Target; -import java.lang.reflect.Field; -import java.nio.charset.StandardCharsets; -import java.nio.file.AtomicMoveNotSupportedException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardCopyOption; -import java.nio.file.StandardOpenOption; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -import java.util.regex.Pattern; -import org.apache.logging.log4j.LogManager; -import org.apache.logging.log4j.Logger; - -/** - * Simple annotation and fields based TOML configuration serializer. - */ -public abstract class AnnotatedConfig { - - private static final Logger logger = LogManager.getLogger(AnnotatedConfig.class); - private static final Pattern STRING_NEEDS_ESCAPE - = Pattern.compile("(\"|\\\\|[\\u0000-\\u0008]|[\\u000a-\\u001f]|\\u007f)"); - - public static Logger getLogger() { - return logger; - } - - /** - * Indicates that a field is a table. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface Table { - /** - * The table's name. - * @return the table's name - */ - String value(); - } - - /** - * Creates a comment. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface Comment { - /** - * The comments to include with this key. Each entry is considered a line. - * @return the comments - */ - String[] value(); - } - - /** - * How field will be named in config. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface ConfigKey { - /** - * The name of this field in the configuration. - * @return the field's name - */ - String value(); - } - - /** - * Indicates that a field is a map and we need to save all map data to config. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface IsMap { - - } - - /** - * Indicates that a field is a string converted to byte[]. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD, ElementType.TYPE}) - public @interface StringAsBytes { - - } - - /** - * Indicates that a field should be skipped. - */ - @Retention(RetentionPolicy.RUNTIME) - @Target({ElementType.FIELD}) - public @interface Ignore { - - } - - /** - * Dumps this configuration to list of strings using {@link #dumpConfig(Object)}. - * - * @return configuration dump - */ - public List dumpConfig() { - return dumpConfig(this); - } - - /** - * Creates TOML configuration from supplied
dumpable
object. - * - * @param dumpable object which is going to be dumped - * @return string list of configuration file lines - * @throws RuntimeException if reading field value(s) fail - */ - private static List dumpConfig(Object dumpable) { - List lines = new ArrayList<>(); - try { - for (Field field : dumpable.getClass().getDeclaredFields()) { - // Skip fields with @Ignore annotation - if (field.getAnnotation(Ignore.class) != null) { - continue; - } - - // Make field accessible - field.setAccessible(true); - - // Add comments - Comment comment = field.getAnnotation(Comment.class); - if (comment != null) { - for (String line : comment.value()) { - lines.add("# " + line); - } - } - - // Get a key name for config. Use field name if @ConfigKey annotation is not present. - ConfigKey key = field.getAnnotation(ConfigKey.class); - final String name = escapeKeyIfNeeded(key == null ? field.getName() : key.value()); - - Object value = field.get(dumpable); - - // Check if field is table. - Table table = field.getAnnotation(Table.class); - if (table != null) { - lines.add(table.value()); // Write [name] - lines.addAll(dumpConfig(value)); // Dump fields of table - continue; - } - - if (field.getAnnotation(IsMap.class) != null) { // Check if field is a map - @SuppressWarnings("unchecked") - Map map = (Map) value; - for (Entry entry : map.entrySet()) { - lines.add(escapeKeyIfNeeded(entry.getKey()) + " = " + serialize(entry.getValue())); - } - lines.add(""); // Add empty line - continue; - } - - // Check if field is a byte[] representation of a string - if (field.getAnnotation(StringAsBytes.class) != null) { - value = new String((byte[]) value, StandardCharsets.UTF_8); - } - - // Save field to config - lines.add(name + " = " + serialize(value)); - lines.add(""); // Add empty line - } - } catch (IllegalAccessException | IllegalArgumentException | SecurityException e) { - throw new RuntimeException("Could not dump configuration", e); - } - - return lines; - } - - /** - * Serializes
value
so it can be parsed as a TOML value. - * - * @param value object to serialize - * @return Serialized object - */ - private static String serialize(Object value) { - if (value instanceof List) { - List listValue = (List) value; - if (listValue.isEmpty()) { - return "[]"; - } - - StringBuilder m = new StringBuilder(); - m.append("["); - - for (Object obj : listValue) { - m.append(System.lineSeparator()).append(" ").append(serialize(obj)).append(","); - } - - m.deleteCharAt(m.length() - 1).append(System.lineSeparator()).append("]"); - return m.toString(); - } - - if (value instanceof Enum) { - value = value.toString(); - } - - if (value instanceof String) { - return writeString((String) value); - } - - return value != null ? value.toString() : "null"; - } - - protected static String escapeKeyIfNeeded(String key) { - if ((key.contains(".") || key.contains(" ")) - && !(key.indexOf('"') == 0 && key.lastIndexOf('"') == (key.length() - 1))) { - return '"' + key + '"'; - } - return key; - } - - private static String writeString(String str) { - if (str.isEmpty()) { - return "\"\""; - } - - // According to the TOML specification (https://toml.io/en/v1.0.0-rc.1#section-7): - // - // Any Unicode character may be used except those that must be escaped: quotation mark, - // backslash, and the control characters other than tab (U+0000 to U+0008, U+000A to U+001F, - // U+007F). - if (STRING_NEEDS_ESCAPE.matcher(str).find()) { - return "'" + str + "'"; - } else { - return "\"" + str.replace("\n", "\\n") + "\""; - } - } - - protected static String unescapeKeyIfNeeded(String key) { - int lastIndex; - if (key.indexOf('"') == 0 && (lastIndex = key.lastIndexOf('"')) == (key.length() - 1)) { - return key.substring(1, lastIndex); - } - return key; - } - - /** - * Writes list of strings to file. - * - * @param lines list of strings to write - * @param to Path of file where lines should be written - * @throws IOException if error occurred during writing - * @throws IllegalArgumentException if
lines
is empty list - */ - public static void saveConfig(List lines, Path to) throws IOException { - if (lines.isEmpty()) { - throw new IllegalArgumentException("lines cannot be empty"); - } - - Path temp = to.toAbsolutePath().getParent().resolve(to.getFileName().toString() + "__tmp"); - Files.write(temp, lines, StandardCharsets.UTF_8, StandardOpenOption.CREATE); - try { - Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING, StandardCopyOption.ATOMIC_MOVE); - } catch (AtomicMoveNotSupportedException e) { - Files.move(temp, to, StandardCopyOption.REPLACE_EXISTING); - } - } -} 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 bac98ed1b..79e4c2d2b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/config/VelocityConfiguration.java @@ -1,11 +1,16 @@ package com.velocitypowered.proxy.config; +import com.electronwill.nightconfig.core.CommentedConfig; +import com.electronwill.nightconfig.core.Config; +import com.electronwill.nightconfig.core.UnmodifiableConfig; +import com.electronwill.nightconfig.core.file.CommentedFileConfig; import com.google.common.base.MoreObjects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.moandjiezana.toml.Toml; import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.util.Favicon; +import com.velocitypowered.proxy.Velocity; import com.velocitypowered.proxy.util.AddressUtil; import java.io.IOException; import java.io.Reader; @@ -25,116 +30,31 @@ import java.util.UUID; import net.kyori.text.Component; import net.kyori.text.serializer.gson.GsonComponentSerializer; import net.kyori.text.serializer.legacy.LegacyComponentSerializer; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.Nullable; -public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfig { +public class VelocityConfiguration implements ProxyConfig { - @Comment("Config version. Do not change this") - @ConfigKey("config-version") - private final String configVersion = "1.0"; + private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class); - @Comment("What port should the proxy be bound to? By default, we'll bind to all addresses on" - + " port 25577.") private String bind = "0.0.0.0:25577"; - - @Comment({"What should be the MOTD? This gets displayed when the player adds your server to", - "their server list. Legacy color codes and JSON are accepted."}) private String motd = "&3A Velocity Server"; - - @Comment({ - "What should we display for the maximum number of players? (Velocity does not support a cap", - "on the number of players online.)" - }) - @ConfigKey("show-max-players") private int showMaxPlayers = 500; - - @Comment("Should we authenticate players with Mojang? By default, this is on.") - @ConfigKey("online-mode") private boolean onlineMode = true; - - @Comment({ - "If client's ISP/AS sent from this proxy is different from the one from Mojang's", - "authentication server, the player is kicked. This disallows some VPN and proxy", - "connections but is a weak form of protection." - }) - @ConfigKey("prevent-client-proxy-connections") private boolean preventClientProxyConnections = false; - - @Comment({ - "Should we forward IP addresses and other data to backend servers?", - "Available options:", - "- \"none\": No forwarding will be done. All players will appear to be connecting", - " from the proxy and will have offline-mode UUIDs.", - "- \"legacy\": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this", - " if you run servers using Minecraft 1.12 or lower.", - "- \"bungeeguard\": Forward player IPs and UUIDs in a format supported by the BungeeGuard", - " plugin. Use this if you run servers using Minecraft 1.12 or lower, and are", - " unable to implement network level firewalling (on a shared host).", - "- \"modern\": Forward player IPs and UUIDs as part of the login process using", - " Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher." - }) - @ConfigKey("player-info-forwarding-mode") private PlayerInfoForwarding playerInfoForwardingMode = PlayerInfoForwarding.NONE; - - @StringAsBytes - @Comment("If you are using modern or BungeeGuard IP forwarding, configure an unique secret here.") - @ConfigKey("forwarding-secret") private byte[] forwardingSecret = generateRandomString(12).getBytes(StandardCharsets.UTF_8); - - @Comment({ - "Announce whether or not your server supports Forge. If you run a modded server, we", - "suggest turning this on.", - "", - "If your network runs one modpack consistently, consider using ping-passthrough = \"mods\"", - "instead for a nicer display in the server list." - }) - @ConfigKey("announce-forge") private boolean announceForge = false; - - @Comment({"If enabled (default is false) and the proxy is in online mode, Velocity will kick", - "any existing player who is online if a duplicate connection attempt is made."}) - @ConfigKey("kick-existing-players") private boolean onlineModeKickExistingPlayers = false; - - @Comment({ - "Should Velocity pass server list ping requests to a backend server?", - "Available options:", - "- \"disabled\": No pass-through will be done. The velocity.toml and server-icon.png", - " will determine the initial server list ping response.", - "- \"mods\": Passes only the mod list from your backend server into the response.", - " The first server in your try list (or forced host) with a mod list will be", - " used. If no backend servers can be contacted, Velocity won't display any", - " mod information.", - "- \"description\": Uses the description and mod list from the backend server. The first", - " server in the try (or forced host) list that responds is used for the", - " description and mod list.", - "- \"all\": Uses the backend server's response as the proxy response. The Velocity", - " configuration is used if no servers could be contacted." - }) - @ConfigKey("ping-passthrough") private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED; - - @Table("[servers]") private final Servers servers; - - @Table("[forced-hosts]") private final ForcedHosts forcedHosts; - - @Table("[advanced]") private final Advanced advanced; - - @Table("[query]") private final Query query; - - @Table("[metrics]") private final Metrics metrics; - - @Ignore private @MonotonicNonNull Component motdAsComponent; - - @Ignore private @Nullable Favicon favicon; private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, @@ -172,7 +92,6 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi */ public boolean validate() { boolean valid = true; - Logger logger = AnnotatedConfig.getLogger(); if (bind.isEmpty()) { logger.error("'bind' option is empty."); @@ -280,7 +199,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi try { this.favicon = Favicon.create(faviconPath); } catch (Exception e) { - getLogger().info("Unable to load your server-icon.png, continuing without it.", e); + logger.info("Unable to load your server-icon.png, continuing without it.", e); } } } @@ -430,7 +349,6 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi @Override public String toString() { return MoreObjects.toStringHelper(this) - .add("configVersion", configVersion) .add("bind", bind) .add("motd", motd) .add("showMaxPlayers", showMaxPlayers) @@ -453,45 +371,67 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi * @throws IOException if we could not read from the {@code path}. */ public static VelocityConfiguration read(Path path) throws IOException { - Toml toml; - if (!path.toFile().exists()) { - getLogger().info("No velocity.toml found, creating one for you..."); - return new VelocityConfiguration(new Servers(), new ForcedHosts(), new Advanced(), - new Query(), new Metrics()); - } else { - try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { - toml = new Toml().read(reader); - } + boolean mustResave = false; + CommentedFileConfig config = CommentedFileConfig.builder(path) + .defaultResource("/default-velocity.toml") + .autosave() + .preserveInsertionOrder() + .sync() + .build(); + config.load(); + + // Handle any cases where the config needs to be saved again + byte[] forwardingSecret; + String forwardingSecretString = config.get("forwarding-secret"); + if (forwardingSecretString.isEmpty()) { + forwardingSecretString = generateRandomString(12); + config.set("forwarding-secret", forwardingSecretString); + mustResave = true; + } + forwardingSecret = forwardingSecretString.getBytes(StandardCharsets.UTF_8); + + if (config.get("metrics.id").isEmpty()) { + config.set("metrics.id", UUID.randomUUID().toString()); + mustResave = true; } - Servers servers = new Servers(toml.getTable("servers")); - ForcedHosts forcedHosts = new ForcedHosts(toml.getTable("forced-hosts")); - Advanced advanced = new Advanced(toml.getTable("advanced")); - Query query = new Query(toml.getTable("query")); - Metrics metrics = new Metrics(toml.getTable("metrics")); - byte[] forwardingSecret = toml.getString("forwarding-secret", generateRandomString(12)) - .getBytes(StandardCharsets.UTF_8); + if (mustResave) { + config.save(); + } - String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN") - .toUpperCase(Locale.US); - String passThroughName = toml.getString("ping-passthrough", "DISABLED") - .toUpperCase(Locale.US); + // Read the rest of the config + CommentedConfig serversConfig = config.get("servers"); + CommentedConfig forcedHostsConfig = config.get("forced-hosts"); + CommentedConfig advancedConfig = config.get("advanced"); + CommentedConfig queryConfig = config.get("query"); + CommentedConfig metricsConfig = config.get("metrics"); + PlayerInfoForwarding forwardingMode = config.getEnumOrElse("player-info-forwarding-mode", + PlayerInfoForwarding.NONE); + PingPassthroughMode pingPassthroughMode = config.getEnumOrElse("ping-passthrough", + PingPassthroughMode.DISABLED); + + String bind = config.getOrElse("bind", "0.0.0.0:25577"); + String motd = config.getOrElse("motd", "&3A Velocity Server"); + int maxPlayers = config.getIntOrElse("show-max-players", 500); + Boolean onlineMode = config.getOrElse("online-mode", true); + Boolean announceForge = config.getOrElse("announce-forge", true); + Boolean kickExisting = config.getOrElse("kick-existing-players", false); return new VelocityConfiguration( - toml.getString("bind", "0.0.0.0:25577"), - toml.getString("motd", "&3A Velocity Server"), - toml.getLong("show-max-players", 500L).intValue(), - toml.getBoolean("online-mode", true), - toml.getBoolean("announce-forge", false), - PlayerInfoForwarding.valueOf(forwardingModeName), + bind, + motd, + maxPlayers, + onlineMode, + announceForge, + forwardingMode, forwardingSecret, - toml.getBoolean("kick-existing-players", false), - PingPassthroughMode.valueOf(passThroughName), - servers, - forcedHosts, - advanced, - query, - metrics + kickExisting, + pingPassthroughMode, + new Servers(serversConfig), + new ForcedHosts(forcedHostsConfig), + new Advanced(advancedConfig), + new Query(queryConfig), + new Metrics(metricsConfig) ); } @@ -511,29 +451,22 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi private static class Servers { - @IsMap - @Comment({"Configure your servers here. Each key represents the server's name, and the value", - "represents the IP address of the server to connect to."}) private Map servers = ImmutableMap.of( "lobby", "127.0.0.1:30066", "factions", "127.0.0.1:30067", "minigames", "127.0.0.1:30068" ); - - @Comment("In what order we should try servers when a player logs in or is kicked from a" - + "server.") - @ConfigKey("try") private List attemptConnectionOrder = Arrays.asList("lobby"); private Servers() { } - private Servers(Toml toml) { - if (toml != null) { + private Servers(CommentedConfig config) { + if (config != null) { Map servers = new HashMap<>(); - for (Map.Entry entry : toml.entrySet()) { + for (UnmodifiableConfig.Entry entry : config.entrySet()) { if (entry.getValue() instanceof String) { - servers.put(cleanServerName(entry.getKey()), (String) entry.getValue()); + servers.put(cleanServerName(entry.getKey()), entry.getValue()); } else { if (!entry.getKey().equalsIgnoreCase("try")) { throw new IllegalArgumentException( @@ -542,7 +475,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi } } this.servers = ImmutableMap.copyOf(servers); - this.attemptConnectionOrder = toml.getList("try", attemptConnectionOrder); + this.attemptConnectionOrder = config.getOrElse("try", attemptConnectionOrder); } } @@ -591,8 +524,6 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi private static class ForcedHosts { - @IsMap - @Comment("Configure your forced hosts here.") private Map> forcedHosts = ImmutableMap.of( "lobby.example.com", ImmutableList.of("lobby"), "factions.example.com", ImmutableList.of("factions"), @@ -602,16 +533,14 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi private ForcedHosts() { } - private ForcedHosts(Toml toml) { - if (toml != null) { + private ForcedHosts(CommentedConfig config) { + if (config != null) { Map> forcedHosts = new HashMap<>(); - for (Map.Entry entry : toml.entrySet()) { + for (UnmodifiableConfig.Entry entry : config.entrySet()) { if (entry.getValue() instanceof String) { - forcedHosts.put(unescapeKeyIfNeeded(entry.getKey()), ImmutableList.of( - (String) entry.getValue())); + forcedHosts.put(entry.getKey(), ImmutableList.of(entry.getValue())); } else if (entry.getValue() instanceof List) { - forcedHosts.put(unescapeKeyIfNeeded(entry.getKey()), - ImmutableList.copyOf((List) entry.getValue())); + forcedHosts.put(entry.getKey(), ImmutableList.copyOf((List) entry.getValue())); } else { throw new IllegalStateException( "Invalid value of type " + entry.getValue().getClass() + " in forced hosts!"); @@ -643,64 +572,30 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi private static class Advanced { - @Comment({ - "How large a Minecraft packet has to be before we compress it. Setting this to zero will", - "compress all packets, and setting it to -1 will disable compression entirely." - }) - @ConfigKey("compression-threshold") private int compressionThreshold = 256; - - @Comment({"How much compression should be done (from 0-9). The default is -1, which uses the", - "default level of 6."}) - @ConfigKey("compression-level") private int compressionLevel = -1; - - @Comment({ - "How fast (in milliseconds) are clients allowed to connect after the last connection? By", - "default, this is three seconds. Disable this by setting this to 0." - }) - @ConfigKey("login-ratelimit") private int loginRatelimit = 3000; - - @Comment({ - "Specify a custom timeout for connection timeouts here. The default is five seconds."}) - @ConfigKey("connection-timeout") private int connectionTimeout = 5000; - - @Comment({"Specify a read timeout for connections here. The default is 30 seconds."}) - @ConfigKey("read-timeout") private int readTimeout = 30000; - - @Comment("Enables compatibility with HAProxy.") - @ConfigKey("proxy-protocol") private boolean proxyProtocol = false; - - @Comment("Enables TCP fast open support on the proxy. Requires the proxy to run on Linux.") - @ConfigKey("tcp-fast-open") private boolean tcpFastOpen = false; - - @Comment("Enables BungeeCord plugin messaging channel support on Velocity.") - @ConfigKey("bungee-plugin-message-channel") private boolean bungeePluginMessageChannel = true; - - @Comment("Shows ping requests to the proxy from clients.") - @ConfigKey("show-ping-requests") private boolean showPingRequests = false; private Advanced() { } - private Advanced(Toml toml) { - if (toml != null) { - this.compressionThreshold = toml.getLong("compression-threshold", 256L).intValue(); - this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); - this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); - this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue(); - this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); - this.proxyProtocol = toml.getBoolean("proxy-protocol", false); - this.tcpFastOpen = toml.getBoolean("tcp-fast-open", false); - this.bungeePluginMessageChannel = toml.getBoolean("bungee-plugin-message-channel", true); - this.showPingRequests = toml.getBoolean("show-ping-requests", false); + private Advanced(CommentedConfig config) { + if (config != null) { + this.compressionThreshold = config.getIntOrElse("compression-threshold", 256); + this.compressionLevel = config.getIntOrElse("compression-level", -1); + this.loginRatelimit = config.getIntOrElse("login-ratelimit", 3000); + this.connectionTimeout = config.getIntOrElse("connection-timeout", 5000); + this.readTimeout = config.getIntOrElse("read-timeout", 30000); + this.proxyProtocol = config.getOrElse("proxy-protocol", false); + this.tcpFastOpen = config.getOrElse("tcp-fast-open", false); + this.bungeePluginMessageChannel = config.getOrElse("bungee-plugin-message-channel", true); + this.showPingRequests = config.getOrElse("show-ping-requests", false); } } @@ -758,20 +653,9 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi private static class Query { - @Comment("Whether to enable responding to GameSpy 4 query responses or not.") - @ConfigKey("enabled") private boolean queryEnabled = false; - - @Comment("If query is enabled, on what port should the query protocol listen on?") - @ConfigKey("port") private int queryPort = 25577; - - @Comment("This is the map name that is reported to the query services.") - @ConfigKey("map") private String queryMap = "Velocity"; - - @Comment("Whether plugins should be shown in query response by default or not") - @ConfigKey("show-plugins") private boolean showPlugins = false; private Query() { @@ -784,12 +668,12 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi this.showPlugins = showPlugins; } - private Query(Toml toml) { - if (toml != null) { - this.queryEnabled = toml.getBoolean("enabled", false); - this.queryPort = toml.getLong("port", 25577L).intValue(); - this.queryMap = toml.getString("map", "Velocity"); - this.showPlugins = toml.getBoolean("show-plugins", false); + private Query(CommentedConfig config) { + if (config != null) { + this.queryEnabled = config.getOrElse("enabled", false); + this.queryPort = config.getIntOrElse("port", 25577); + this.queryMap = config.getOrElse("map", "Velocity"); + this.showPlugins = config.getOrElse("show-plugins", false); } } @@ -821,34 +705,21 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi } public static class Metrics { - - @Comment({"Whether metrics will be reported to bStats (https://bstats.org).", - "bStats collects some basic information, like how many people use Velocity and their", - "player count. We recommend keeping bStats enabled, but if you're not comfortable with", - "this, you can turn this setting off. There is no performance penalty associated with", - "having metrics enabled, and data sent to bStats can't identify your server."}) - @ConfigKey("enabled") private boolean enabled = true; - - @Comment("A unique, anonymous ID to identify this proxy with.") - @ConfigKey("id") private String id = UUID.randomUUID().toString(); - - @ConfigKey("log-failure") private boolean logFailure = false; - @Ignore private boolean fromConfig; private Metrics() { this.fromConfig = false; } - private Metrics(Toml toml) { + private Metrics(CommentedConfig toml) { if (toml != null) { - this.enabled = toml.getBoolean("enabled", false); - this.id = toml.getString("id", UUID.randomUUID().toString()); - this.logFailure = toml.getBoolean("log-failure", false); + this.enabled = toml.getOrElse("enabled", false); + this.id = toml.getOrElse("id", UUID.randomUUID().toString()); + this.logFailure = toml.getOrElse("log-failure", false); this.fromConfig = true; } } diff --git a/proxy/src/main/resources/default-velocity.toml b/proxy/src/main/resources/default-velocity.toml new file mode 100644 index 000000000..a83ddba94 --- /dev/null +++ b/proxy/src/main/resources/default-velocity.toml @@ -0,0 +1,145 @@ +# Config version. Do not change this +config-version = "1.0" + +# What port should the proxy be bound to? By default, we'll bind to all addresses on port 25577. +bind = "0.0.0.0:25577" + +# What should be the MOTD? This gets displayed when the player adds your server to +# their server list. Legacy color codes and JSON are accepted. +motd = "&3A Velocity Server" + +# What should we display for the maximum number of players? (Velocity does not support a cap +# on the number of players online.) +show-max-players = 500 + +# Should we authenticate players with Mojang? By default, this is on. +online-mode = true + +# If client's ISP/AS sent from this proxy is different from the one from Mojang's +# authentication server, the player is kicked. This disallows some VPN and proxy +# connections but is a weak form of protection. +prevent-client-proxy-connections = false + +# Should we forward IP addresses and other data to backend servers? +# Available options: +# - "none": No forwarding will be done. All players will appear to be connecting +# from the proxy and will have offline-mode UUIDs. +# - "legacy": Forward player IPs and UUIDs in a BungeeCord-compatible format. Use this +# if you run servers using Minecraft 1.12 or lower. +# - "bungeeguard": Forward player IPs and UUIDs in a format supported by the BungeeGuard +# plugin. Use this if you run servers using Minecraft 1.12 or lower, and are +# unable to implement network level firewalling (on a shared host). +# - "modern": Forward player IPs and UUIDs as part of the login process using +# Velocity's native forwarding. Only applicable for Minecraft 1.13 or higher. +player-info-forwarding-mode = "NONE" + +# If you are using modern or BungeeGuard IP forwarding, configure an unique secret here. +forwarding-secret = "" + +# Announce whether or not your server supports Forge. If you run a modded server, we +# suggest turning this on. +# +# If your network runs one modpack consistently, consider using ping-passthrough = "mods" +# instead for a nicer display in the server list. +announce-forge = false + +# If enabled (default is false) and the proxy is in online mode, Velocity will kick +# any existing player who is online if a duplicate connection attempt is made. +kick-existing-players = false + +# Should Velocity pass server list ping requests to a backend server? +# Available options: +# - "disabled": No pass-through will be done. The velocity.toml and server-icon.png +# will determine the initial server list ping response. +# - "mods": Passes only the mod list from your backend server into the response. +# The first server in your try list (or forced host) with a mod list will be +# used. If no backend servers can be contacted, Velocity won't display any +# mod information. +# - "description": Uses the description and mod list from the backend server. The first +# server in the try (or forced host) list that responds is used for the +# description and mod list. +# - "all": Uses the backend server's response as the proxy response. The Velocity +# configuration is used if no servers could be contacted. +ping-passthrough = "DISABLED" + +[servers] +# Configure your servers here. Each key represents the server's name, and the value +# represents the IP address of the server to connect to. +lobby = "127.0.0.1:30066" +factions = "127.0.0.1:30067" +minigames = "127.0.0.1:30068" + +# In what order we should try servers when a player logs in or is kicked from aserver. +try = [ + "lobby" +] + +[forced-hosts] +# Configure your forced hosts here. +"lobby.example.com" = [ + "lobby" +] +"factions.example.com" = [ + "factions" +] +"minigames.example.com" = [ + "minigames" +] + +[advanced] +# How large a Minecraft packet has to be before we compress it. Setting this to zero will +# compress all packets, and setting it to -1 will disable compression entirely. +compression-threshold = 256 + +# How much compression should be done (from 0-9). The default is -1, which uses the +# default level of 6. +compression-level = -1 + +# How fast (in milliseconds) are clients allowed to connect after the last connection? By +# default, this is three seconds. Disable this by setting this to 0. +login-ratelimit = 3000 + +# Specify a custom timeout for connection timeouts here. The default is five seconds. +connection-timeout = 5000 + +# Specify a read timeout for connections here. The default is 30 seconds. +read-timeout = 30000 + +# Enables compatibility with HAProxy. +proxy-protocol = false + +# Enables TCP fast open support on the proxy. Requires the proxy to run on Linux. +tcp-fast-open = false + +# Enables BungeeCord plugin messaging channel support on Velocity. +bungee-plugin-message-channel = true + +# Shows ping requests to the proxy from clients. +show-ping-requests = false + +[query] +# Whether to enable responding to GameSpy 4 query responses or not. +enabled = false + +# If query is enabled, on what port should the query protocol listen on? +port = 25577 + +# This is the map name that is reported to the query services. +map = "Velocity" + +# Whether plugins should be shown in query response by default or not +show-plugins = false + +[metrics] +# Whether metrics will be reported to bStats (https://bstats.org). +# bStats collects some basic information, like how many people use Velocity and their +# player count. We recommend keeping bStats enabled, but if you're not comfortable with +# this, you can turn this setting off. There is no performance penalty associated with +# having metrics enabled, and data sent to bStats can't identify your server. +enabled = true + +# A unique, anonymous ID to identify this proxy with. +id = "" + +log-failure = false +