fix: real listener restart + remove listen_port editing from UI

Reviewer concerns:
1. POST /api/proxy/restart only saved config (reload), did not restart
   the listener. Now it touches data/restart.signal; proxy watcher
   polls that file separately and force-restarts the listener even
   when config is unchanged.
2. Editing proxy.listen_port via UI could break Docker port mapping
   (compose publishes 25565:25565 only). UI now shows it read-only;
   README documents how to change it together with compose.

- proxy/main.py: ProxyState.check_restart_signal() + watcher uses it
- api/config_io.py: touch_restart_signal() helper
- api/routes/status.py: /api/proxy/restart -> touch_restart_signal()
- frontend Settings: disabled listen_port input + 프록시 재시작 button
- README + .gitignore updated
This commit is contained in:
2026-05-20 16:47:42 +09:00
parent d10dae5cb9
commit 4b0d790748
7 changed files with 82 additions and 19 deletions

View File

@@ -20,6 +20,7 @@ import config as cfg_mod
from handshake import HandshakeError, parse_handshake, read_handshake_bytes
LOG_DB = Path(os.environ.get("MC_LOG_DB", "/data/logs.db"))
RESTART_SIGNAL = Path(os.environ.get("MC_RESTART_SIGNAL", "/data/restart.signal"))
logging.basicConfig(
level=logging.INFO,
@@ -76,10 +77,18 @@ def log_event(
# ---------------------------------------------------------------------------
# Runtime state
# ---------------------------------------------------------------------------
def _signal_mtime() -> float:
try:
return RESTART_SIGNAL.stat().st_mtime
except FileNotFoundError:
return 0.0
class ProxyState:
def __init__(self) -> None:
self.cfg = cfg_mod.load()
self.cfg_mtime = cfg_mod.mtime()
self.signal_mtime = _signal_mtime()
self.listen_port: int = int(self.cfg["proxy"]["listen_port"])
def allowed(self) -> set[str]:
@@ -110,6 +119,15 @@ class ProxyState:
log.warning("config reload failed: %s", exc)
return False
def check_restart_signal(self) -> bool:
"""`POST /api/proxy/restart` 가 touch 한 신호 파일 변경 여부."""
m = _signal_mtime()
if m == self.signal_mtime:
return False
self.signal_mtime = m
log.info("restart signal received")
return True
# ---------------------------------------------------------------------------
# TCP tunneling
@@ -249,11 +267,16 @@ async def config_watcher(state: ProxyState, listener: Listener) -> None:
await asyncio.sleep(2)
old_port = state.listen_port
old_enabled = state.enabled()
if not state.reload_if_changed():
continue
config_changed = state.reload_if_changed()
signal_changed = state.check_restart_signal()
new_port = int(state.cfg["proxy"]["listen_port"])
new_enabled = state.enabled()
if new_port != old_port or new_enabled != old_enabled:
port_or_enabled_changed = (
config_changed and (new_port != old_port or new_enabled != old_enabled)
)
if signal_changed or port_or_enabled_changed:
state.listen_port = new_port
await listener.restart()