From 6169dd1b12ad00c87fe1057c0ea3fb71ce49f38c Mon Sep 17 00:00:00 2001 From: Alexandre Frigon Date: Mon, 7 Sep 2026 00:51:52 -0400 Subject: [PATCH 1/3] warn about unknown server property keys at run Closes #23 --- docs/src/commands/run.md | 3 ++- docs/src/reference/manifest.md | 4 ++++ src/minecraft/server_properties.rs | 21 +++++++++++++++++++++ src/minecraft/tests.rs | 20 ++++++++++++++++++++ src/ops/run.rs | 7 +++++++ 5 files changed, 54 insertions(+), 1 deletion(-) diff --git a/docs/src/commands/run.md b/docs/src/commands/run.md index 40e123b..57ad27e 100644 --- a/docs/src/commands/run.md +++ b/docs/src/commands/run.md @@ -9,7 +9,8 @@ missing or out of date is installed first: the Java runtime, the Minecraft binary, the mod loader, the mods (added, updated, and removed to match the manifest), and the tunnel agent when a tunnel is configured. The generated configuration files and player lists are rewritten from the manifest on -every start. +every start. A warning is printed for every entry of the manifest's +`properties` block that is not a vanilla server property. Only one running server is allowed per instance directory; a second `mc run` refuses to start. diff --git a/docs/src/reference/manifest.md b/docs/src/reference/manifest.md index baa832b..ad872c9 100644 --- a/docs/src/reference/manifest.md +++ b/docs/src/reference/manifest.md @@ -194,6 +194,10 @@ Values may be strings, integers, floats, or booleans. Keys containing a dot must be quoted, as above; a nested block spells the same key, so `rcon { broadcast "yes" }` sets `rcon.broadcast`. +Any key is accepted, since mods read their own settings from the same +file, but [`mc run`](../commands/run.md) prints a warning for each key +that is not a vanilla server property, so a typo does not go unnoticed. + Keys managed by mc are rejected: an entry for a key that a `server` field or the top-level `name` and `description` already drive is an error naming the field to use instead. `enable-rcon` is rejected too — RCON is enabled diff --git a/src/minecraft/server_properties.rs b/src/minecraft/server_properties.rs index 722915e..471704f 100644 --- a/src/minecraft/server_properties.rs +++ b/src/minecraft/server_properties.rs @@ -301,6 +301,27 @@ impl ServerProperties { Ok(password) } + // Mods read their own keys from server.properties, so an unknown key is a + // warning rather than an error. + pub fn unknown_keys(overrides: &BTreeMap) -> McResult> { + let s = serde_java_properties::to_string(&ServerProperties::default()) + .context("could not serialize server.properties")?; + + let known: BTreeMap = serde_java_properties::from_str(&s) + .context("could not parse the generated server.properties")?; + + Ok(overrides + .keys() + .filter(|key| { + !known.contains_key(*key) + && !MANAGED_PROPERTY_KEYS + .iter() + .any(|(managed, _)| managed == key) + }) + .cloned() + .collect()) + } + pub fn to_entries( &self, overrides: &BTreeMap, diff --git a/src/minecraft/tests.rs b/src/minecraft/tests.rs index 584b207..544f8d8 100644 --- a/src/minecraft/tests.rs +++ b/src/minecraft/tests.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use chrono::DateTime; use chrono::Utc; use uuid::Uuid; @@ -6,6 +8,7 @@ use crate::minecraft::players::BanDetails; use crate::minecraft::players::BanEntry; use crate::minecraft::players::OpEntry; use crate::minecraft::players::offline_uuid; +use crate::minecraft::server_properties::ServerProperties; use crate::utils::errors::McResult; #[test] @@ -51,3 +54,20 @@ fn op_entries_use_the_server_field_names() -> McResult<()> { Ok(()) } + +#[test] +fn unknown_property_keys_are_reported() -> McResult<()> { + let overrides = BTreeMap::from([ + (String::from("spawn-protection"), String::from("0")), + (String::from("query.port"), String::from("25565")), + (String::from("level-seed"), String::from("42")), + (String::from("spawn-protetion"), String::from("0")), + (String::from("fabric.custom"), String::from("x")) + ]); + + let unknown = ServerProperties::unknown_keys(&overrides)?; + + assert_eq!(unknown, vec!["fabric.custom", "spawn-protetion"]); + + Ok(()) +} diff --git a/src/ops/run.rs b/src/ops/run.rs index 550c1a7..c5eaba4 100644 --- a/src/ops/run.rs +++ b/src/ops/run.rs @@ -215,6 +215,13 @@ pub async fn run(context: &mut McContext, options: &RunOptions) -> McResult Date: Mon, 7 Sep 2026 01:03:10 -0400 Subject: [PATCH 2/3] name the manifest block in the unknown property warning --- docs/src/commands/run.md | 4 ++-- docs/src/reference/manifest.md | 5 +++-- src/minecraft/server_properties.rs | 4 ++-- src/ops/run.rs | 2 +- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/src/commands/run.md b/docs/src/commands/run.md index 57ad27e..8feeabd 100644 --- a/docs/src/commands/run.md +++ b/docs/src/commands/run.md @@ -9,8 +9,8 @@ missing or out of date is installed first: the Java runtime, the Minecraft binary, the mod loader, the mods (added, updated, and removed to match the manifest), and the tunnel agent when a tunnel is configured. The generated configuration files and player lists are rewritten from the manifest on -every start. A warning is printed for every entry of the manifest's -`properties` block that is not a vanilla server property. +every start. A warning is printed for every key in the manifest's +`properties` block that is not a known server property. Only one running server is allowed per instance directory; a second `mc run` refuses to start. diff --git a/docs/src/reference/manifest.md b/docs/src/reference/manifest.md index ad872c9..984ab0b 100644 --- a/docs/src/reference/manifest.md +++ b/docs/src/reference/manifest.md @@ -195,8 +195,9 @@ must be quoted, as above; a nested block spells the same key, so `rcon { broadcast "yes" }` sets `rcon.broadcast`. Any key is accepted, since mods read their own settings from the same -file, but [`mc run`](../commands/run.md) prints a warning for each key -that is not a vanilla server property, so a typo does not go unnoticed. +file, but [`mc run`](../commands/run.md) prints a warning for each key in +this block that is not a known server property, so a typo does not go +unnoticed. Keys managed by mc are rejected: an entry for a key that a `server` field or the top-level `name` and `description` already drive is an error naming diff --git a/src/minecraft/server_properties.rs b/src/minecraft/server_properties.rs index 471704f..364127e 100644 --- a/src/minecraft/server_properties.rs +++ b/src/minecraft/server_properties.rs @@ -301,8 +301,8 @@ impl ServerProperties { Ok(password) } - // Mods read their own keys from server.properties, so an unknown key is a - // warning rather than an error. + // Mods read their own keys from the same file, so a key that is not a + // vanilla one is a warning rather than an error. pub fn unknown_keys(overrides: &BTreeMap) -> McResult> { let s = serde_java_properties::to_string(&ServerProperties::default()) .context("could not serialize server.properties")?; diff --git a/src/ops/run.rs b/src/ops/run.rs index c5eaba4..a47e0b2 100644 --- a/src/ops/run.rs +++ b/src/ops/run.rs @@ -217,7 +217,7 @@ pub async fn run(context: &mut McContext, options: &RunOptions) -> McResult Date: Mon, 7 Sep 2026 12:52:46 -0400 Subject: [PATCH 3/3] detect unknown property keys while building the entries --- src/minecraft/server_properties.rs | 41 +++++++++++++----------------- src/minecraft/tests.rs | 16 +++++++++--- src/ops/run.rs | 10 +++++--- 3 files changed, 38 insertions(+), 29 deletions(-) diff --git a/src/minecraft/server_properties.rs b/src/minecraft/server_properties.rs index 364127e..46e454a 100644 --- a/src/minecraft/server_properties.rs +++ b/src/minecraft/server_properties.rs @@ -212,6 +212,11 @@ pub const MANAGED_PROPERTY_KEYS: [(&str, &str); 18] = [ ("white-list", "set `allow-list` in `server` instead") ]; +pub struct PropertyEntries { + pub entries: BTreeMap, + pub unknown_keys: Vec +} + // The subset of server.properties driven by `mc.kdl` and the environment. // Serializing it yields the managed keys. #[derive(Serialize)] @@ -301,42 +306,32 @@ impl ServerProperties { Ok(password) } - // Mods read their own keys from the same file, so a key that is not a - // vanilla one is a warning rather than an error. - pub fn unknown_keys(overrides: &BTreeMap) -> McResult> { - let s = serde_java_properties::to_string(&ServerProperties::default()) - .context("could not serialize server.properties")?; - - let known: BTreeMap = serde_java_properties::from_str(&s) - .context("could not parse the generated server.properties")?; - - Ok(overrides - .keys() - .filter(|key| { - !known.contains_key(*key) - && !MANAGED_PROPERTY_KEYS - .iter() - .any(|(managed, _)| managed == key) - }) - .cloned() - .collect()) - } - pub fn to_entries( &self, overrides: &BTreeMap, managed: &BTreeMap - ) -> McResult> { + ) -> McResult { let s = serde_java_properties::to_string(self) .context("could not serialize server.properties")?; let mut entries: BTreeMap = serde_java_properties::from_str(&s) .context("could not parse the generated server.properties")?; + // Mods read their own keys from the same file, so an override for a + // key that is not a vanilla one is reported rather than rejected. + let unknown_keys = overrides + .keys() + .filter(|key| !entries.contains_key(*key)) + .cloned() + .collect(); + entries.extend(overrides.clone()); entries.extend(managed.clone()); - Ok(entries) + Ok(PropertyEntries { + entries, + unknown_keys + }) } pub fn entries_to_string(entries: &BTreeMap) -> McResult { diff --git a/src/minecraft/tests.rs b/src/minecraft/tests.rs index 544f8d8..763deaa 100644 --- a/src/minecraft/tests.rs +++ b/src/minecraft/tests.rs @@ -60,14 +60,24 @@ fn unknown_property_keys_are_reported() -> McResult<()> { let overrides = BTreeMap::from([ (String::from("spawn-protection"), String::from("0")), (String::from("query.port"), String::from("25565")), - (String::from("level-seed"), String::from("42")), (String::from("spawn-protetion"), String::from("0")), (String::from("fabric.custom"), String::from("x")) ]); - let unknown = ServerProperties::unknown_keys(&overrides)?; + let result = ServerProperties::default().to_entries(&overrides, &BTreeMap::new())?; - assert_eq!(unknown, vec!["fabric.custom", "spawn-protetion"]); + assert_eq!( + result.unknown_keys, + vec!["fabric.custom", "spawn-protetion"] + ); + assert_eq!( + result.entries.get("spawn-protection").map(String::as_str), + Some("0") + ); + assert_eq!( + result.entries.get("spawn-protetion").map(String::as_str), + Some("0") + ); Ok(()) } diff --git a/src/ops/run.rs b/src/ops/run.rs index a47e0b2..b710a9f 100644 --- a/src/ops/run.rs +++ b/src/ops/run.rs @@ -19,6 +19,7 @@ use crate::manifest::Manifest; use crate::manifest::ManifestPaths; use crate::minecraft::log4j; use crate::minecraft::server_properties::ManagedServerProperties; +use crate::minecraft::server_properties::PropertyEntries; use crate::minecraft::server_properties::ServerProperties; use crate::ops; use crate::ops::backups::BackupOptions; @@ -215,15 +216,18 @@ pub async fn run(context: &mut McContext, options: &RunOptions) -> McResult