diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 4f993007..334f8259 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -19,7 +19,7 @@ jobs: contents: read steps: - uses: actions/checkout@v4 - - uses: actions/setup-java@v3 + - uses: actions/setup-java@v4 with: java-version: "17" distribution: "adopt" diff --git a/README-JP.md b/README-JP.md index 9024b26f..d911ceb9 100644 --- a/README-JP.md +++ b/README-JP.md @@ -16,6 +16,7 @@ Pluginは [Releases](https://github.com/AzisabaNetwork/Kuvel/releases/latest) からダウンロードできます。 `Kuvel.jar` をダウンロードしVelocityに導入してください。ダウンロード後、コンフィグの設定を行ってください。 ```yml +namespace: "" redis: group-name: "production" # Redisサーバーが同じかつgroup-nameが同じサーバー間でのみ名前同期が行われます connection: diff --git a/README.md b/README.md index c90c9b82..f7cbc59f 100644 --- a/README.md +++ b/README.md @@ -17,6 +17,8 @@ from [Releases](https://github.com/AzisabaNetwork/Kuvel/releases/latest). Downlo install it into Velocity plugins directory. Also, you have to fill in the configuration file. ```yml +# The kubernetes namespace to use for the server discovery. +namespace: "" # Server name synchronization by Redis is required in load-balanced environments using multiple Velocity. redis: group-name: "production" @@ -29,6 +31,10 @@ redis: password: "password" ``` +Alternatively you can use environment variables to configure Kuvel. The environment variable will override + the config.yml and are `KUVEL_NAMESPACE`, `KUVEL_REDIS_GROUPNAME`, `KUVEL_REDIS_CONNECTION_HOSTNAME`, +`KUVEL_REDIS_CONNECTION_PORT`, `KUVEL_REDIS_CONNECTION_USERNAME`, and `KUVEL_REDIS_CONNECTION_PASSWORD`. + In order for Kuvel to monitor the server, you must request permission from Kubernetes to allow Velocity pods discovery Minecraft servers. For Velocity pods, please allow get/list/watch to Pods and ReplicaSets. diff --git a/pom.xml b/pom.xml index a32e7cd6..9face74b 100644 --- a/pom.xml +++ b/pom.xml @@ -6,7 +6,7 @@ net.azisaba Kuvel - 2.0.2 + 2.1.0 jar ${project.artifactId} diff --git a/src/main/java/net/azisaba/kuvel/Kuvel.java b/src/main/java/net/azisaba/kuvel/Kuvel.java index 3dd7911e..49dc1c54 100644 --- a/src/main/java/net/azisaba/kuvel/Kuvel.java +++ b/src/main/java/net/azisaba/kuvel/Kuvel.java @@ -5,12 +5,15 @@ import com.velocitypowered.api.event.proxy.ProxyInitializeEvent; import com.velocitypowered.api.event.proxy.ProxyShutdownEvent; import com.velocitypowered.api.plugin.Plugin; +import com.velocitypowered.api.plugin.annotation.DataDirectory; import com.velocitypowered.api.proxy.ProxyServer; import io.fabric8.kubernetes.client.DefaultKubernetesClient; import io.fabric8.kubernetes.client.KubernetesClient; + +import java.io.File; +import java.nio.file.Path; import java.util.Objects; import java.util.concurrent.TimeUnit; -import java.util.logging.Logger; import lombok.Getter; import net.azisaba.kuvel.config.KuvelConfig; import net.azisaba.kuvel.discovery.impl.redis.RedisLoadBalancerDiscovery; @@ -20,11 +23,12 @@ import net.azisaba.kuvel.redis.ProxyIdProvider; import net.azisaba.kuvel.redis.RedisConnectionLeader; import net.azisaba.kuvel.redis.RedisSubscriberExecutor; +import org.slf4j.Logger; @Plugin( id = "kuvel", name = "Kuvel", - version = "2.0.2", + version = "2.1.0", url = "https://github.com/AzisabaNetwork/Kuvel", description = "Server-discovery Velocity plugin for Minecraft servers running in a Kubernetes cluster.", @@ -34,6 +38,7 @@ public class Kuvel { private final ProxyServer proxy; private final Logger logger; + private final File dataDirectory; private KubernetesClient client; private KuvelServiceHandler kuvelServiceHandler; @@ -44,9 +49,10 @@ public class Kuvel { private KuvelConfig kuvelConfig; @Inject - public Kuvel(ProxyServer server, Logger logger) { + public Kuvel(ProxyServer server, Logger logger, @DataDirectory Path dataDirectory) { this.proxy = server; this.logger = logger; + this.dataDirectory = dataDirectory.toFile(); } @Subscribe @@ -57,12 +63,11 @@ public void onProxyInitialization(ProxyInitializeEvent event) { try { kuvelConfig.load(); } catch (Exception e) { - logger.severe("Failed to load config file. Plugin feature will be disabled."); - e.printStackTrace(); + logger.error("Failed to load config file. Plugin feature will be disabled.", e); return; } - kuvelServiceHandler = new KuvelServiceHandler(this, client); + kuvelServiceHandler = new KuvelServiceHandler(this, client, kuvelConfig.getNamespace()); Objects.requireNonNull(kuvelConfig.getRedisConnectionData()); Objects.requireNonNull(kuvelConfig.getProxyGroupName()); @@ -88,6 +93,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { new RedisLoadBalancerDiscovery( client, this, + kuvelConfig.getNamespace(), kuvelConfig.getRedisConnectionData().createJedisPool(), kuvelConfig.getProxyGroupName(), redisConnectionLeader, @@ -97,6 +103,7 @@ public void onProxyInitialization(ProxyInitializeEvent event) { new RedisServerDiscovery( client, this, + kuvelConfig.getNamespace(), kuvelConfig.getRedisConnectionData().createJedisPool(), kuvelConfig.getProxyGroupName(), redisConnectionLeader, diff --git a/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java b/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java index 1c5a07a5..7cdc583c 100644 --- a/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java +++ b/src/main/java/net/azisaba/kuvel/KuvelServiceHandler.java @@ -25,6 +25,7 @@ public class KuvelServiceHandler { private final Kuvel plugin; private final KubernetesClient client; + private final String namespace; private final HashMap loadBalancerServerMap = new HashMap<>(); private final UidAndServerNameMap podUidAndServerNameMap = new UidAndServerNameMap(); @@ -125,7 +126,7 @@ private void updateLoadBalancerEndpoints(LoadBalancer loadBalancer) { List pods = client .pods() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .list() .getItems(); @@ -251,7 +252,7 @@ public void registerPod(String podUid, String serverName) { Optional pod = client .pods() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .list() .getItems() diff --git a/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java b/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java index 3d260492..21ca2978 100644 --- a/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java +++ b/src/main/java/net/azisaba/kuvel/config/KuvelConfig.java @@ -2,6 +2,7 @@ import java.io.File; import java.io.IOException; +import java.util.Map; import javax.annotation.Nullable; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -14,31 +15,61 @@ public class KuvelConfig { private final Kuvel plugin; - private static final String CONFIG_FILE_PATH = "./plugins/Kuvel/config.yml"; + private static final String CONFIG_FILE_NAME = "config.yml"; + @Nullable private String namespace; private boolean redisEnabled; @Nullable private RedisConnectionData redisConnectionData; @Nullable private String proxyGroupName; public void load() throws IOException { - VelocityConfigLoader conf = VelocityConfigLoader.load(new File(CONFIG_FILE_PATH)); + File uppercaseDataFolder = new File(plugin.getDataDirectory().getParentFile(), "Kuvel"); + if (uppercaseDataFolder.exists() && !plugin.getDataDirectory().exists()) { + if (uppercaseDataFolder.renameTo(plugin.getDataDirectory())) { + plugin + .getLogger() + .info( + "Successfully renamed the data folder to use a lowercase name."); + } else { + plugin + .getLogger() + .warn( + "Failed to rename the data folder to be lowercase. Please manually rename the data folder to 'kuvel'."); + } + } + + VelocityConfigLoader conf = VelocityConfigLoader.load(new File(plugin.getDataDirectory(), CONFIG_FILE_NAME)); conf.saveDefaultConfig(); - String hostname = conf.getString("redis.connection.hostname"); + Map env = System.getenv(); + + namespace = env.getOrDefault("KUVEL_NAMESPACE", conf.getString("namespace", null)); + + String hostname = env.getOrDefault("KUVEL_REDIS_CONNECTION_HOSTNAME", conf.getString("redis.connection.hostname")); int port = conf.getInt("redis.connection.port", -1); - String username = conf.getString("redis.connection.username"); - String password = conf.getString("redis.connection.password"); + if (env.containsKey("KUVEL_REDIS_CONNECTION_PORT")) { + try { + port = Integer.parseInt(env.get("KUVEL_REDIS_CONNECTION_PORT")); + } catch (NumberFormatException e) { + plugin + .getLogger() + .warn( + "Invalid port number for Redis connection specified in KUVEL_REDIS_CONNECTION_PORT environment variable. Using port " + port + " from config.yml."); + } + } + String username = env.getOrDefault("KUVEL_REDIS_CONNECTION_USERNAME", conf.getString("redis.connection.username")); + String password = env.getOrDefault("KUVEL_REDIS_CONNECTION_PASSWORD", conf.getString("redis.connection.password")); if (hostname == null || port <= 0) { redisEnabled = false; plugin .getLogger() - .warning( + .warn( "Redis is enabled, but hostname or port is invalid. Redis sync will be disabled."); } else { redisConnectionData = new RedisConnectionData(hostname, port, username, password); } - proxyGroupName = conf.getString("redis.group-name", null); + proxyGroupName = env.getOrDefault("KUVEL_REDIS_GROUPNAME", conf.getString("redis.group-name", null)); } } diff --git a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java index c1388eea..75ac9e00 100644 --- a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java +++ b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisLoadBalancerDiscovery.java @@ -32,6 +32,7 @@ public class RedisLoadBalancerDiscovery implements LoadBalancerDiscovery { private final KubernetesClient client; private final Kuvel plugin; + private final String namespace; private final JedisPool jedisPool; private final String groupName; private final RedisConnectionLeader redisConnectionLeader; @@ -55,7 +56,7 @@ public void start() { client .apps() .replicaSets() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .withLabel(LabelKeys.PREFERRED_SERVER_NAME.getKey()) .list() @@ -241,7 +242,7 @@ public void registerLoadBalancersForStartup() { client .apps() .replicaSets() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .withLabel(LabelKeys.PREFERRED_SERVER_NAME.getKey()) .list() @@ -272,7 +273,7 @@ private ReplicaSet getReplicaSetFromUid(String uid) { return client .apps() .replicaSets() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .withLabel(LabelKeys.PREFERRED_SERVER_NAME.getKey()) .list() diff --git a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java index a93b43e2..e9ae8ac8 100644 --- a/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java +++ b/src/main/java/net/azisaba/kuvel/discovery/impl/redis/RedisServerDiscovery.java @@ -32,6 +32,7 @@ public class RedisServerDiscovery implements ServerDiscovery { private final KubernetesClient client; private final Kuvel plugin; + private final String namespace; private final JedisPool jedisPool; private final String groupName; private final RedisConnectionLeader redisConnectionLeader; @@ -52,7 +53,7 @@ public void start() { List podList = client .pods() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .list() .getItems(); @@ -113,7 +114,7 @@ public HashMap getServersForStartup() { client .pods() - .inAnyNamespace() + .inNamespace(namespace) .withLabel(LabelKeys.ENABLE_SERVER_DISCOVERY.getKey(), "true") .withField("status.phase", "Running") .list() diff --git a/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java b/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java index 0444a6f4..187784d2 100644 --- a/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java +++ b/src/main/java/net/azisaba/kuvel/redis/RedisConnectionLeader.java @@ -120,6 +120,7 @@ private void runDiscoveryTask() { new RedisLoadBalancerDiscovery( plugin.getClient(), plugin, + plugin.getKuvelConfig().getNamespace(), plugin.getKuvelConfig().getRedisConnectionData().createJedisPool(), plugin.getKuvelConfig().getProxyGroupName(), this, @@ -131,6 +132,7 @@ private void runDiscoveryTask() { new RedisServerDiscovery( plugin.getClient(), plugin, + plugin.getKuvelConfig().getNamespace(), plugin.getKuvelConfig().getRedisConnectionData().createJedisPool(), plugin.getKuvelConfig().getProxyGroupName(), this, diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml index 1957e0d2..bfe0c060 100644 --- a/src/main/resources/config.yml +++ b/src/main/resources/config.yml @@ -1,3 +1,5 @@ +# The kubernetes namespace to use for the server discovery. +namespace: "" # Server name synchronization by Redis is required in load-balanced environments using multiple Velocity. redis: group-name: "production"