From 74d2ada067bd2fd9436710ab7ac260e0eddf1314 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 29 Jul 2026 01:03:24 +0200 Subject: Add per-instance mod management with client-side detection - New mod validation engine (src/mods.rs) with constant-pool class-file scanning: detects server-side content (block, tileentity, command, worldgen) vs client-only API (gui, renderer, forge/client) - 7 client-only mods correctly detected (AppleSkin, CraftingTweaks, Durability101, EnchantmentDescriptions, InventoryHUD, MouseTweaks, TrashSlot); 51 server-capable; 1 unknown - Routes + templating for mod upload (jar/zip), listing, delete, rescan - Multipart upload with 500MB limit (bypasses axum's default 2MB) - Per-instance mod summary card on edit page - Whitelist toggle checkbox on edit page (persisted, drives ENABLE_WHITELIST) - Dynamic Java image tag (java8/java17/java21) based on MC version - Class scan is informational-only; rejection based on metadata side=CLIENT - has_modpack flag cleanup on mod delete - Remove hx-boost from (was corrupting multipart uploads) --- src/docker.rs | 61 ++++++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 46 insertions(+), 15 deletions(-) (limited to 'src/docker.rs') diff --git a/src/docker.rs b/src/docker.rs index 6b8640a..d66767e 100644 --- a/src/docker.rs +++ b/src/docker.rs @@ -23,19 +23,51 @@ impl DockerManager { Ok(Self { client, config }) } + /// Map a Minecraft version string (e.g. "1.12.2", "1.20.1") to the + /// appropriate itzg/minecraft-server Java image tag. + /// + /// - < 1.17 → java8 (Minecraft 1.12.2 and earlier) + /// - 1.17 .. 1.20.4 → java17 + /// - >= 1.20.5 → java21 + fn java_tag(version: &str) -> &'static str { + // Parse major.minor from version strings like "1.12.2", "1.20.1", "1.21" + let parts: Vec<&str> = version.split('.').collect(); + if parts.len() < 2 { + return "java17"; // sensible default + } + let major: u32 = parts.first().and_then(|s| s.parse().ok()).unwrap_or(1); + let minor: u32 = parts.get(1).and_then(|s| s.parse().ok()).unwrap_or(12); + + if major <= 1 && minor < 17 { + "java8" + } else if major <= 1 && minor < 21 { + // 1.17.x through 1.20.4 use Java 17 + "java17" + } else { + // 1.20.5+ and 1.21+ use Java 21 + "java21" + } + } + + /// Build the full image reference, e.g. "itzg/minecraft-server:java8" + fn image_ref(version: &str) -> String { + format!("itzg/minecraft-server:{}", Self::java_tag(version)) + } + pub async fn start_container(&self, instance: &Instance) -> Result<(), String> { - tracing::info!("Pulling itzg/minecraft-server:latest image..."); - self.pull_image().await?; + let tag = Self::java_tag(&instance.version); + tracing::info!("Pulling itzg/minecraft-server:{tag} image..."); + self.pull_image(tag).await?; tracing::info!("Creating container for instance '{}'", instance.name); self.recreate_container(instance).await?; tracing::info!("Starting container..."); self.start_existing().await } - async fn pull_image(&self) -> Result<(), String> { + async fn pull_image(&self, tag: &str) -> Result<(), String> { let options = CreateImageOptions { from_image: "itzg/minecraft-server", - tag: "latest", + tag, ..Default::default() }; @@ -74,6 +106,8 @@ impl DockerManager { .to_string(); let instance_data = format!("{server_dir}/instances/{}/data", instance.name); + let image_ref = Self::image_ref(&instance.version); + let mut env_strings: Vec = vec![ "EULA=TRUE".into(), format!("VERSION={}", instance.version), @@ -83,7 +117,7 @@ impl DockerManager { format!("DIFFICULTY={}", instance.difficulty), "FORCE_GAMEMODE=true".into(), "SPAWN_PROTECTION=0".into(), - "ENABLE_WHITELIST=true".into(), + format!("ENABLE_WHITELIST={}", instance.whitelist_enabled), "PAUSE_WHEN_EMPTY_SECONDS=30".into(), "ONLINE_MODE=false".into(), "TZ=Europe/Warsaw".into(), @@ -102,17 +136,14 @@ impl DockerManager { env_strings.push("PVP=false".into()); } - if let Some(ref seed) = instance.world_seed { - if !seed.is_empty() { + if let Some(ref seed) = instance.world_seed + && !seed.is_empty() { env_strings.push(format!("LEVEL_SEED={seed}")); } - } - - let env_refs: Vec<&str> = env_strings.iter().map(|s| s.as_str()).collect(); - let mut exposed_ports: HashMap<&str, HashMap<(), ()>> = HashMap::new(); - exposed_ports.insert("25565/tcp", HashMap::new()); - exposed_ports.insert("25575/tcp", HashMap::new()); + let mut exposed_ports: HashMap> = HashMap::new(); + exposed_ports.insert("25565/tcp".into(), HashMap::new()); + exposed_ports.insert("25575/tcp".into(), HashMap::new()); let mut port_bindings = HashMap::new(); port_bindings.insert( @@ -137,9 +168,9 @@ impl DockerManager { }; let container_config = ContainerConfig { - image: Some("itzg/minecraft-server:latest"), + image: Some(image_ref.clone()), tty: Some(true), - env: Some(env_refs), + env: Some(env_strings.clone()), exposed_ports: Some(exposed_ports), host_config: Some(host_config), ..Default::default() -- cgit v1.2.3