Add Spinner, a Minecraft hostname-routing reverse proxy
CI / test (push) Successful in 2m32s

Routes Java Edition connections by the hostname in the handshake, with
wildcard domains, backend failover, PROXY protocol v2, cached server-list
status from health probes, hot-reloaded config, and an admin page and
metrics endpoint. Includes CI, tagged releases and a container image.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
VeillaxandClaude Sonnet 5.5 committed 2026-10-26 19:40:40 -06:00
1 parent e640c5bf02
commit b53af0e196
29 files changed
+2640

No files matched your search

+75
View File
@@ -0,0 +1,75 @@
package proxy
import (
"sort"
"sync"
"sync/atomic"
"time"
)
type serverStats struct {
active atomic.Int64
total atomic.Int64
failures atomic.Int64
bytesIn atomic.Int64 // client -> backend
bytesOut atomic.Int64 // backend -> client
}
type Stats struct {
Started time.Time
active atomic.Int64
total atomic.Int64
rejected atomic.Int64
unknown atomic.Int64
mu sync.Mutex
servers map[string]*serverStats
}
func newStats() *Stats {
return &Stats{Started: time.Now(), servers: map[string]*serverStats{}}
}
func (s *Stats) server(name string) *serverStats {
s.mu.Lock()
defer s.mu.Unlock()
ss := s.servers[name]
if ss == nil {
ss = &serverStats{}
s.servers[name] = ss
}
return ss
}
type ServerSnapshot struct {
Name string `json:"name"`
Active int64 `json:"active"`
Total int64 `json:"total"`
Failures int64 `json:"failures"`
BytesIn int64 `json:"bytes_in"`
BytesOut int64 `json:"bytes_out"`
}
type Snapshot struct {
UptimeSecs int64 `json:"uptime_secs"`
Active int64 `json:"active"`
Total int64 `json:"total"`
Rejected int64 `json:"rejected"`
Unknown int64 `json:"unknown_host"`
Servers []ServerSnapshot `json:"servers"`
}
func (s *Stats) Snapshot() Snapshot {
out := Snapshot{
UptimeSecs: int64(time.Since(s.Started).Seconds()),
Active: s.active.Load(), Total: s.total.Load(),
Rejected: s.rejected.Load(), Unknown: s.unknown.Load(),
}
s.mu.Lock()
for n, ss := range s.servers {
out.Servers = append(out.Servers, ServerSnapshot{n, ss.active.Load(), ss.total.Load(),
ss.failures.Load(), ss.bytesIn.Load(), ss.bytesOut.Load()})
}
s.mu.Unlock()
sort.Slice(out.Servers, func(i, j int) bool { return out.Servers[i].Name < out.Servers[j].Name })
return out
}