3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-11-17 05:20:14 +01:00

Switch Velocity from existing toml4j+homebrew TOML serializer to night-config.

This allows us to allow many more valid configurations and allows us to eliminate a bunch of ugly hacks.
Dieser Commit ist enthalten in:
Andrew Steinborn 2020-06-24 21:31:39 -04:00
Ursprung 4bebda2549
Commit 28d2366c73
5 geänderte Dateien mit 242 neuen und 496 gelöschten Zeilen

Datei anzeigen

@ -71,6 +71,8 @@ dependencies {
compile 'com.spotify:completable-futures:0.3.2' 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-api:${junitVersion}"
testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}" testCompile "org.junit.jupiter:junit-jupiter-engine:${junitVersion}"
} }

Datei anzeigen

@ -25,7 +25,6 @@ import com.velocitypowered.proxy.command.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand; import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand; import com.velocitypowered.proxy.command.VelocityCommand;
import com.velocitypowered.proxy.command.VelocityCommandManager; import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.config.AnnotatedConfig;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole; 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.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable; import org.checkerframework.checker.nullness.qual.Nullable;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public class VelocityServer implements ProxyServer { public class VelocityServer implements ProxyServer {
@ -185,9 +183,6 @@ public class VelocityServer implements ProxyServer {
Path configPath = Paths.get("velocity.toml"); Path configPath = Paths.get("velocity.toml");
configuration = VelocityConfiguration.read(configPath); configuration = VelocityConfiguration.read(configPath);
// Resave config to add new values
AnnotatedConfig.saveConfig(configuration.dumpConfig(), configPath);
if (!configuration.validate()) { if (!configuration.validate()) {
logger.error("Your configuration is invalid. Velocity will not start up until the errors " logger.error("Your configuration is invalid. Velocity will not start up until the errors "
+ "are resolved."); + "are resolved.");

Datei anzeigen

@ -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<String> dumpConfig() {
return dumpConfig(this);
}
/**
* Creates TOML configuration from supplied <pre>dumpable</pre> 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<String> dumpConfig(Object dumpable) {
List<String> 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<String, ?> map = (Map<String, ?>) value;
for (Entry<String, ?> 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 <pre>value</pre> 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 <pre>lines</pre> is empty list
*/
public static void saveConfig(List<String> 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);
}
}
}

Datei anzeigen

@ -1,11 +1,16 @@
package com.velocitypowered.proxy.config; 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.base.MoreObjects;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.moandjiezana.toml.Toml; import com.moandjiezana.toml.Toml;
import com.velocitypowered.api.proxy.config.ProxyConfig; import com.velocitypowered.api.proxy.config.ProxyConfig;
import com.velocitypowered.api.util.Favicon; import com.velocitypowered.api.util.Favicon;
import com.velocitypowered.proxy.Velocity;
import com.velocitypowered.proxy.util.AddressUtil; import com.velocitypowered.proxy.util.AddressUtil;
import java.io.IOException; import java.io.IOException;
import java.io.Reader; import java.io.Reader;
@ -25,116 +30,31 @@ import java.util.UUID;
import net.kyori.text.Component; import net.kyori.text.Component;
import net.kyori.text.serializer.gson.GsonComponentSerializer; import net.kyori.text.serializer.gson.GsonComponentSerializer;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer; import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.Nullable; 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") private static final Logger logger = LogManager.getLogger(VelocityConfiguration.class);
@ConfigKey("config-version")
private final String configVersion = "1.0";
@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"; 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"; 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; private int showMaxPlayers = 500;
@Comment("Should we authenticate players with Mojang? By default, this is on.")
@ConfigKey("online-mode")
private boolean onlineMode = true; 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; 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; 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); 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; 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; 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; private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
@Table("[servers]")
private final Servers servers; private final Servers servers;
@Table("[forced-hosts]")
private final ForcedHosts forcedHosts; private final ForcedHosts forcedHosts;
@Table("[advanced]")
private final Advanced advanced; private final Advanced advanced;
@Table("[query]")
private final Query query; private final Query query;
@Table("[metrics]")
private final Metrics metrics; private final Metrics metrics;
@Ignore
private @MonotonicNonNull Component motdAsComponent; private @MonotonicNonNull Component motdAsComponent;
@Ignore
private @Nullable Favicon favicon; private @Nullable Favicon favicon;
private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced, private VelocityConfiguration(Servers servers, ForcedHosts forcedHosts, Advanced advanced,
@ -172,7 +92,6 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
*/ */
public boolean validate() { public boolean validate() {
boolean valid = true; boolean valid = true;
Logger logger = AnnotatedConfig.getLogger();
if (bind.isEmpty()) { if (bind.isEmpty()) {
logger.error("'bind' option is empty."); logger.error("'bind' option is empty.");
@ -280,7 +199,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
try { try {
this.favicon = Favicon.create(faviconPath); this.favicon = Favicon.create(faviconPath);
} catch (Exception e) { } 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 @Override
public String toString() { public String toString() {
return MoreObjects.toStringHelper(this) return MoreObjects.toStringHelper(this)
.add("configVersion", configVersion)
.add("bind", bind) .add("bind", bind)
.add("motd", motd) .add("motd", motd)
.add("showMaxPlayers", showMaxPlayers) .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}. * @throws IOException if we could not read from the {@code path}.
*/ */
public static VelocityConfiguration read(Path path) throws IOException { public static VelocityConfiguration read(Path path) throws IOException {
Toml toml; boolean mustResave = false;
if (!path.toFile().exists()) { CommentedFileConfig config = CommentedFileConfig.builder(path)
getLogger().info("No velocity.toml found, creating one for you..."); .defaultResource("/default-velocity.toml")
return new VelocityConfiguration(new Servers(), new ForcedHosts(), new Advanced(), .autosave()
new Query(), new Metrics()); .preserveInsertionOrder()
} else { .sync()
try (Reader reader = Files.newBufferedReader(path, StandardCharsets.UTF_8)) { .build();
toml = new Toml().read(reader); 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.<String>get("metrics.id").isEmpty()) {
config.set("metrics.id", UUID.randomUUID().toString());
mustResave = true;
} }
Servers servers = new Servers(toml.getTable("servers")); if (mustResave) {
ForcedHosts forcedHosts = new ForcedHosts(toml.getTable("forced-hosts")); config.save();
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);
String forwardingModeName = toml.getString("player-info-forwarding-mode", "MODERN") // Read the rest of the config
.toUpperCase(Locale.US); CommentedConfig serversConfig = config.get("servers");
String passThroughName = toml.getString("ping-passthrough", "DISABLED") CommentedConfig forcedHostsConfig = config.get("forced-hosts");
.toUpperCase(Locale.US); 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( return new VelocityConfiguration(
toml.getString("bind", "0.0.0.0:25577"), bind,
toml.getString("motd", "&3A Velocity Server"), motd,
toml.getLong("show-max-players", 500L).intValue(), maxPlayers,
toml.getBoolean("online-mode", true), onlineMode,
toml.getBoolean("announce-forge", false), announceForge,
PlayerInfoForwarding.valueOf(forwardingModeName), forwardingMode,
forwardingSecret, forwardingSecret,
toml.getBoolean("kick-existing-players", false), kickExisting,
PingPassthroughMode.valueOf(passThroughName), pingPassthroughMode,
servers, new Servers(serversConfig),
forcedHosts, new ForcedHosts(forcedHostsConfig),
advanced, new Advanced(advancedConfig),
query, new Query(queryConfig),
metrics new Metrics(metricsConfig)
); );
} }
@ -511,29 +451,22 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
private static class Servers { 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<String, String> servers = ImmutableMap.of( private Map<String, String> servers = ImmutableMap.of(
"lobby", "127.0.0.1:30066", "lobby", "127.0.0.1:30066",
"factions", "127.0.0.1:30067", "factions", "127.0.0.1:30067",
"minigames", "127.0.0.1:30068" "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<String> attemptConnectionOrder = Arrays.asList("lobby"); private List<String> attemptConnectionOrder = Arrays.asList("lobby");
private Servers() { private Servers() {
} }
private Servers(Toml toml) { private Servers(CommentedConfig config) {
if (toml != null) { if (config != null) {
Map<String, String> servers = new HashMap<>(); Map<String, String> servers = new HashMap<>();
for (Map.Entry<String, Object> entry : toml.entrySet()) { for (UnmodifiableConfig.Entry entry : config.entrySet()) {
if (entry.getValue() instanceof String) { if (entry.getValue() instanceof String) {
servers.put(cleanServerName(entry.getKey()), (String) entry.getValue()); servers.put(cleanServerName(entry.getKey()), entry.getValue());
} else { } else {
if (!entry.getKey().equalsIgnoreCase("try")) { if (!entry.getKey().equalsIgnoreCase("try")) {
throw new IllegalArgumentException( throw new IllegalArgumentException(
@ -542,7 +475,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
} }
} }
this.servers = ImmutableMap.copyOf(servers); 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 { private static class ForcedHosts {
@IsMap
@Comment("Configure your forced hosts here.")
private Map<String, List<String>> forcedHosts = ImmutableMap.of( private Map<String, List<String>> forcedHosts = ImmutableMap.of(
"lobby.example.com", ImmutableList.of("lobby"), "lobby.example.com", ImmutableList.of("lobby"),
"factions.example.com", ImmutableList.of("factions"), "factions.example.com", ImmutableList.of("factions"),
@ -602,16 +533,14 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
private ForcedHosts() { private ForcedHosts() {
} }
private ForcedHosts(Toml toml) { private ForcedHosts(CommentedConfig config) {
if (toml != null) { if (config != null) {
Map<String, List<String>> forcedHosts = new HashMap<>(); Map<String, List<String>> forcedHosts = new HashMap<>();
for (Map.Entry<String, Object> entry : toml.entrySet()) { for (UnmodifiableConfig.Entry entry : config.entrySet()) {
if (entry.getValue() instanceof String) { if (entry.getValue() instanceof String) {
forcedHosts.put(unescapeKeyIfNeeded(entry.getKey()), ImmutableList.of( forcedHosts.put(entry.getKey(), ImmutableList.of(entry.getValue()));
(String) entry.getValue()));
} else if (entry.getValue() instanceof List) { } else if (entry.getValue() instanceof List) {
forcedHosts.put(unescapeKeyIfNeeded(entry.getKey()), forcedHosts.put(entry.getKey(), ImmutableList.copyOf((List<String>) entry.getValue()));
ImmutableList.copyOf((List<String>) entry.getValue()));
} else { } else {
throw new IllegalStateException( throw new IllegalStateException(
"Invalid value of type " + entry.getValue().getClass() + " in forced hosts!"); "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 { 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; 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; 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; 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; private int connectionTimeout = 5000;
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
@ConfigKey("read-timeout")
private int readTimeout = 30000; private int readTimeout = 30000;
@Comment("Enables compatibility with HAProxy.")
@ConfigKey("proxy-protocol")
private boolean proxyProtocol = false; 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; private boolean tcpFastOpen = false;
@Comment("Enables BungeeCord plugin messaging channel support on Velocity.")
@ConfigKey("bungee-plugin-message-channel")
private boolean bungeePluginMessageChannel = true; private boolean bungeePluginMessageChannel = true;
@Comment("Shows ping requests to the proxy from clients.")
@ConfigKey("show-ping-requests")
private boolean showPingRequests = false; private boolean showPingRequests = false;
private Advanced() { private Advanced() {
} }
private Advanced(Toml toml) { private Advanced(CommentedConfig config) {
if (toml != null) { if (config != null) {
this.compressionThreshold = toml.getLong("compression-threshold", 256L).intValue(); this.compressionThreshold = config.getIntOrElse("compression-threshold", 256);
this.compressionLevel = toml.getLong("compression-level", -1L).intValue(); this.compressionLevel = config.getIntOrElse("compression-level", -1);
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue(); this.loginRatelimit = config.getIntOrElse("login-ratelimit", 3000);
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue(); this.connectionTimeout = config.getIntOrElse("connection-timeout", 5000);
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue(); this.readTimeout = config.getIntOrElse("read-timeout", 30000);
this.proxyProtocol = toml.getBoolean("proxy-protocol", false); this.proxyProtocol = config.getOrElse("proxy-protocol", false);
this.tcpFastOpen = toml.getBoolean("tcp-fast-open", false); this.tcpFastOpen = config.getOrElse("tcp-fast-open", false);
this.bungeePluginMessageChannel = toml.getBoolean("bungee-plugin-message-channel", true); this.bungeePluginMessageChannel = config.getOrElse("bungee-plugin-message-channel", true);
this.showPingRequests = toml.getBoolean("show-ping-requests", false); this.showPingRequests = config.getOrElse("show-ping-requests", false);
} }
} }
@ -758,20 +653,9 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
private static class Query { private static class Query {
@Comment("Whether to enable responding to GameSpy 4 query responses or not.")
@ConfigKey("enabled")
private boolean queryEnabled = false; private boolean queryEnabled = false;
@Comment("If query is enabled, on what port should the query protocol listen on?")
@ConfigKey("port")
private int queryPort = 25577; private int queryPort = 25577;
@Comment("This is the map name that is reported to the query services.")
@ConfigKey("map")
private String queryMap = "Velocity"; 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 boolean showPlugins = false;
private Query() { private Query() {
@ -784,12 +668,12 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
this.showPlugins = showPlugins; this.showPlugins = showPlugins;
} }
private Query(Toml toml) { private Query(CommentedConfig config) {
if (toml != null) { if (config != null) {
this.queryEnabled = toml.getBoolean("enabled", false); this.queryEnabled = config.getOrElse("enabled", false);
this.queryPort = toml.getLong("port", 25577L).intValue(); this.queryPort = config.getIntOrElse("port", 25577);
this.queryMap = toml.getString("map", "Velocity"); this.queryMap = config.getOrElse("map", "Velocity");
this.showPlugins = toml.getBoolean("show-plugins", false); this.showPlugins = config.getOrElse("show-plugins", false);
} }
} }
@ -821,34 +705,21 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
} }
public static class Metrics { 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; private boolean enabled = true;
@Comment("A unique, anonymous ID to identify this proxy with.")
@ConfigKey("id")
private String id = UUID.randomUUID().toString(); private String id = UUID.randomUUID().toString();
@ConfigKey("log-failure")
private boolean logFailure = false; private boolean logFailure = false;
@Ignore
private boolean fromConfig; private boolean fromConfig;
private Metrics() { private Metrics() {
this.fromConfig = false; this.fromConfig = false;
} }
private Metrics(Toml toml) { private Metrics(CommentedConfig toml) {
if (toml != null) { if (toml != null) {
this.enabled = toml.getBoolean("enabled", false); this.enabled = toml.getOrElse("enabled", false);
this.id = toml.getString("id", UUID.randomUUID().toString()); this.id = toml.getOrElse("id", UUID.randomUUID().toString());
this.logFailure = toml.getBoolean("log-failure", false); this.logFailure = toml.getOrElse("log-failure", false);
this.fromConfig = true; this.fromConfig = true;
} }
} }

Datei anzeigen

@ -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