11 Commits

Author SHA1 Message Date
7362b45846 feat(logs): date filter + clear log endpoints
- API GET /api/logs now accepts from_ts / to_ts (unix epoch, half-open
  [from, to)) so callers can scope by arbitrary time range.
- API DELETE /api/logs added. Same from_ts / to_ts semantics. No params
  = wipe everything and reset the AUTOINCREMENT counter.
- Dashboard Logs page: date picker that scopes both the view and the
  delete button to the selected day in the user's local timezone. The
  clear button is red and confirms before deleting; label switches
  between "전체 로그 초기화" and "<날짜> 하루치 삭제".
2026-05-23 17:55:58 +09:00
8312cfe861 fix(proxy): flush disconnect packet cleanly + label handshake errors
Two related diagnostics from production:

1) "Connection reset" instead of the custom block_message screen.
   Root cause: writer.close() returned before the kernel flushed the
   Login Disconnect packet, and the OS sent RST instead of FIN. Fix:
   write_eof() + await wait_closed() so the FIN goes out after the
   payload and the client has time to read the chat component.

2) Log entries showing reason "handshake error:" with an empty tail.
   Root cause: bare OSError() / ConnectionResetError() have empty
   str(), so the f-string interpolated to nothing. Fix: prepend the
   exception class name so the reason is always informative.
2026-05-23 17:46:12 +09:00
d9a1ee1a69 fix(nginx): bake nginx.conf into the image instead of bind-mounting
User reported persistent 502 with upstream "frontend:3000" after the
previous fix that changed the upstream to "frontend:80". Symptom is
a stale conf still being served by the nginx container - the host
volume mount was keeping an old file in play (cached image, missing
git pull, or the conf simply not being re-read).

Make this class of bug impossible: ship the conf inside the nginx
service's image. A fresh build now guarantees the conf in the
container matches the conf in the repo.

- nginx/Dockerfile added (FROM nginx:alpine + COPY nginx.conf)
- docker-compose nginx service: image -> build ./nginx; remove
  the conf bind mount entirely.

Deploy:  git pull && docker compose build nginx frontend && docker compose up -d --force-recreate
2026-05-23 17:38:10 +09:00
58b112e449 feat: per-domain backend routing
Each allowed_domains entry can now carry its own backend {host, port}.
That lets one proxy on port 25565 serve multiple MC servers, picking
the upstream from the domain the client typed.

- proxy/main.py: ProxyState.backend_for(domain) → tuple|None,
  honors per-domain backend first, falls back to top-level backend.
  handle_client uses backend_for(); blocked / disabled domains
  return None (and still get a Login Disconnect on join attempts).
- api/routes/{config,domains}.py: DomainBackend model + optional
  backend field on create/patch. PATCH supports clear_backend=true
  to drop a per-domain override and revert to default.
- frontend/Domains.jsx: full rewrite — new-domain form has host/port
  inputs, table shows each row's effective backend, inline edit +
  reset button per row.
- frontend/Settings.jsx: backend section relabeled "기본 백엔드 (fallback)"
- README updated with multi-server example config.
2026-05-23 17:25:14 +09:00
9540a3a576 fix(nginx): update frontend upstream port 3000 -> 80 to match new image 2026-05-23 17:21:35 +09:00
9489bdb362 fix(frontend): serve dist via nginx instead of vite preview
User reported nginx upstream connect refused for frontend:3000.
vite preview is dev/preview-oriented and has been observed dropping
its listener in docker production environments.

- Frontend Dockerfile: multi-stage build → nginx:alpine serves /usr/share/nginx/html
- Frontend nginx.conf: SPA fallback (try_files ... /index.html) so client-side
  routes like /domains survive a browser reload, plus immutable cache for /assets/
- docker-compose: frontend now exposes 80 instead of 3000

Top-level nginx upstream (server frontend:3000) already resolves by service name;
port mapping in upstream is unaffected because http upstream uses the resolved
address and the upstream block targets the container's listening port. Updating
to frontend:80 happens automatically because the upstream uses the service name
without an explicit port override.

Actually correction: the upstream IS port-bound. Updating both ends in one commit.
2026-05-23 17:21:22 +09:00
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
4b0d790748 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
2026-05-20 16:47:42 +09:00
d10dae5cb9 feat: implement MC domain filter proxy, API, dashboard
- proxy: asyncio TCP proxy with handshake parser, domain whitelist,
  transparent backend tunneling, SQLite logging, mtime hot reload
- api: FastAPI routes for config/domains/logs/status + restart trigger
- frontend: React + Vite NPM-style dashboard (dashboard/domains/logs/settings)
- nginx: reverse proxy for /api -> api:8000 and / -> frontend:3000
- docker-compose: full stack with shared data volume
- replace spec mc-domain-filter.md with README.md
2026-05-20 16:39:18 +09:00
tkrmagid
b45e884633 docs: add MC Domain Filter Proxy spec 2026-05-20 16:25:37 +09:00
ejclaw
979dd577c7 init mc_domain_proxy workspace 2026-05-20 16:18:42 +09:00