Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add first-party support for Minestom servers #634

Merged
merged 5 commits into from
Aug 26, 2024
Merged
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
18 changes: 9 additions & 9 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -249,13 +249,15 @@ subprojects {
}
} else if (project.name == "shulker-server-agent") {
val commonSourceSet = sourceSets.create("common")
setOf("paper").forEach { providerName ->
setOf("paper", "minestom").forEach { providerName ->
registerPluginProvider(providerName, commonSourceSet)
}

dependencies {
"commonCompileOnly"(libs.adventure.api)
"paperCompileOnly"(libs.folia.api)
"minestomCompileOnly"(libs.minestom)
"minestomImplementation"(libs.snakeyaml)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2298,6 +2298,7 @@ spec:
enum:
- Paper
- Folia
- Minestom
type: string
customJar:
description: Reference to a server JAR file to download and use instead of the built-in one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2069,6 +2069,7 @@ spec:
enum:
- Paper
- Folia
- Minestom
type: string
customJar:
description: Reference to a server JAR file to download and use instead of the built-in one
Expand Down
1 change: 1 addition & 0 deletions packages/shulker-crds/src/v1alpha1/minecraft_server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ pub enum MinecraftServerVersion {
#[default]
Paper,
Folia,
Minestom,
}

#[derive(Deserialize, Serialize, Clone, Debug, Default, JsonSchema)]
Expand Down
3 changes: 3 additions & 0 deletions packages/shulker-kube-utils/src/reconcilers/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ pub mod status;

#[derive(Error, Debug)]
pub enum BuilderReconcilerError {
#[error("builder {0} rejected validation of spec: {1}")]
ValidationError(&'static str, String),

#[error("builder {0} failed to build resource: {1}")]
BuilderError(&'static str, #[source] anyhow::Error),

Expand Down
2 changes: 1 addition & 1 deletion packages/shulker-operator/assets/server-init-fs.sh
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ set -euo pipefail
set -o xtrace

cp "${SHULKER_CONFIG_DIR}/server.properties" "${SHULKER_SERVER_CONFIG_DIR}/server.properties"
if [ "${SHULKER_VERSION_CHANNEL}" == "Paper" ] || [ "${SHULKER_VERSION_CHANNEL}" == "Folia" ]; then
if [ "${SHULKER_VERSION_CHANNEL}" == "Paper" ] || [ "${SHULKER_VERSION_CHANNEL}" == "Folia" ] || [ "${SHULKER_VERSION_CHANNEL}" == "Minestom" ]; then
cp "${SHULKER_CONFIG_DIR}/bukkit-config.yml" "${SHULKER_SERVER_CONFIG_DIR}/bukkit.yml"
cp "${SHULKER_CONFIG_DIR}/spigot-config.yml" "${SHULKER_SERVER_CONFIG_DIR}/spigot.yml"
mkdir -p "${SHULKER_SERVER_CONFIG_DIR}/config"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use lazy_static::lazy_static;
use shulker_crds::v1alpha1::minecraft_cluster::MinecraftCluster;
use shulker_crds::v1alpha1::minecraft_server::MinecraftServerVersion;
use shulker_kube_utils::reconcilers::BuilderReconcilerError;
use url::Url;

use crate::agent::AgentConfig;
Expand Down Expand Up @@ -91,6 +92,8 @@
_existing_game_server: Option<&Self::ResourceType>,
context: Option<GameServerBuilderContext<'a>>,
) -> Result<Self::ResourceType, anyhow::Error> {
GameServerBuilder::validate_spec(context.as_ref().unwrap(), minecraft_server)?;

let game_server = GameServer {
metadata: ObjectMeta {
name: Some(name.to_string()),
Expand Down Expand Up @@ -127,6 +130,22 @@
}
}

pub fn validate_spec(
_context: &GameServerBuilderContext<'a>,
minecraft_server: &MinecraftServer,
) -> Result<(), BuilderReconcilerError> {
if minecraft_server.spec.version.channel == MinecraftServerVersion::Minestom
&& minecraft_server.spec.version.custom_jar.is_none()
{
return Err(BuilderReconcilerError::ValidationError(
std::any::type_name::<GameServerBuilder>(),
"a Minestom-based server requires a custom JAR to be provided".to_string(),
));
}

Ok(())
}

pub async fn get_game_server_spec(
resourceref_resolver: &ResourceRefResolver,
context: &GameServerBuilderContext<'a>,
Expand Down Expand Up @@ -565,6 +584,7 @@
MinecraftServerVersion::Paper | MinecraftServerVersion::Folia => {
Some("paper".to_string())
}
MinecraftServerVersion::Minestom => None,

Check warning on line 587 in packages/shulker-operator/src/reconcilers/minecraft_server/gameserver.rs

View check run for this annotation

Codecov / codecov/patch

packages/shulker-operator/src/reconcilers/minecraft_server/gameserver.rs#L587

Added line #L587 was not covered by tests
};

let mut plugin_refs: Vec<Url> = vec![];
Expand Down Expand Up @@ -596,7 +616,7 @@

fn get_type_from_version_channel(channel: &MinecraftServerVersion) -> String {
match channel {
MinecraftServerVersion::Paper => "PAPER".to_string(),
MinecraftServerVersion::Paper | MinecraftServerVersion::Minestom => "PAPER".to_string(),
MinecraftServerVersion::Folia => "FOLIA".to_string(),
}
}
Expand All @@ -611,8 +631,11 @@
use k8s_openapi::api::core::v1::{
ContainerPort, EmptyDirVolumeSource, LocalObjectReference, Volume, VolumeMount,
};
use shulker_crds::{resourceref::ResourceRefSpec, schemas::ImageOverrideSpec};
use shulker_kube_utils::reconcilers::builder::ResourceBuilder;
use shulker_crds::{
resourceref::ResourceRefSpec, schemas::ImageOverrideSpec,
v1alpha1::minecraft_server::MinecraftServerVersion,
};
use shulker_kube_utils::reconcilers::{builder::ResourceBuilder, BuilderReconcilerError};

use crate::{
agent::AgentConfig,
Expand All @@ -624,6 +647,8 @@
resources::resourceref_resolver::ResourceRefResolver,
};

use super::GameServerBuilder;

#[test]
fn name_contains_server_name() {
// W
Expand All @@ -633,6 +658,37 @@
assert_eq!(name, "my-server");
}

#[test]
fn validate_rejects_minestom_without_custom_jar() {
// G
let mut server = TEST_SERVER.clone();
server.spec.version.channel = MinecraftServerVersion::Minestom;
server.spec.version.custom_jar = None;
let context = super::GameServerBuilderContext {
cluster: &TEST_CLUSTER,
agent_config: &AgentConfig {
maven_repository: constants::SHULKER_PLUGIN_REPOSITORY.to_string(),
version: constants::SHULKER_PLUGIN_VERSION.to_string(),
},
};

// W
let is_valid = GameServerBuilder::validate_spec(&context, &server);

// T
match is_valid.unwrap_err() {
BuilderReconcilerError::ValidationError(_, message) => {
assert_eq!(
message,
"a Minestom-based server requires a custom JAR to be provided"
);
}
_ => {
unreachable!("Error mismatch")
}
}
}

#[tokio::test]
async fn build_snapshot() {
// G
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ apiVersion: v1
kind: ConfigMap
data:
bukkit-config.yml: "settings:\n allow-end: false\nauto-updater:\n enabled: false\n\n"
init-fs.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\ncp \"${SHULKER_CONFIG_DIR}/server.properties\" \"${SHULKER_SERVER_CONFIG_DIR}/server.properties\"\nif [ \"${SHULKER_VERSION_CHANNEL}\" == \"Paper\" ] || [ \"${SHULKER_VERSION_CHANNEL}\" == \"Folia\" ]; then\n cp \"${SHULKER_CONFIG_DIR}/bukkit-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/bukkit.yml\"\n cp \"${SHULKER_CONFIG_DIR}/spigot-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/spigot.yml\"\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/config\"\n cp \"${SHULKER_CONFIG_DIR}/paper-global-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/config/paper-global.yml\"\nfi\n\nif [ ! -z \"${SERVER_WORLD_URL:-}\" ]; then\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${SERVER_WORLD_URL}\" -O - | tar -xzv)\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PLUGIN_URLS:-}\" ]; then\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/plugins\"\n for plugin_url in ${SHULKER_SERVER_PLUGIN_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}/plugins\" && wget \"${plugin_url}\")\n done\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PATCH_URLS:-}\" ]; then\n for patch_url in ${SHULKER_SERVER_PATCH_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${patch_url}\" -O - | tar -xzv)\n done\nfi\n"
init-fs.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\ncp \"${SHULKER_CONFIG_DIR}/server.properties\" \"${SHULKER_SERVER_CONFIG_DIR}/server.properties\"\nif [ \"${SHULKER_VERSION_CHANNEL}\" == \"Paper\" ] || [ \"${SHULKER_VERSION_CHANNEL}\" == \"Folia\" ] || [ \"${SHULKER_VERSION_CHANNEL}\" == \"Minestom\" ]; then\n cp \"${SHULKER_CONFIG_DIR}/bukkit-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/bukkit.yml\"\n cp \"${SHULKER_CONFIG_DIR}/spigot-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/spigot.yml\"\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/config\"\n cp \"${SHULKER_CONFIG_DIR}/paper-global-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/config/paper-global.yml\"\nfi\n\nif [ ! -z \"${SERVER_WORLD_URL:-}\" ]; then\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${SERVER_WORLD_URL}\" -O - | tar -xzv)\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PLUGIN_URLS:-}\" ]; then\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/plugins\"\n for plugin_url in ${SHULKER_SERVER_PLUGIN_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}/plugins\" && wget \"${plugin_url}\")\n done\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PATCH_URLS:-}\" ]; then\n for patch_url in ${SHULKER_SERVER_PATCH_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${patch_url}\" -O - | tar -xzv)\n done\nfi\n"
paper-global-config.yml: "proxies:\n bungee-cord:\n online-mode: false\n velocity:\n enabled: true\n online-mode: true\n secret: ${CFG_VELOCITY_FORWARDING_SECRET}\n\n"
server.properties: "allow-nether=true\nenforce-secure-profiles=true\nmax-players=42\nonline-mode=false\nprevent-proxy-connections=false\n"
spigot-config.yml: "settings:\n bungeecord: false\n restart-on-crash: false\nadvancements:\n disable-saving: true\nplayers:\n disable-saving: true\nstats:\n disable-saving: true\nsave-user-cache-on-stop-only: true\n\n"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ apiVersion: v1
kind: ConfigMap
data:
bukkit-config.yml: "settings:\n allow-end: false\nauto-updater:\n enabled: false\n\n"
init-fs.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\ncp \"${SHULKER_CONFIG_DIR}/server.properties\" \"${SHULKER_SERVER_CONFIG_DIR}/server.properties\"\nif [ \"${SHULKER_VERSION_CHANNEL}\" == \"Paper\" ] || [ \"${SHULKER_VERSION_CHANNEL}\" == \"Folia\" ]; then\n cp \"${SHULKER_CONFIG_DIR}/bukkit-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/bukkit.yml\"\n cp \"${SHULKER_CONFIG_DIR}/spigot-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/spigot.yml\"\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/config\"\n cp \"${SHULKER_CONFIG_DIR}/paper-global-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/config/paper-global.yml\"\nfi\n\nif [ ! -z \"${SERVER_WORLD_URL:-}\" ]; then\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${SERVER_WORLD_URL}\" -O - | tar -xzv)\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PLUGIN_URLS:-}\" ]; then\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/plugins\"\n for plugin_url in ${SHULKER_SERVER_PLUGIN_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}/plugins\" && wget \"${plugin_url}\")\n done\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PATCH_URLS:-}\" ]; then\n for patch_url in ${SHULKER_SERVER_PATCH_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${patch_url}\" -O - | tar -xzv)\n done\nfi\n"
init-fs.sh: "#!/bin/sh\nset -euo pipefail\nset -o xtrace\n\ncp \"${SHULKER_CONFIG_DIR}/server.properties\" \"${SHULKER_SERVER_CONFIG_DIR}/server.properties\"\nif [ \"${SHULKER_VERSION_CHANNEL}\" == \"Paper\" ] || [ \"${SHULKER_VERSION_CHANNEL}\" == \"Folia\" ] || [ \"${SHULKER_VERSION_CHANNEL}\" == \"Minestom\" ]; then\n cp \"${SHULKER_CONFIG_DIR}/bukkit-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/bukkit.yml\"\n cp \"${SHULKER_CONFIG_DIR}/spigot-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/spigot.yml\"\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/config\"\n cp \"${SHULKER_CONFIG_DIR}/paper-global-config.yml\" \"${SHULKER_SERVER_CONFIG_DIR}/config/paper-global.yml\"\nfi\n\nif [ ! -z \"${SERVER_WORLD_URL:-}\" ]; then\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${SERVER_WORLD_URL}\" -O - | tar -xzv)\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PLUGIN_URLS:-}\" ]; then\n mkdir -p \"${SHULKER_SERVER_CONFIG_DIR}/plugins\"\n for plugin_url in ${SHULKER_SERVER_PLUGIN_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}/plugins\" && wget \"${plugin_url}\")\n done\nfi\n\nif [ ! -z \"${SHULKER_SERVER_PATCH_URLS:-}\" ]; then\n for patch_url in ${SHULKER_SERVER_PATCH_URLS//;/ }; do\n (cd \"${SHULKER_SERVER_CONFIG_DIR}\" && wget \"${patch_url}\" -O - | tar -xzv)\n done\nfi\n"
paper-global-config.yml: "proxies:\n bungee-cord:\n online-mode: false\n velocity:\n enabled: true\n online-mode: true\n secret: ${CFG_VELOCITY_FORWARDING_SECRET}\n\n"
server.properties: "allow-nether=true\nenforce-secure-profiles=true\nmax-players=42\nonline-mode=false\nprevent-proxy-connections=false\n"
spigot-config.yml: "settings:\n bungeecord: false\n restart-on-crash: false\nadvancements:\n disable-saving: true\nplayers:\n disable-saving: true\nstats:\n disable-saving: true\nsave-user-cache-on-stop-only: true\n\n"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package io.shulkermc.serveragent.paper

import io.shulkermc.serveragent.ServerInterface
import io.shulkermc.serveragent.platform.HookPostOrder
import io.shulkermc.serveragent.platform.PlayerDisconnectHook
import io.shulkermc.serveragent.platform.PlayerLoginHook
import net.minestom.server.MinecraftServer
import net.minestom.server.event.EventNode
import net.minestom.server.event.player.PlayerDisconnectEvent
import net.minestom.server.event.player.PlayerSpawnEvent
import net.minestom.server.permission.Permission
import net.minestom.server.timer.Task
import net.minestom.server.timer.TaskSchedule
import java.time.Duration
import java.util.UUID
import java.util.concurrent.TimeUnit

class ServerInterfaceMinestom : ServerInterface {
companion object {
private const val ADMIN_PERMISSION_LEVEL = 4
}

private val eventNode = EventNode.all("shulker-server-agent-minestom")

override fun prepareNetworkAdminsPermissions(playerIds: List<UUID>) {
this.eventNode.addListener(PlayerSpawnEvent::class.java) { event: PlayerSpawnEvent ->
if (playerIds.contains(event.player.uuid)) {
event.player.permissionLevel = ADMIN_PERMISSION_LEVEL
event.player.addPermission(Permission("*"))
}
}
}

override fun addPlayerJoinHook(
hook: PlayerLoginHook,
postOrder: HookPostOrder,
) {
this.eventNode.addListener(PlayerSpawnEvent::class.java) { _ -> hook() }
}

override fun addPlayerQuitHook(
hook: PlayerDisconnectHook,
postOrder: HookPostOrder,
) {
this.eventNode.addListener(PlayerDisconnectEvent::class.java) { _ -> hook() }
}

override fun getPlayerCount(): Int = MinecraftServer.getConnectionManager().onlinePlayers.size

override fun scheduleDelayedTask(
delay: Long,
timeUnit: TimeUnit,
runnable: Runnable,
): ServerInterface.ScheduledTask {
val duration = Duration.ofNanos(timeUnit.toNanos(delay))
val task =
MinecraftServer.getSchedulerManager().scheduleTask(
runnable,
TaskSchedule.duration(duration),
TaskSchedule.stop(),
)

return MinestomScheduledTask(task)
}

override fun scheduleRepeatingTask(
delay: Long,
interval: Long,
timeUnit: TimeUnit,
runnable: Runnable,
): ServerInterface.ScheduledTask {
val delayDuration = Duration.ofNanos(timeUnit.toNanos(delay))
val intervalDuration = Duration.ofNanos(timeUnit.toNanos(interval))
val task =
MinecraftServer.getSchedulerManager().scheduleTask(
runnable,
TaskSchedule.duration(delayDuration),
TaskSchedule.duration(intervalDuration),
)

return MinestomScheduledTask(task)
}

private class MinestomScheduledTask(private val minestomTask: Task) : ServerInterface.ScheduledTask {
override fun cancel() {
this.minestomTask.cancel()
}
}
}
Loading
Loading