Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions proxy/src/main/java/com/velocitypowered/proxy/ProxyOptions.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

import com.velocitypowered.api.proxy.server.ServerInfo;
import com.velocitypowered.proxy.util.AddressUtil;
import java.io.File;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Arrays;
Expand All @@ -43,6 +44,7 @@ public final class ProxyOptions {
private final @Nullable Boolean haproxy;
private final boolean ignoreConfigServers;
private final List<ServerInfo> servers;
private final List<File> extraPluginDirectories;

ProxyOptions(final String[] args) {
final OptionParser parser = new OptionParser();
Expand All @@ -64,13 +66,21 @@ public final class ProxyOptions {
final OptionSpec<Void> ignoreConfigServers = parser.accepts("ignore-config-servers",
"Skip registering servers from the config file. "
+ "Useful in dynamic setups or with the --add-server flag.");
final OptionSpec<File> extraPluginDirectories = parser.acceptsAll(List.of("add-plugin-dir", "add-extra-plugin-dir"),
"Specify paths to extra plugin directories to be loaded in addition to the plugins folder. "
+ "This argument can be specified multiple times, once for each extra plugin dir path.")
.withRequiredArg()
.ofType(File.class)
.defaultsTo(new File[] {})
.describedAs("Plugin directory");
final OptionSet set = parser.parse(args);

this.help = set.has(help);
this.port = port.value(set);
this.haproxy = haproxy.value(set);
this.servers = servers.values(set);
this.ignoreConfigServers = set.has(ignoreConfigServers);
this.extraPluginDirectories = extraPluginDirectories.values(set);

if (this.help) {
try {
Expand Down Expand Up @@ -101,6 +111,10 @@ public List<ServerInfo> getServers() {
return this.servers;
}

public List<File> getExtraPluginDirectories() {
return this.extraPluginDirectories;
}

private static class ServerInfoConverter implements ValueConverter<ServerInfo> {

@Override
Expand Down
19 changes: 9 additions & 10 deletions proxy/src/main/java/com/velocitypowered/proxy/VelocityServer.java
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
import io.netty.channel.Channel;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.InetAddress;
Expand All @@ -86,6 +87,7 @@
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
Expand Down Expand Up @@ -305,7 +307,7 @@ void start() {
ipAttemptLimiter = Ratelimiters.createWithMilliseconds(configuration.getLoginRatelimit());
commandRateLimiter = Ratelimiters.createWithMilliseconds(configuration.getCommandRatelimit());
tabCompleteRateLimiter = Ratelimiters.createWithMilliseconds(configuration.getTabCompleteRatelimit());
loadPlugins();
loadPlugins(options.getExtraPluginDirectories());

// Go ahead and fire the proxy initialization event. We block since plugins should have a chance
// to fully initialize before we accept any connections to the server.
Expand Down Expand Up @@ -419,23 +421,20 @@ private void doStartupConfigLoad() {
}
}

private void loadPlugins() {
private void loadPlugins(List<File> extraPluginDirectories) {
logger.info("Loading plugins...");

try {
Path pluginPath = Path.of("plugins");

if (!pluginPath.toFile().exists()) {
Files.createDirectory(pluginPath);
} else {
if (!pluginPath.toFile().isDirectory()) {
logger.warn("Plugin location {} is not a directory, continuing without loading plugins",
pluginPath);
return;
}

pluginManager.loadPlugins(pluginPath);
}

List<Path> pluginDirectories = new ArrayList<>();
pluginDirectories.add(pluginPath);
pluginDirectories.addAll(extraPluginDirectories.stream().map(File::toPath).toList());
pluginManager.loadPlugins(pluginDirectories, pluginPath);
} catch (Exception e) {
logger.error("Couldn't load plugins", e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,34 +85,54 @@ public void registerPlugin(PluginContainer plugin) {
* @param directory the directory to load from
* @throws IOException if we could not open the directory
*/
@Deprecated
public void loadPlugins(Path directory) throws IOException {
loadPlugins(Collections.singletonList(directory), directory);
}

/**
* Loads all plugins from the specified {@code directories}.
*
* @param directories the directories to load from
* @param baseDirectory the base directory to give plugins
* @throws IOException if we could not open the directory
*/
@SuppressFBWarnings(value = "RCN_REDUNDANT_NULLCHECK_WOULD_HAVE_BEEN_A_NPE",
justification = "I looked carefully and there's no way SpotBugs is right.")
public void loadPlugins(Path directory) throws IOException {
checkNotNull(directory, "directory");
checkArgument(directory.toFile().isDirectory(), "provided path isn't a directory");
public void loadPlugins(List<Path> directories, Path baseDirectory) throws IOException {
checkNotNull(directories, "directories");
checkArgument(!directories.isEmpty(), "no directories provided");
checkArgument(!directories.contains(null), "null directories provided");
checkArgument(directories.contains(baseDirectory), "base directory not provided in directories search path");
checkArgument(baseDirectory.toFile().isDirectory(), "provided baseDirectory path isn't a directory");

Map<String, PluginDescription> foundCandidates = new LinkedHashMap<>();
JavaPluginLoader loader = new JavaPluginLoader(server, directory);

try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory,
p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
for (Path path : stream) {
try {
PluginDescription candidate = loader.loadCandidate(path);

// If we found a duplicate candidate (with the same ID), don't load it.
PluginDescription maybeExistingCandidate = foundCandidates.putIfAbsent(
candidate.getId(), candidate);

if (maybeExistingCandidate != null) {
logger.error("Refusing to load plugin at path {} since we already "
+ "loaded a plugin with the same ID {} from {}",
candidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"),
candidate.getId(),
maybeExistingCandidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"));
JavaPluginLoader loader = new JavaPluginLoader(server, baseDirectory);

for (Path directory : directories) {
if (!directory.toFile().isDirectory()) {
continue;
}

try (DirectoryStream<Path> stream = Files.newDirectoryStream(directory,
p -> p.toFile().isFile() && p.toString().endsWith(".jar"))) {
for (Path path : stream) {
try {
PluginDescription candidate = loader.loadCandidate(path);

// If we found a duplicate candidate (with the same ID), don't load it.
PluginDescription maybeExistingCandidate = foundCandidates.putIfAbsent(candidate.getId(), candidate);

if (maybeExistingCandidate != null) {
logger.error("Refusing to load plugin at path {} since we already "
+ "loaded a plugin with the same ID {} from {}",
candidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"),
candidate.getId(),
maybeExistingCandidate.getSource().map(Objects::toString).orElse("<UNKNOWN>"));
}
} catch (Throwable e) {
logger.error("Unable to load plugin {}", path, e);
}
} catch (Throwable e) {
logger.error("Unable to load plugin {}", path, e);
}
}
}
Expand Down