Files
mc_domain_proxy/api/config_io.py
claude-bot 75c4242365 feat: send Login Disconnect with custom message on blocked join
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
2026-05-23 17:10:49 +09:00

65 lines
2.2 KiB
Python

"""API 측 config IO 헬퍼.
프록시와 동일한 파일을 가리키고, atomic rename 으로 갱신해서 프록시가
mtime polling 으로 안전하게 hot reload 할 수 있게 한다.
"""
from __future__ import annotations
import json
import os
import threading
import time
from pathlib import Path
CONFIG_PATH = Path(os.environ.get("MC_CONFIG_PATH", "/data/config.json"))
LOG_DB = Path(os.environ.get("MC_LOG_DB", "/data/logs.db"))
RESTART_SIGNAL = Path(os.environ.get("MC_RESTART_SIGNAL", "/data/restart.signal"))
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_config() -> dict:
if not CONFIG_PATH.exists():
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
save_config(DEFAULT_CONFIG)
return json.loads(json.dumps(DEFAULT_CONFIG))
with _lock:
with CONFIG_PATH.open("r", encoding="utf-8") as f:
cfg = json.load(f)
# 구버전 설정 파일 호환: 누락된 최상위 키는 기본값으로 채워준다
if "block_message" not in cfg:
cfg["block_message"] = DEFAULT_CONFIG["block_message"]
return cfg
def save_config(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 touch_restart_signal() -> float:
"""프록시에 강제 listener restart 를 요청하는 신호 파일 갱신.
config 가 바뀌지 않은 상태에서도 listener 를 stop→start 시키고 싶을 때 사용.
"""
RESTART_SIGNAL.parent.mkdir(parents=True, exist_ok=True)
ts = time.time()
with _lock:
# touch: 파일이 없으면 만들고, 있으면 mtime 만 갱신
RESTART_SIGNAL.touch(exist_ok=True)
os.utime(RESTART_SIGNAL, (ts, ts))
return ts