{"id":50932727,"url":"https://github.com/skevo18/minecraft_proxy","last_synced_at":"2026-06-17T06:01:40.519Z","repository":{"id":361395750,"uuid":"1254305053","full_name":"SKevo18/minecraft_proxy","owner":"SKevo18","description":"A lightweight, dependency-free Java wrapper that lets one Minecraft server accept players from both direct connections (your own domain/IP) and some other proxy (Minehut etc.) at the same time. It is a drop-in replacement for your server jar: it launches the real server on a backend port and fronts it with a dual proxy","archived":false,"fork":false,"pushed_at":"2026-05-30T12:03:47.000Z","size":24,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T14:04:58.817Z","etag":null,"topics":["dual-proxy","minecraft","minehut","mitm-proxy","proxy"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/SKevo18.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T11:58:54.000Z","updated_at":"2026-05-30T13:13:09.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/SKevo18/minecraft_proxy","commit_stats":null,"previous_names":["skevo18/minecraft_proxy"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/SKevo18/minecraft_proxy","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SKevo18%2Fminecraft_proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SKevo18%2Fminecraft_proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SKevo18%2Fminecraft_proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SKevo18%2Fminecraft_proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/SKevo18","download_url":"https://codeload.github.com/SKevo18/minecraft_proxy/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/SKevo18%2Fminecraft_proxy/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34435981,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-17T02:00:05.408Z","response_time":127,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["dual-proxy","minecraft","minehut","mitm-proxy","proxy"],"created_at":"2026-06-17T06:01:39.666Z","updated_at":"2026-06-17T06:01:40.514Z","avatar_url":"https://github.com/SKevo18.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Minehut Dual-Proxy Wrapper\n\nA lightweight, dependency-free Java wrapper that lets one Minecraft server accept\nplayers from **both** direct connections (your own domain/IP) and the Minehut\nproxy at the same time. It is a drop-in replacement for your server jar: instead\nof running the server directly, it launches the real server on a backend port and\nfronts it with a dual proxy.\n\nIt is a minimal Java port of [mc-dual-proxy](SKevo18/mc-dual-proxy), built on plain\n`java.base`-only APIs (blocking sockets on virtual threads, `HttpURLConnection`)\nso it stays small, fast, and runs even on trimmed JREs that omit extra modules.\n\n\u003e This is the proxy running in production on the **steedsgate.quest** MC server.\n\n## The problem\n\nConnecting an external server to Minehut forces two incompatible choices:\n\n1. **PROXY protocol** — Minehut prepends HAProxy PROXY protocol headers; direct\n   players don't. The backend can't have it both on and off.\n2. **Session server** — Minehut authenticates `hasJoined` against its own MITM\n   session server; direct players use Mojang's. The backend can only point at one.\n\n## The solution\n\nThe wrapper runs three things in one process:\n\n```plaintext\n  players ───────────────→ [tcp proxy :25565] ──→ [real server :25566]\n  (direct + minehut)            normalizes              proxy-protocol: true\n                                PROXY protocol          session.host → :8652\n                                                              │\n  real server hasJoined ──→ [multiauth :8652] ──→ Mojang + Minehut (first 200 wins)\n```\n\n1. **TCP proxy** (`listen`): direct connections get a generated PROXY protocol v2\n   header; Minehut connections have theirs forwarded verbatim. The backend always\n   sees a header, so it can keep `proxy-protocol: true` for everyone.\n2. **Backend server** (`backend`): launched as a child process with the Mojang API\n   host overrides injected, and `session.host` pointed at the local multiauth port.\n3. **Multiauth HTTP server** (`auth_listen`): fans each `hasJoined` out to Mojang\n   and Minehut concurrently and returns the first HTTP 200. The `serverId` hash is\n   cryptographically unique per connection, so exactly one upstream ever matches.\n\n## Setup\n\n1. Build: `./build.sh` → produces `proxy.jar`.\n2. Put `proxy.jar`, your real server jar, and `startup.ini` in the server folder.\n3. Rename your server jar to `real-server.jar` (or set `[server] jar` in `startup.ini`).\n4. Configure the backend for the proxy (see below).\n5. Run it:\n\n   ```bash\n   java -jar proxy.jar --nogui\n   ```\n\n   The wrapper caps its own heap at `[proxy] heap` (128M) automatically, so the\n   memory in `startup.ini` goes to the backend — no `-Xmx` needed on the launch\n   command (pass one anyway and it's respected as-is).\n\nAny arguments after the jar are passed through to the real server. `--port` is\ninjected automatically (from `[proxy] backend`) unless you pass your own.\n\n## Configuration (`startup.ini`)\n\n```ini\n[proxy]\nheap = 128M                     ; cap the wrapper's OWN heap (re-exec); \"off\" disables\nlisten = 0.0.0.0:25565          ; public address players connect to\nbackend = 127.0.0.1:25566       ; the real server this wrapper launches\nauth_listen = 127.0.0.1:8652    ; local multiauth server (localhost only)\nsession_servers = https://sessionserver.mojang.com,https://api.minehut.com/mitm/proxy\n\n[api]\nenabled = true                  ; serve GET /source (Minehut vs Mojang) on the auth server\n\n[web]\nenabled = true                  ; landing page + live status\nname = SteedsGate\naddress = steedsgate.quest       ; shown on the page / Copy IP button\nlisten = 0.0.0.0:8080\n\n[server]\njar = real-server.jar\nmin_memory = 8G                 ; backend heap (applied as -Xms/-Xmx)\nmax_memory = 8G\n\n[restart]\nautorestart = true              ; restart the backend when it crashes\nrestart_on_stop = false         ; /stop stops the wrapper; true = always-up (only a signal stops it)\nrestart_delay = 5               ; seconds to wait before each restart\nrestart_max = 5                 ; max restarts per window, 0 = unlimited\nrestart_window = 60             ; window the limit applies to\n\n[startup_flags]\n; backend JVM flags, one per line. The Mojang API host overrides go here —\n; session.host must match auth_listen so the backend authenticates via multiauth.\n-Dminecraft.api.auth.host=https://authserver.mojang.com/\n-Dminecraft.api.account.host=https://api.mojang.com/\n-Dminecraft.api.services.host=https://api.minecraftservices.com/\n-Dminecraft.api.profiles.host=https://api.mojang.com/\n-Dminecraft.api.session.host=http://127.0.0.1:8652\n;-XX:+UseG1GC                   ; plus any GC/perf flags\n```\n\nEvery value is optional; omitted keys fall back to the defaults above. The\nwrapper's own heap is capped at `heap` (128M) via a one-time self re-exec, unless\nyou pass an explicit `-Xmx` on the launch command (which is respected as-is).\n\n### Backend supervision (`[restart]`)\n\nWhen `autorestart` is on (the default), the wrapper relaunches the backend after\nit **crashes** (non-zero exit), waiting `restart_delay` seconds each time. A clean\n`/stop` (exit 0) instead exits the wrapper, so the panel's Stop button — which\nusually just sends `/stop` to the console — actually stops the server; the panel's\nRestart (stop→start) works too. Set `restart_on_stop = true` to relaunch even on a\nclean stop (always-up; then only a kill signal stops it).\n\nIf the backend restarts more than `restart_max` times within `restart_window`\nseconds the wrapper gives up and exits — this stops a crash-looping server from\nhammering the box (`restart_max = 0` for unlimited). A signal to the wrapper\nitself (Ctrl-C / SIGTERM) always shuts everything down without a restart.\n\n## Backend configuration\n\nThese are server-side settings the wrapper can't set via flags:\n\n`config/paper-global.yml`:\n\n```yaml\nproxies:\n  proxy-protocol: true\n```\n\n`server.properties`:\n\n```properties\nenforce-secure-profile=false\n```\n\nThe Mojang API host overrides live in `[startup_flags]` (Paper ignores them unless\n**all** of `auth.host`, `account.host`, `services.host`, `profiles.host` are set).\nKeep those four pointed at Mojang and point `session.host` at the local multiauth\nserver — i.e. it must match `auth_listen` (default `http://127.0.0.1:8652`).\n\n## Minehut panel\n\n1. Point your external server at your **public IP** on the `listen` port (25565).\n2. DNS record type: `Port`. TCP Shield: `Not Configured`.\n3. Proxy type: `Other` for standalone Paper.\n\n## Firewall\n\nOnly the `listen` port (25565) must be open externally. The `backend` (25566) and\n`auth_listen` (8652) ports only need to be reachable from localhost.\n\n## Restart screen\n\nWhile the backend is down (restarting or still booting), the proxy:\n\n- answers **server-list pings** with a custom MOTD (`starting_motd`, with `\u0026`\n  colors and `\\n` for a second line — hand-pad it with spaces to taste), and\n- **holds joining players** at \"Connecting…\" — it keeps retrying the backend for\n  `connect_wait` seconds and splices them straight through once it's up, instead\n  of kicking them.\n\nThe protocol is only parsed on this slow path; a healthy backend is spliced raw.\n\n## Landing page + status (`[web]`)\n\nWith `[web] enabled = true`, the proxy serves a small **landing page** for the\nserver (hero, description, screenshot gallery) with **live status** woven in —\nplayers online, uptime, system memory, and service health.\n\n- The template is `web/index.html` with simple `{{placeholder}}` substitution\n  (`name`, `address`, `status`, `players_online/max`, `version`, `uptime`,\n  `mem_used/total/percent`, `*_status`). Edit it freely.\n- Images and other static files live in `web/assets/` and are served from\n  `/assets/...`. Swap in your own.\n- `GET /status.json` returns the live data; the page polls it every 5s.\n- Player counts come from a status ping to the backend; memory from `/proc/meminfo`.\n\nDeploy the whole `web/` folder next to `proxy.jar`. Set `[web] name` and\n`address` for your branding.\n\n## Join source (Minehut vs Mojang)\n\nThe multiauth server knows how each player authenticated (Minehut players hit\nMinehut's session server, direct players hit Mojang's) and records it. With\n`[api] enabled = true` it's exposed at:\n\n```text\nGET http://127.0.0.1:\u003cauth_listen\u003e/source?username=\u003cname\u003e\n→ {\"source\":\"minehut\"}   |   {\"source\":\"mojang\"}   |   {\"source\":null}\n```\n\nA ready-made PlaceholderAPI expansion that consumes this lives in\n[`papi-expansion/`](papi-expansion/) and provides `%proxy_source%`,\n`%proxy_source_badge%` (`[MH]`/``), and `%proxy_source_raw%`.\n\n## Notes on performance\n\nThe dual proxy adds a per-connection byte pump, so very long uptimes on small\nboxes can raise ping. To keep it lean:\n\n- The wrapper caps its own heap (`[proxy] heap`, default 128M) so it can't grow\n  toward the JVM's ~25%-of-RAM default; the backend gets the real memory.\n- `TCP_NODELAY` is set on both sides to avoid Nagle latency.\n- Connections and pipe directions run on virtual threads (low per-connection cost).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskevo18%2Fminecraft_proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fskevo18%2Fminecraft_proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fskevo18%2Fminecraft_proxy/lists"}