From b8a8586616201a3b2b4cb3b28509c5beea2b5ae4 Mon Sep 17 00:00:00 2001 From: cotore-game <102813037+cotore-game@users.noreply.github.com> Date: Thu, 17 Sep 2026 05:09:17 +0900 Subject: [PATCH 1/2] fix: keep imported server properties in server.env --- libexec/mcserver-kit/server-config.py | 38 +++++++++++++++++++++++--- libexec/mcserver-kit/server-manager.sh | 2 +- tests/run-tests.sh | 25 ++++++++++++----- 3 files changed, 53 insertions(+), 12 deletions(-) diff --git a/libexec/mcserver-kit/server-config.py b/libexec/mcserver-kit/server-config.py index c41ab14..f52d542 100755 --- a/libexec/mcserver-kit/server-config.py +++ b/libexec/mcserver-kit/server-config.py @@ -141,12 +141,45 @@ def validate_properties_source(source: Path) -> None: raise ValueError(f"Not a file: {source}") if source.name != "server.properties": raise ValueError("Select a file named server.properties") - if not read_properties(source): + if not parse_import_properties(source): raise ValueError("The selected file has no property entries") +def parse_import_properties(source: Path) -> dict[str, str]: + """Read the simple key=value format emitted by Minecraft without dropping entries.""" + values: dict[str, str] = {} + for number, line in enumerate(source.read_text(encoding="utf-8-sig").splitlines(), 1): + stripped = line.strip() + if not stripped or stripped.startswith(("#", "!")): + continue + if "=" not in line: + raise ValueError(f"Line {number}: expected key=value") + key, value = line.split("=", 1) + key = key.strip() + if not re.fullmatch(r"[A-Za-z0-9_.-]+", key): + raise ValueError(f"Line {number}: unsupported property key: {key}") + if "\\" in key or "\\" in value: + raise ValueError(f"Line {number}: escaped properties are not supported") + values[key] = value + if values.get("level-name", "world") != "world": + raise ValueError("level-name must be world for this server layout") + if values.get("server-port", "25565") != "25565": + raise ValueError("server-port must be 25565 for this server layout") + return values + + def import_properties(server_dir: Path, source: Path) -> Path | None: validate_properties_source(source) + imported = parse_import_properties(source) + env_path = server_dir / "server.env" + values = read_env(env_path) + for env_key, property_key in PROPERTY_KEYS.items(): + if property_key in imported: + values[env_key] = imported[property_key] + known = set(PROPERTY_KEYS.values()) | {"level-name", "server-port"} + extras = [f"{key}={value}" for key, value in imported.items() if key not in known] + values["CUSTOM_SERVER_PROPERTIES"] = "\n".join(extras) + values["OVERRIDE_SERVER_PROPERTIES"] = "true" destination = server_dir / "data" / "server.properties" destination.parent.mkdir(parents=True, exist_ok=True) @@ -167,9 +200,6 @@ def import_properties(server_dir: Path, source: Path) -> Path | None: with os.fdopen(descriptor, "wb") as output, source.open("rb") as input_file: shutil.copyfileobj(input_file, output) os.chmod(temporary_name, 0o644) - env_path = server_dir / "server.env" - values = read_env(env_path) - values["OVERRIDE_SERVER_PROPERTIES"] = "false" write_env(env_path, values) os.replace(temporary_name, destination) finally: diff --git a/libexec/mcserver-kit/server-manager.sh b/libexec/mcserver-kit/server-manager.sh index 94ed1d1..0e2a1c4 100755 --- a/libexec/mcserver-kit/server-manager.sh +++ b/libexec/mcserver-kit/server-manager.sh @@ -121,7 +121,7 @@ import_properties() { backup="$(python3 "$CONFIG_TOOL" import-properties "$directory" "$source")" || die "$(tr server.import_failed)" compose_in "$directory" config --quiet || die "$(tr properties.compose_invalid)" [[ -z "$backup" ]] || printf '%s\n' "$(tr server.import_backup "$backup")" - printf '%s\n' "$(tr server.import_done "${directory}/data/server.properties")" + printf '%s\n' "$(tr server.import_done "${directory}/server.env")" } open_folder() { diff --git a/tests/run-tests.sh b/tests/run-tests.sh index 44ec56b..49a6d7b 100755 --- a/tests/run-tests.sh +++ b/tests/run-tests.sh @@ -755,6 +755,7 @@ PROPERTIES motd=Distributed MOTD difficulty=hard custom-setting=preserve-me +plugin.option=enabled PROPERTIES cat >"${fake_bin}/docker" <<'DOCKER' #!/usr/bin/env bash @@ -778,6 +779,16 @@ EXPLORER bash "${REPO_ROOT}/mcserver-kit" server alpha import-properties "${source_dir}/invalid.properties" assert_equal 'absent' "$(! grep -q '^MAX_PLAYERS=' "${server_dir}/server.env" && printf absent)" 'invalid import does not migrate server settings' + mkdir -p "${source_dir}/incompatible" + cat >"${source_dir}/incompatible/server.properties" <<'PROPERTIES' +motd=Should not be imported +server-port=25566 +PROPERTIES + assert_fails 'an incompatible server port is rejected before migration' \ + env PATH="${fake_bin}:$PATH" MCSERVER_KIT_LANG=en MCSERVER_KIT_CONFIG="$config_file" \ + bash "${REPO_ROOT}/mcserver-kit" server alpha import-properties "${source_dir}/incompatible/server.properties" + assert_equal 'Old MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" MOTD)" 'rejected import leaves the environment unchanged' + assert_fails 'import refuses to replace properties while the server is running' \ env PATH="${fake_bin}:$PATH" MCSERVER_KIT_LANG=en MCSERVER_KIT_CONFIG="$config_file" \ MCSERVER_KIT_TEST_RUNNING=true bash "${REPO_ROOT}/mcserver-kit" server alpha import-properties "${source_dir}/server.properties" @@ -786,16 +797,16 @@ EXPLORER output="$(PATH="${fake_bin}:$PATH" MCSERVER_KIT_LANG=en MCSERVER_KIT_CONFIG="$config_file" \ bash "${REPO_ROOT}/mcserver-kit" server alpha import-properties "${source_dir}/server.properties")" assert_equal 'Distributed MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" property-get "${server_dir}/data/server.properties" MOTD)" 'import copies the supplied server.properties' - assert_equal 'false' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" OVERRIDE_SERVER_PROPERTIES)" 'import disables property overrides for this server' + assert_equal 'true' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" OVERRIDE_SERVER_PROPERTIES)" 'import keeps server.env authoritative' + assert_equal 'Distributed MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" MOTD)" 'import converts managed properties to environment settings' + assert_equal 'hard' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" DIFFICULTY)" 'import updates the difficulty' + assert_equal $'custom-setting=preserve-me\nplugin.option=enabled' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" CUSTOM_SERVER_PROPERTIES)" 'import converts extra properties to CUSTOM_SERVER_PROPERTIES' assert_equal 'present' "$(grep -q 'custom-setting=preserve-me' "${server_dir}/data/server.properties" && printf present)" 'import keeps custom settings' assert_equal 'present' "$(grep -q 'motd=Old MOTD' "${server_dir}"/data/server.properties.mcserver-kit.*.bak && printf present)" 'import backs up the previous properties' assert_equal 'present' "$(grep -q 'Previous server.properties backup' <<<"$output" && printf present)" 'import reports the backup location' python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" migrate "$server_dir" - assert_equal 'false' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" OVERRIDE_SERVER_PROPERTIES)" 'later migration preserves manual property mode' - python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" property-set "${server_dir}/data/server.properties" MOTD 'Edited MOTD' - assert_equal 'Edited MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" property-get "${server_dir}/data/server.properties" MOTD)" 'manual property editor writes the data file' - assert_equal 'present' "$(grep -q 'custom-setting=preserve-me' "${server_dir}/data/server.properties" && printf present)" 'manual property editor preserves unrelated entries' + assert_equal 'true' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" OVERRIDE_SERVER_PROPERTIES)" 'later migration preserves environment property mode' cat >"${fake_bin}/whiptail" <<'WHIPTAIL' #!/usr/bin/env bash @@ -817,8 +828,8 @@ WHIPTAIL PATH="${fake_bin}:$PATH" MCSERVER_KIT_LANG=en \ MCSERVER_KIT_TEST_WHIPTAIL_STATE="${temp_dir}/property-import/whiptail-state" \ bash "${REPO_ROOT}/libexec/mcserver-kit/server-properties-tui.sh" alpha "$server_dir" >/dev/null - assert_equal 'TUI MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" property-get "${server_dir}/data/server.properties" MOTD)" 'the TUI edits imported properties directly' - assert_equal 'Old MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" MOTD)" 'manual property edits do not rewrite the old environment value' + assert_equal 'TUI MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" get "${server_dir}/server.env" MOTD)" 'the TUI edits the authoritative environment value' + assert_equal 'Distributed MOTD' "$(python3 "${REPO_ROOT}/libexec/mcserver-kit/server-config.py" property-get "${server_dir}/data/server.properties" MOTD)" 'the data file changes only when the container applies server.env' PATH="${fake_bin}:$PATH" MCSERVER_KIT_LANG=en MCSERVER_KIT_CONFIG="$config_file" \ MCSERVER_KIT_TEST_EXPLORER_LOG="${temp_dir}/property-import/explorer.log" \ From b1df1c97a633beed080677790f51d3481689db7c Mon Sep 17 00:00:00 2001 From: cotore-game <102813037+cotore-game@users.noreply.github.com> Date: Thu, 17 Sep 2026 05:09:17 +0900 Subject: [PATCH 2/2] ui: move property import into server settings --- README-JA.md | 2 +- README.md | 2 +- libexec/mcserver-kit/home-tui.sh | 8 ------- libexec/mcserver-kit/server-properties-tui.sh | 21 +++++++++++++++++++ share/mcserver-kit/locales/en.json | 5 +++-- share/mcserver-kit/locales/ja.json | 5 +++-- 6 files changed, 29 insertions(+), 14 deletions(-) diff --git a/README-JA.md b/README-JA.md index 3d66dc9..4fea211 100644 --- a/README-JA.md +++ b/README-JA.md @@ -189,7 +189,7 @@ MOTD、難易度、ゲームモード、最大人数、オンラインモード 通常、ツールが管理する設定の正本は各サーバーの`server.env`です。Docker Composeが値を`itzg/minecraft-server`へ渡し、コンテナ起動時に`server.properties`へ反映します。 -配布された`server.properties`を使う場合は、サーバーを停止してホーム画面の「server.propertiesをインポート」を選ぶか、`mcserver-kit server import-properties /path/to/server.properties`を実行します。Windowsダイアログを有効にしているWSL環境では、ホーム画面からWindowsのファイル選択画面を開けます。既存ファイルは置換前にバックアップされます。インポートしたサーバーだけ、プロパティ設定の正本が`data/server.properties`に切り替わり、設定画面もそのファイルを読み書きします。ホワイトリストとOPのメンバー一覧は引き続き`server.env`で管理します。起動中にファイルを直接編集しないでください。 +配布された`server.properties`を使う場合は、サーバーを停止して「サーバー設定 → server.propertiesをインポート」を選ぶか、`mcserver-kit server import-properties /path/to/server.properties`を実行します。Windowsダイアログを有効にしているWSL環境では、設定画面からWindowsのファイル選択画面を開けます。既存のデータファイルは置換前にバックアップします。対応する項目を`server.env`へ取り込み、その他のキーは`CUSTOM_SERVER_PROPERTIES`へ保存します。設定の正本は引き続き`server.env`で、元ファイルも`data/server.properties`へコピーします。ワールド名やポートがこのサーバー構成と異なる場合、または未対応の書式の場合は黙って変更せずエラーにします。サーバー起動中にファイルを直接編集しないでください。 ホーム画面からサーバーフォルダ、または永続データの`data/`をWindowsのExplorerで開けます。コマンドでは`mcserver-kit server open server`または`open data`です。WSLとExplorerの連携が必要です。 diff --git a/README.md b/README.md index 6f7b3a2..da3563c 100644 --- a/README.md +++ b/README.md @@ -188,7 +188,7 @@ The editor covers MOTD, difficulty, game mode, player limit, online mode, whitel Normally, `server.env` is the source of truth for settings managed by the toolkit. Docker Compose passes these values to `itzg/minecraft-server`, which applies them to `server.properties` when the container starts. -For a distributed `server.properties`, stop the server and choose **Import server.properties** in the dashboard, or run `mcserver-kit server import-properties /path/to/server.properties`. On WSL, the dashboard offers a Windows file picker when Windows dialogs are enabled. The existing file is backed up before replacement. Only that server switches to using `data/server.properties` as the source of truth for property settings; the editor then reads and writes that file. Whitelist and OP member lists remain in `server.env`. Do not edit the file while the server is running. +For a distributed `server.properties`, stop the server and choose **Server settings → Import server.properties**, or run `mcserver-kit server import-properties /path/to/server.properties`. On WSL, the settings screen offers a Windows file picker when Windows dialogs are enabled. The existing data file is backed up before replacement. Supported properties are imported into `server.env`; additional keys are stored in `CUSTOM_SERVER_PROPERTIES`. `server.env` remains the source of truth, and the imported file is also copied to `data/server.properties`. Imports with a nonstandard world name, port, or unsupported property syntax are rejected rather than silently changing the server layout. Do not edit the file while the server is running. The dashboard can also open the server folder or its persistent `data/` folder in Windows Explorer. From a terminal, use `mcserver-kit server open server` or `open data`. This requires WSL interop with Explorer. diff --git a/libexec/mcserver-kit/home-tui.sh b/libexec/mcserver-kit/home-tui.sh index a47c063..8988f0e 100755 --- a/libexec/mcserver-kit/home-tui.sh +++ b/libexec/mcserver-kit/home-tui.sh @@ -143,7 +143,6 @@ server_action_menu() { status "$(tr home.status)" \ logs "$(tr home.logs)" \ properties "$(tr home.properties)" \ - import-properties "$(tr home.import_properties)" \ open-data "$(tr home.open_data)" \ open-server "$(tr home.open_server)" \ down "$(tr home.down)" \ @@ -161,13 +160,6 @@ server_action_menu() { properties) "${SCRIPT_DIR}/server-manager.sh" server "$id" properties || true ;; - import-properties) - if whiptail --yesno "$(tr home.import_confirm "$id")" 12 76; then - clear - "${SCRIPT_DIR}/server-manager.sh" server "$id" import-properties || true - pause_for_enter - fi - ;; open-data | open-server) new_temp_file output if [[ "$choice" == open-data ]]; then diff --git a/libexec/mcserver-kit/server-properties-tui.sh b/libexec/mcserver-kit/server-properties-tui.sh index 9d67445..2622eab 100755 --- a/libexec/mcserver-kit/server-properties-tui.sh +++ b/libexec/mcserver-kit/server-properties-tui.sh @@ -220,6 +220,25 @@ finish() { fi } +import_properties() { + local output result + whiptail --yesno "$(tr home.import_confirm "$SERVER_ID")" 12 76 || return + output="$(mktemp)" + if "${SCRIPT_DIR}/server-manager.sh" server "$SERVER_ID" import-properties >"$output" 2>&1; then + if [[ -s "$output" ]]; then + whiptail --title "$(tr properties.import)" --textbox "$output" 16 82 + manual_properties=false + changed=true + fi + else + result=$? + whiptail --title "$(tr common.error)" --textbox "$output" 16 82 + rm -f -- "$output" + return "$result" + fi + rm -f -- "$output" +} + main() { local selected local items @@ -260,11 +279,13 @@ main() { SIMULATION_DISTANCE "$(menu_item "$(tr properties.simulation_distance)" "$(setting_get SIMULATION_DISTANCE 10)")" __more "$(tr properties.more_settings)" __resource "$(tr properties.resource_pack_settings)" + __import "$(tr properties.import)" __exit "$(tr properties.exit)" ) selected="$(whiptail --title "${SERVER_ID}" --menu "$(tr properties.choose)" 25 94 18 "${items[@]}" 3>&1 1>&2 2>&3)" || break case "$selected" in __exit) break ;; + __import) import_properties || true ;; __more) selected="$(whiptail --title "${SERVER_ID}" --menu "$(tr properties.more_settings)" 24 90 15 \ FORCE_GAMEMODE "$(menu_item "$(tr properties.force_gamemode)" "$(setting_get FORCE_GAMEMODE false)")" \ diff --git a/share/mcserver-kit/locales/en.json b/share/mcserver-kit/locales/en.json index ccc1a89..4618302 100644 --- a/share/mcserver-kit/locales/en.json +++ b/share/mcserver-kit/locales/en.json @@ -41,7 +41,7 @@ "home.logs": "Follow logs", "home.properties": "Server settings", "home.import_properties": "Import server.properties", - "home.import_confirm": "Import server.properties for %s? Stop the server first. The current file will be backed up, and this server will use data/server.properties for property settings.", + "home.import_confirm": "Import server.properties for %s? Stop the server first. The current file will be backed up. Imported settings will be managed in server.env.", "home.open_data": "Open persistent data in Explorer", "home.open_server": "Open server folder in Explorer", "home.down": "Remove containers", @@ -142,7 +142,7 @@ "server.import_source_missing": "File not found: %s", "server.import_failed": "Could not import server.properties.", "server.import_backup": "Previous server.properties backup: %s", - "server.import_done": "Imported to %s. This server now uses data/server.properties as the source of truth for property settings.", + "server.import_done": "Imported successfully. Property settings are managed in %s; the original file was copied to data/server.properties.", "server.open_usage": "Usage: mcserver-kit server SERVER_ID open [data|server]", "server.open_missing": "Folder not found: %s", "server.explorer_unavailable": "Windows Explorer is unavailable. This action requires WSL interop and wslpath.", @@ -177,6 +177,7 @@ "properties.current": "%s: %s", "properties.exit": "Save and exit", "properties.choose": "Choose a setting to edit", + "properties.import": "Import server.properties", "properties.selected": "(current)", "properties.motd": "Message of the day (MOTD)", "properties.max_players": "Maximum players", diff --git a/share/mcserver-kit/locales/ja.json b/share/mcserver-kit/locales/ja.json index a322c55..94fdf86 100644 --- a/share/mcserver-kit/locales/ja.json +++ b/share/mcserver-kit/locales/ja.json @@ -41,7 +41,7 @@ "home.logs": "ログを表示", "home.properties": "サーバー設定", "home.import_properties": "server.propertiesをインポート", - "home.import_confirm": "%sにserver.propertiesをインポートしますか?先にサーバーを停止してください。既存ファイルをバックアップし、このサーバーのプロパティ設定はdata/server.propertiesを正本に切り替えます。", + "home.import_confirm": "%sにserver.propertiesをインポートしますか?先にサーバーを停止してください。既存ファイルをバックアップし、インポートした設定はserver.envで管理します。", "home.open_data": "永続データをExplorerで開く", "home.open_server": "サーバーフォルダをExplorerで開く", "home.down": "コンテナを削除", @@ -142,7 +142,7 @@ "server.import_source_missing": "ファイルが見つかりません: %s", "server.import_failed": "server.propertiesをインポートできませんでした。", "server.import_backup": "元のserver.propertiesのバックアップ: %s", - "server.import_done": "%sにインポートしました。このサーバーのプロパティ設定はdata/server.propertiesが正本になります。", + "server.import_done": "インポートしました。プロパティ設定は%sで管理し、元ファイルはdata/server.propertiesにコピーしました。", "server.open_usage": "使い方: mcserver-kit server サーバーID open [data|server]", "server.open_missing": "フォルダが見つかりません: %s", "server.explorer_unavailable": "Windows Explorerを利用できません。WSL連携とwslpathが必要です。", @@ -177,6 +177,7 @@ "properties.current": "%s: %s", "properties.exit": "保存して終了", "properties.choose": "編集する設定を選択してください", + "properties.import": "server.propertiesをインポート", "properties.selected": "(現在値)", "properties.motd": "サーバー説明(MOTD)", "properties.max_players": "最大プレイヤー数",