diff options
| -rw-r--r-- | src/docker.rs | 8 | ||||
| -rw-r--r-- | src/main.rs | 2 | ||||
| -rw-r--r-- | src/mods.rs | 30 |
3 files changed, 20 insertions, 20 deletions
diff --git a/src/docker.rs b/src/docker.rs index f171d43..fe2232f 100644 --- a/src/docker.rs +++ b/src/docker.rs | |||
| @@ -125,6 +125,7 @@ impl DockerManager { | |||
| 125 | "ENABLE_RCON=true".into(), | 125 | "ENABLE_RCON=true".into(), |
| 126 | format!("RCON_PASSWORD={}", instance.rcon_password), | 126 | format!("RCON_PASSWORD={}", instance.rcon_password), |
| 127 | "RCON_PORT=25575".into(), | 127 | "RCON_PORT=25575".into(), |
| 128 | "JVM_OPTS=-Dfml.queryResult=confirm".into(), | ||
| 128 | format!("MOTD={}", instance.motd), | 129 | format!("MOTD={}", instance.motd), |
| 129 | format!("GAMEMODE={}", instance.gamemode), | 130 | format!("GAMEMODE={}", instance.gamemode), |
| 130 | format!("LEVEL_TYPE={}", instance.level_type), | 131 | format!("LEVEL_TYPE={}", instance.level_type), |
| @@ -137,9 +138,10 @@ impl DockerManager { | |||
| 137 | } | 138 | } |
| 138 | 139 | ||
| 139 | if let Some(ref seed) = instance.world_seed | 140 | if let Some(ref seed) = instance.world_seed |
| 140 | && !seed.is_empty() { | 141 | && !seed.is_empty() |
| 141 | env_strings.push(format!("LEVEL_SEED={seed}")); | 142 | { |
| 142 | } | 143 | env_strings.push(format!("LEVEL_SEED={seed}")); |
| 144 | } | ||
| 143 | 145 | ||
| 144 | let mut exposed_ports: HashMap<String, HashMap<(), ()>> = HashMap::new(); | 146 | let mut exposed_ports: HashMap<String, HashMap<(), ()>> = HashMap::new(); |
| 145 | exposed_ports.insert("25565/tcp".into(), HashMap::new()); | 147 | exposed_ports.insert("25565/tcp".into(), HashMap::new()); |
diff --git a/src/main.rs b/src/main.rs index 666b2d1..28579f3 100644 --- a/src/main.rs +++ b/src/main.rs | |||
| @@ -64,7 +64,7 @@ async fn main() { | |||
| 64 | 64 | ||
| 65 | let router = routes::build_router(state); | 65 | let router = routes::build_router(state); |
| 66 | 66 | ||
| 67 | let addr = format!("127.0.0.1:{}", config.webui_port); | 67 | let addr = format!("0.0.0.0:{}", config.webui_port); |
| 68 | tracing::info!("Starting Minecraft UI on http://{addr}"); | 68 | tracing::info!("Starting Minecraft UI on http://{addr}"); |
| 69 | 69 | ||
| 70 | let listener = tokio::net::TcpListener::bind(&addr) | 70 | let listener = tokio::net::TcpListener::bind(&addr) |
diff --git a/src/mods.rs b/src/mods.rs index 10799f0..e165f20 100644 --- a/src/mods.rs +++ b/src/mods.rs | |||
| @@ -4,12 +4,10 @@ | |||
| 4 | //! 2. Class scan — walks `.class` entries looking for `net/minecraft/server` | 4 | //! 2. Class scan — walks `.class` entries looking for `net/minecraft/server` |
| 5 | //! vs `net/minecraft/client` references. | 5 | //! vs `net/minecraft/client` references. |
| 6 | //! | 6 | //! |
| 7 | //! **Rejection policy:** only mods that explicitly declare themselves client-only | 7 | //! **Rejection policy:** only structurally invalid mods (oversized, unreadable |
| 8 | //! in their metadata (`mods.toml` side=CLIENT, `fabric.mod.json` environment=client) | 8 | //! archive) are rejected. Client-only and ambiguous mods are accepted — Forge |
| 9 | //! are rejected. The class scan is purely informational — many Forge mods that | 9 | //! ignores client-only mods on the server, but they may still be needed for |
| 10 | //! work on servers reference `net/minecraft/client/…` for rendering/GUI but do | 10 | //! client-server handshake compatibility (e.g. AppleSkin, TrashSlot). |
| 11 | //! their server work through common API classes (`net/minecraft/block/Block`, etc.) | ||
| 12 | //! without ever referencing `net/minecraft/server/…` directly. | ||
| 13 | 11 | ||
| 14 | use std::io::{Cursor, Read}; | 12 | use std::io::{Cursor, Read}; |
| 15 | use zip::ZipArchive; | 13 | use zip::ZipArchive; |
| @@ -51,13 +49,10 @@ const MAX_MOD_SIZE_BYTES: usize = 500 * 1024 * 1024; | |||
| 51 | /// Returns a `ValidationResult` indicating whether the mod is safe to install. | 49 | /// Returns a `ValidationResult` indicating whether the mod is safe to install. |
| 52 | /// | 50 | /// |
| 53 | /// **Rejection policy:** | 51 | /// **Rejection policy:** |
| 54 | /// - Only mods that explicitly declare themselves client-only **in their metadata** | 52 | /// - Only mods that fail structural checks (oversized, unreadable archive) are |
| 55 | /// (`mods.toml` side=CLIENT, `fabric.mod.json` environment=client) are rejected | 53 | /// rejected outright. Client-only and ambiguous mods are accepted — Forge |
| 56 | /// outright. The class-file scan is purely informational — many Forge mods that | 54 | /// ignores client-only mods on the server, but they may still be needed for |
| 57 | /// work perfectly on a server reference `net/minecraft/client/…` classes for | 55 | /// client-server handshake compatibility (e.g. AppleSkin, TrashSlot). |
| 58 | /// rendering/GUI while doing their server logic through common API classes | ||
| 59 | /// (`net/minecraft/block/Block`, `net/minecraft/item/Item`, …) without ever | ||
| 60 | /// referencing `net/minecraft/server/…` directly. | ||
| 61 | pub fn validate_mod(data: &[u8], filename: &str) -> ValidationResult { | 56 | pub fn validate_mod(data: &[u8], filename: &str) -> ValidationResult { |
| 62 | // Reject oversized files early to prevent DoS | 57 | // Reject oversized files early to prevent DoS |
| 63 | if data.len() > MAX_MOD_SIZE_BYTES { | 58 | if data.len() > MAX_MOD_SIZE_BYTES { |
| @@ -75,13 +70,16 @@ pub fn validate_mod(data: &[u8], filename: &str) -> ValidationResult { | |||
| 75 | // Phase 1: metadata check | 70 | // Phase 1: metadata check |
| 76 | let (meta_side, meta_display_name) = read_metadata(data); | 71 | let (meta_side, meta_display_name) = read_metadata(data); |
| 77 | 72 | ||
| 78 | // Only reject based on metadata — the mod author's explicit declaration. | 73 | // Client-only mods are accepted — Forge simply ignores them on the |
| 74 | // server side, and they may still be required in the mods folder for | ||
| 75 | // client-server compatibility handshake or anti-cheat measures. | ||
| 79 | if meta_side == Some(ModSide::Client) { | 76 | if meta_side == Some(ModSide::Client) { |
| 80 | return ValidationResult { | 77 | return ValidationResult { |
| 81 | accepted: false, | 78 | accepted: true, |
| 82 | rejection_reason: Some(format!( | 79 | rejection_reason: Some(format!( |
| 83 | "Mod '{}' declares itself as client-only in its metadata. \ | 80 | "Mod '{}' declares itself as client-only in its metadata. \ |
| 84 | It would crash the server.", | 81 | It is accepted — Forge ignores client-only mods on the server, \ |
| 82 | but they may be needed for client-server compatibility.", | ||
| 85 | meta_display_name.as_deref().unwrap_or(filename) | 83 | meta_display_name.as_deref().unwrap_or(filename) |
| 86 | )), | 84 | )), |
| 87 | display_name: meta_display_name, | 85 | display_name: meta_display_name, |
