6.3 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
mc-router is a Minecraft Java Edition reverse proxy that routes client connections to backend servers based on the requested server address (hostname). It multiplexes multiple Minecraft servers onto a single public IP/port and supports auto-discovery via Kubernetes and Docker, auto-scaling (scale to zero/one), webhooks, rate limiting, IP filtering, PROXY protocol, and ngrok tunneling.
Build & Test Commands
go build ./cmd/mc-router/ # Build the binary
make test # Run all tests (go test ./...)
go test ./server/... # Run only server package tests
go test ./mcproto/... # Run only protocol package tests
go test -run TestRouteLookup ./server/ # Run a single test
docker build -t mc-router . # Build Docker image
Go version: 1.26.2. Testing uses testify (assert/require). Tests are table-driven with subtests. Mock pattern: embed mock.Mock and call m.MethodCalled() (see k8s_test.go). Protocol packet tests use hex fixture files in testdata/ (e.g., handshake-status.hex). Test setup for route tests calls NewRoutes() and restores the global Routes singleton with defer.
Architecture
Request Flow
- Connector (
server/connector.go) accepts TCP connections on port 25565 - mcproto package (
mcproto/) reads the Minecraft handshake packet to extract the target server address - Routes (
server/routes.go) looks up the backend address for that hostname - If auto-scale is enabled and the backend is sleeping, a waker function starts it (Kubernetes StatefulSet replica 0→1 or Docker container start/unpause)
- Traffic is proxied bidirectionally between client and backend
- On disconnect, metrics are updated, webhooks fired, and the DownScaler (
server/down_scaler.go) may schedule scale-down after idle timeout
Key Packages
cmd/mc-router/— Entry point. Parses CLI flags viago-flagsfiller, sets up signal handling (SIGINT for shutdown, SIGHUP for config reload).server/— Core router logic:server.go— Initializes all subsystems (metrics, routes, connector, API, service discovery)connector.go— Connection handler: accepts clients, reads handshake, proxies traffic, manages rate limiting and client filteringroutes.go— In-memory route table mapping server addresses to backends; supports default route fallbackroutes_config_loader.go— Loads/watches JSON routes config file (with fsnotify)k8s.go— Kubernetes service discovery via annotationmc-router.itzg.me/externalServerNamedocker.go— Docker container discovery via labelmc-router.host; event-driven via Docker Events API (client.Events). Each event handled incrementally byapplyEvent→containersForID(singleContainerInspect) →applyContainerRoutesLocked(touches only that container's routes). FullmonitorContainersre-list runs at startup and on event-stream reconnect (exponential backoff)docker_swarm.go— Docker Swarm service discovery via labelmc-router.host; event-driven, but each service event triggers a fullreconcileServicesre-list (services churn rarely, swarm has no autoscaling)down_scaler.go— Auto-scale down after idle periodapi_server.go— REST API (GET/POST /routes,POST /defaultRoute,DELETE /routes/{serverAddress})metrics.go— Pluggable metrics backends (Prometheus, InfluxDB, expvar, discard)webhook_notifier.go— POST notifications on connect/disconnect eventsclient_filter.go/allow_deny_list.go— IP and player allow/deny listsconfigs.go— All configuration structs with CLI flag annotations
mcproto/— Minecraft Java protocol implementation:types.go— Frame, Packet, Handshake, LoginStart typesread.go/write.go— Network I/O for Minecraft protocol framesdecode.go— Packet decoding (handshake, login, status)
Configuration
CLI flags are the primary config mechanism, with environment variable support via go-flagsfiller (flag --some-flag maps to env SOME_FLAG). Routes can also be loaded from a JSON config file (--routes-config). The Config struct in server/configs.go defines all options.
Service Discovery
Routes are populated from three sources that can be combined:
- Static
--mappingflags or JSON config file - Kubernetes: watches Services with
mc-router.itzg.me/externalServerNameannotation - Docker/Swarm: watches containers/services via the Docker Events API, filtered to lifecycle events (label
mc-router.host)
Key Dependencies
sirupsen/logrus— Loggingk8s.io/client-go— Kubernetes clientgithub.com/docker/docker— Docker clientgithub.com/gorilla/mux— HTTP routing (API server)github.com/prometheus/client_golang— Prometheus metricsgolang.ngrok.com/ngrok— ngrok tunnel integrationgithub.com/stretchr/testify— Test assertions
Concurrency Model
routes.go: global singletonvar Routes = NewRoutes().sync.RWMutexprotectsmappingsanddefaultRoute—RLockfor all reads,Lockfor mutations.connector.go:ActiveConnectionsmap guarded bysync.RWMutex;totalActiveConnectionscounter usesatomic.AddInt32. Shutdown drain usessync.CondinWaitForConnections().- Bidirectional proxy: two goroutines per connection (client→backend, backend→client) communicate via a buffered
chan error(size 2) — first error triggers mutual close. - All goroutines respect context cancellation via
select { case <-ctx.Done() }.
Error Handling
- Wrap with context:
fmt.Errorf("message: %w", err) - Check specific sentinels:
errors.Is(err, io.EOF) - Log with fields:
logrus.WithError(err).WithField("key", val).Error("msg")
Protocol Notes
The mcproto package handles Minecraft Java protocol quirks: Forge mod identifiers appended to server addresses (separated by \x00), DNS root zone trailing dots, legacy server list ping format, and VarInt encoding. Server address matching in routes also strips TCP Shield patterns (///...) and lowercases before lookup.
Auto-scale MOTD fallback: waking servers display LoadingMOTD; sleeping servers display AsleepMOTD. Per-route MOTDs take precedence over global ones.