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
38 changes: 38 additions & 0 deletions libexec/mcserver-kit/compose-state.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
#!/usr/bin/env python3
"""Classify Docker Compose's JSON container listing for the CLI and TUI."""

import json
import sys


def classify(raw: str) -> str:
raw = raw.strip()
if not raw:
return "absent"

try:
parsed = json.loads(raw)
except json.JSONDecodeError:
try:
parsed = [json.loads(line) for line in raw.splitlines()]
except json.JSONDecodeError:
return "unavailable"

if isinstance(parsed, dict):
containers = [parsed]
elif isinstance(parsed, list) and all(isinstance(item, dict) for item in parsed):
containers = parsed
else:
return "unavailable"

if any(
item.get("Service") == "minecraft"
and str(item.get("State", "")).lower() == "running"
for item in containers
):
return "running"
return "stopped" if containers else "absent"


if __name__ == "__main__":
print(classify(sys.stdin.read()))
60 changes: 38 additions & 22 deletions libexec/mcserver-kit/home-tui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,7 @@ tool_availability() {
}

server_status() {
local directory="$1"
if (cd "$directory" && docker compose ps --status running --services 2>/dev/null | grep -qx minecraft); then
tr home.running
else
tr home.stopped
fi
"${SCRIPT_DIR}/server-manager.sh" server "$(basename "$1")" state
}

