Skip to content
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
2 changes: 1 addition & 1 deletion README-JA.md
Original file line number Diff line number Diff line change
Expand Up @@ -189,7 +189,7 @@ MOTD、難易度、ゲームモード、最大人数、オンラインモード

通常、ツールが管理する設定の正本は各サーバーの`server.env`です。Docker Composeが値を`itzg/minecraft-server`へ渡し、コンテナ起動時に`server.properties`へ反映します。

配布された`server.properties`を使う場合は、サーバーを停止してホーム画面の「server.propertiesをインポート」を選ぶか、`mcserver-kit server <server-id> import-properties /path/to/server.properties`を実行します。Windowsダイアログを有効にしているWSL環境では、ホーム画面からWindowsのファイル選択画面を開けます。既存ファイルは置換前にバックアップされます。インポートしたサーバーだけ、プロパティ設定の正本が`data/server.properties`に切り替わり、設定画面もそのファイルを読み書きします。ホワイトリストとOPのメンバー一覧は引き続き`server.env`で管理します。起動中にファイルを直接編集しないでください。
配布された`server.properties`を使う場合は、サーバーを停止して「サーバー設定 → server.propertiesをインポート」を選ぶか、`mcserver-kit server <server-id> 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 <server-id> open server`または`open data`です。WSLとExplorerの連携が必要です。

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <server-id> 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 <server-id> 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 <server-id> open server` or `open data`. This requires WSL interop with Explorer.

Expand Down
8 changes: 0 additions & 8 deletions libexec/mcserver-kit/home-tui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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)" \
Expand All @@ -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
Expand Down
38 changes: 34 additions & 4 deletions libexec/mcserver-kit/server-config.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand All @@ -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:
Expand Down
2 changes: 1 addition & 1 deletion libexec/mcserver-kit/server-manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down
21 changes: 21 additions & 0 deletions libexec/mcserver-kit/server-properties-tui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)")" \
Expand Down
5 changes: 3 additions & 2 deletions share/mcserver-kit/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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.",
Expand Down Expand Up @@ -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",
Expand Down
5 changes: 3 additions & 2 deletions share/mcserver-kit/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -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": "コンテナを削除",
Expand Down Expand Up @@ -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が必要です。",
Expand Down Expand Up @@ -177,6 +177,7 @@
"properties.current": "%s: %s",
"properties.exit": "保存して終了",
"properties.choose": "編集する設定を選択してください",
"properties.import": "server.propertiesをインポート",
"properties.selected": "(現在値)",
"properties.motd": "サーバー説明(MOTD)",
"properties.max_players": "最大プレイヤー数",
Expand Down
25 changes: 18 additions & 7 deletions tests/run-tests.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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"
Expand All @@ -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
Expand All @@ -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" \
Expand Down