Previously a blocked join just dropped the socket, so the MC client showed 'Internal Exception: SocketException: Connection reset'. Now when next_state=2 (login), the proxy sends a proper Login Disconnect (0x00) packet containing a JSON chat component, and the client displays the message on its disconnect screen. - block_message added to config (default Korean message); editable in Settings UI as a textarea - build_login_disconnect() encodes (varint length)+(0x00)+(JSON str) - Status/ping (next_state=1) still silently dropped so the proxy presence is not announced to scanners - Backward-compat: load_config() backfills block_message on old files
60 lines
1.7 KiB
Python
60 lines
1.7 KiB
Python
"""프록시용 config 로더.
|
|
|
|
API 서비스와 동일한 `data/config.json` 파일을 공유 볼륨으로 읽는다.
|
|
atomic rename(tempfile + os.replace) 으로 갱신되기 때문에 mtime polling
|
|
방식으로 안전하게 hot reload 가 가능하다.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import threading
|
|
from pathlib import Path
|
|
|
|
CONFIG_PATH = Path(os.environ.get("MC_CONFIG_PATH", "/data/config.json"))
|
|
|
|
DEFAULT_CONFIG = {
|
|
"proxy": {"listen_port": 25565, "enabled": True},
|
|
"backend": {"host": "127.0.0.1", "port": 25565},
|
|
"allowed_domains": [
|
|
{"domain": "mc.tkrmagid.kr", "enabled": True, "note": "메인 도메인"}
|
|
],
|
|
"block_message": "이 서버는 허용된 도메인에서만 접속 가능합니다.",
|
|
}
|
|
|
|
_lock = threading.Lock()
|
|
|
|
|
|
def load() -> dict:
|
|
if not CONFIG_PATH.exists():
|
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
save(DEFAULT_CONFIG)
|
|
return dict(DEFAULT_CONFIG)
|
|
with _lock:
|
|
with CONFIG_PATH.open("r", encoding="utf-8") as f:
|
|
return json.load(f)
|
|
|
|
|
|
def save(cfg: dict) -> None:
|
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
tmp = CONFIG_PATH.with_suffix(".json.tmp")
|
|
with _lock:
|
|
with tmp.open("w", encoding="utf-8") as f:
|
|
json.dump(cfg, f, ensure_ascii=False, indent=2)
|
|
os.replace(tmp, CONFIG_PATH)
|
|
|
|
|
|
def mtime() -> float:
|
|
try:
|
|
return CONFIG_PATH.stat().st_mtime
|
|
except FileNotFoundError:
|
|
return 0.0
|
|
|
|
|
|
def allowed_domain_set(cfg: dict) -> set[str]:
|
|
return {
|
|
d["domain"].lower().strip()
|
|
for d in cfg.get("allowed_domains", [])
|
|
if d.get("enabled", True)
|
|
}
|