logo() {
Expand All @@ -100,7 +95,7 @@ dashboard_text() {
for directory in "$root"/*; do
[[ -d "$directory" && -f "${directory}/compose.yaml" ]] || continue
total=$((total + 1))
if (cd "$directory" && docker compose ps --status running --services 2>/dev/null | grep -qx minecraft); then
if [[ "$("${SCRIPT_DIR}/server-manager.sh" server "$(basename "$directory")" state --raw)" == running ]]; then
running=$((running + 1))
fi
done
Expand All @@ -124,36 +119,54 @@ pause_for_enter() {
run_and_show() {
local title="$1"
shift
local output
local output pid result=0 frame_index=0
local frames=('⠋' '⠙' '⠹' '⠸' '⠼' '⠴' '⠦' '⠧' '⠇' '⠏')
new_temp_file output
if "$@" >"$output" 2>&1; then
whiptail --title "$title" --textbox "$output" 22 84
"$@" >"$output" 2>&1 &
pid=$!
while kill -0 "$pid" 2>/dev/null; do
whiptail --title "$title" --infobox "$(tr home.processing "${frames[frame_index]}" "$title")" 8 72
frame_index=$(((frame_index + 1) % ${#frames[@]}))
sleep 0.15
done
wait "$pid" || result=$?
if [[ ! -s "$output" ]]; then
if ((result == 0)); then
tr home.completed >"$output"
else
tr home.failed_without_output >"$output"
fi
fi
if ((result == 0)); then
whiptail --title "$title" --textbox "$output" 22 84 || true
else
whiptail --title "$(tr common.error)" --textbox "$output" 22 84
whiptail --title "$(tr common.error)" --textbox "$output" 22 84 || true
fi
RUN_RESULT=$result
}

server_action_menu() {
local id="$1" directory="$2" choice output
while true; do
choice="$(whiptail --title "$id" --menu "$(tr home.server_status "$(server_status "$directory")")" 23 78 13 \
start "$(tr home.start)" \
down "$(tr home.down)" \
stop "$(tr home.stop)" \
restart "$(tr home.restart)" \
status "$(tr home.status)" \
logs "$(tr home.logs)" \
properties "$(tr home.properties)" \
open-data "$(tr home.open_data)" \
open-server "$(tr home.open_server)" \
down "$(tr home.down)" \
back "$(tr tui.back)" \
3>&1 1>&2 2>&3)" || return
case "$choice" in
start | stop | restart | status)
run_and_show "$id" "${SCRIPT_DIR}/server-manager.sh" server "$id" "$choice"
run_and_show "$(tr "home.${choice}") · $id" "${SCRIPT_DIR}/server-manager.sh" server "$id" "$choice"
;;
logs)
clear
printf '%s\n\n' "$(tr home.logs_return_hint)"
"${SCRIPT_DIR}/server-manager.sh" server "$id" logs || true
pause_for_enter
;;
Expand All @@ -172,7 +185,7 @@ server_action_menu() {
;;
down)
if whiptail --yesno "$(tr home.down_confirm "$id")" 10 72; then
run_and_show "$id" "${SCRIPT_DIR}/server-manager.sh" server "$id" down
run_and_show "$(tr home.down) · $id" "${SCRIPT_DIR}/server-manager.sh" server "$id" down
fi
;;
back) return ;;
Expand Down Expand Up @@ -235,7 +248,7 @@ diagnostics() {
}

main() {
local root choice installed_version
local root choice installed_version latest
command -v whiptail >/dev/null 2>&1 || {
tr tui.missing >&2
exit 1
Expand Down Expand Up @@ -269,15 +282,18 @@ main() {
language) language_menu ;;
diagnostics) diagnostics ;;
update)
clear
if "${SCRIPT_DIR}/update.sh"; then
installed_version="$(head -n 1 "${ROOT_DIR}/VERSION" 2>/dev/null || printf unknown)"
if [[ "$installed_version" != "$VERSION" ]]; then
clear
exec "${ROOT_DIR}/mcserver-kit" home
run_and_show "$(tr home.update)" "${SCRIPT_DIR}/update.sh" check
if ((RUN_RESULT == 0)); then
latest="$("${SCRIPT_DIR}/update.sh" --cached-quiet 2>/dev/null || true)"
if [[ -n "$latest" ]] && whiptail --yesno "$(tr home.update_confirm "$VERSION" "$latest")" 10 72; then
run_and_show "$(tr home.update)" "${SCRIPT_DIR}/update.sh" --yes
installed_version="$(head -n 1 "${ROOT_DIR}/VERSION" 2>/dev/null || printf unknown)"
if ((RUN_RESULT == 0)) && [[ "$installed_version" != "$VERSION" ]]; then
clear
exec "${ROOT_DIR}/mcserver-kit" home
fi
fi
fi
pause_for_enter
;;
help) run_and_show "$(tr home.help)" "${ROOT_DIR}/mcserver-kit" --help ;;
exit) return ;;
Expand Down
6 changes: 4 additions & 2 deletions libexec/mcserver-kit/i18n.sh
Original file line number Diff line number Diff line change
Expand Up @@ -43,10 +43,12 @@ load_messages() {

load_catalog() {
local catalog="$1"
local key encoded
local key encoded decoded
while IFS=$'\t' read -r key encoded; do
[[ -n "$key" ]] || continue
I18N_MESSAGES["$key"]="$(printf '%s' "$encoded" | base64 --decode)"
# The sentinel prevents command substitution from stripping translated newlines.
decoded="$(printf '%s' "$encoded" | base64 --decode; printf '\034')"
I18N_MESSAGES["$key"]="${decoded%$'\034'}"
done < <(python3 - "$catalog" <<'PYTHON'
import base64
import json
Expand Down
53 changes: 47 additions & 6 deletions libexec/mcserver-kit/server-manager.sh
Original file line number Diff line number Diff line change
Expand Up @@ -66,10 +66,7 @@ list_servers() {
[[ -d "$directory" && -f "${directory}/compose.yaml" ]] || continue
found=true
printf '%-28s ' "$(basename "$directory")"
(
cd "$directory"
docker compose ps --status running --services 2>/dev/null | grep -qx minecraft && printf '%s\n' "$(tr server.running)" || printf '%s\n' "$(tr server.stopped)"
)
server_state "$directory"
done
shopt -u nullglob
[[ "$found" == true ]] || printf '%s\n' "$(tr server.none)"
Expand All @@ -84,6 +81,24 @@ compose_in() {
)
}

# Compose emits either a JSON array or one object per line, depending on version.
# Keep these machine-readable values separate from the translated menu labels.
container_state() {
local directory="$1" listing
if ! listing="$(compose_in "$directory" ps -a --format json 2>/dev/null)"; then
printf 'unavailable\n'
return
fi
printf '%s\n' "$listing" | python3 "${SCRIPT_DIR}/compose-state.py"
}

server_state() {
local state
state="$(container_state "$1")"
tr "server.state_${state}"
printf '\n'
}

require_stopped() {
local directory="$1" running
running="$(compose_in "$directory" ps --status running --services)" || die "$(tr server.status_failed)"
Expand Down Expand Up @@ -174,15 +189,41 @@ manage_server() {
compose_in "$directory" ps
;;
status)
compose_in "$directory" ps
if [[ "$(container_state "$directory")" == absent ]]; then
tr server.no_containers
else
compose_in "$directory" ps -a
fi
;;
logs)
if [[ "$(container_state "$directory")" == absent ]]; then
tr server.no_containers_logs
return
fi
if [[ "${1-}" == '--no-follow' ]]; then
compose_in "$directory" logs minecraft
local log_file result=0
log_file="$(mktemp /tmp/mcserver-kit-logs.XXXXXX)"
compose_in "$directory" logs minecraft >"$log_file" || result=$?
if [[ -s "$log_file" ]]; then
cat -- "$log_file"
elif ((result == 0)); then
tr server.no_logs
fi
rm -f -- "$log_file"
if ((result != 0)); then
return "$result"
fi
else
compose_in "$directory" logs --follow minecraft
fi
;;
state)
if [[ "${1-}" == '--raw' ]]; then
container_state "$directory"
else
server_state "$directory"
fi
;;
down)
printf '%s\n' "$(tr server.down "$id")"
compose_in "$directory" down
Expand Down
16 changes: 14 additions & 2 deletions share/mcserver-kit/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"home.help": "Help",
"home.exit": "Exit",
"home.start": "Start",
"home.stop": "Stop",
"home.stop": "Stop (keep container)",
"home.restart": "Restart",
"home.status": "Show status",
"home.logs": "Follow logs",
Expand All @@ -44,8 +44,13 @@
"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",
"home.down": "Remove containers (keep world)",
"home.down_confirm": "Remove containers and networks for %s? World data is preserved.",
"home.processing": "%s Working on %s...",
"home.completed": "Completed.\n",
"home.failed_without_output": "The operation failed without a diagnostic message.\n",
"home.logs_return_hint": "Live logs: press Ctrl+C to stop following, then Enter to return. If no container exists, there are no logs to show.",
"home.update_confirm": "Install update %s -> %s now?",
"home.diagnostics_title": "Environment diagnostics",
"home.available": "available",
"home.unavailable": "unavailable",
Expand Down Expand Up @@ -133,6 +138,13 @@
"server.list_status": "STATUS",
"server.running": "running",
"server.stopped": "stopped",
"server.state_running": "running",
"server.state_stopped": "stopped (container retained)",
"server.state_absent": "no container",
"server.state_unavailable": "Docker unavailable",
"server.no_containers": "No containers exist for this server. Its world data is still on disk.\n",
"server.no_containers_logs": "No containers exist for this server, so there are no container logs.\n",
"server.no_logs": "This container has no logs yet.\n",
"server.none": "(no servers)",
"server.usage": "Usage: mcserver-kit server SERVER_ID start|stop|restart|status|logs|down|properties|import-properties|open",
"server.status_failed": "Could not check whether the server is running. Check Docker and try again.",
Expand Down
16 changes: 14 additions & 2 deletions share/mcserver-kit/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@
"home.help": "ヘルプ",
"home.exit": "終了",
"home.start": "起動",
"home.stop": "停止",
"home.stop": "停止(コンテナを残す)",
"home.restart": "再起動",
"home.status": "状態を表示",
"home.logs": "ログを表示",
Expand All @@ -44,8 +44,13 @@
"home.import_confirm": "%sにserver.propertiesをインポートしますか?先にサーバーを停止してください。既存ファイルをバックアップし、インポートした設定はserver.envで管理します。",
"home.open_data": "永続データをExplorerで開く",
"home.open_server": "サーバーフォルダをExplorerで開く",
"home.down": "コンテナを削除",
"home.down": "コンテナを削除(ワールドは保持)",
"home.down_confirm": "%sのコンテナとネットワークを削除しますか?ワールドデータは保持されます。",
"home.processing": "%s %sを処理中...",
"home.completed": "処理が完了しました。\n",
"home.failed_without_output": "処理に失敗しましたが、詳細なエラーメッセージはありません。\n",
"home.logs_return_hint": "ログの追跡を止めるにはCtrl+C、その後Enterで戻ります。コンテナがない場合はログもありません。",
"home.update_confirm": "%sから%sへ更新しますか?",
"home.diagnostics_title": "動作環境の診断",
"home.available": "利用可能",
"home.unavailable": "利用不可",
Expand Down Expand Up @@ -133,6 +138,13 @@
"server.list_status": "状態",
"server.running": "起動中",
"server.stopped": "停止中",
"server.state_running": "起動中",
"server.state_stopped": "停止中(コンテナあり)",
"server.state_absent": "コンテナなし",
"server.state_unavailable": "Dockerを利用できません",
"server.no_containers": "このサーバーのコンテナはありません。ワールドデータはディスクに残っています。\n",
"server.no_containers_logs": "このサーバーのコンテナがないため、コンテナログはありません。\n",
"server.no_logs": "このコンテナにはまだログがありません。\n",
"server.none": "(サーバーがありません)",
"server.usage": "使い方: mcserver-kit server サーバーID start|stop|restart|status|logs|down|properties|import-properties|open",
"server.status_failed": "サーバーが起動中か確認できませんでした。Dockerを確認して再試行してください。",
Expand Down
Loading