f823c05aa3
Standalone Go service that routes SVC client traffic to per-server
backend voice endpoints, configured via pg LISTEN/NOTIFY (same channel
mc-router subscribes to). Each pg `servers` row with both
`voice_address` and `voice_proxy_port` set spawns a Valve: a public
UDP listener that maintains per-client ephemeral bridges to the
backend's SVC port.
Pieces:
cmd/svc-proxy/main.go entry; wires config, log fan-out,
bridge.Manager, pgsync, httpsrv
internal/config/ DATABASE_URL + BIND_HOST +
BRIDGE_IDLE_TTL (default 1m) +
HTTP_ADDR (default :8081)
internal/pgsync/ LISTEN automc_routes_changed; diff
desired/actual routes; emit Apply()
internal/bridge/ Valve per public port; per-client
bridge with atomic up/down byte counters;
idle eviction every 15s against TTL
internal/httpsrv/ operator UI — embedded single-page HTML
with active-connections table polled
every 1s + SSE log stream
(last 500 lines backlog on connect)
Reverse-proxied behind server-manager at /infra/svc-proxy/* — bind
internal-only addresses for production; auth is the dashboard's
Basic gate.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
49 lines
896 B
Go
49 lines
896 B
Go
package config
|
|
|
|
import (
|
|
"fmt"
|
|
"os"
|
|
"time"
|
|
)
|
|
|
|
type Config struct {
|
|
DatabaseURL string
|
|
BindHost string
|
|
BridgeIdleTTL time.Duration
|
|
HTTPAddr string
|
|
LogLevel string
|
|
}
|
|
|
|
func Load() (Config, error) {
|
|
cfg := Config{
|
|
DatabaseURL: os.Getenv("DATABASE_URL"),
|
|
BindHost: envOr("BIND_HOST", "0.0.0.0"),
|
|
BridgeIdleTTL: envDur("BRIDGE_IDLE_TTL", 1*time.Minute),
|
|
HTTPAddr: envOr("HTTP_ADDR", ":8081"),
|
|
LogLevel: envOr("LOG_LEVEL", "info"),
|
|
}
|
|
if cfg.DatabaseURL == "" {
|
|
return cfg, fmt.Errorf("DATABASE_URL required")
|
|
}
|
|
return cfg, nil
|
|
}
|
|
|
|
func envOr(key, fallback string) string {
|
|
if v := os.Getenv(key); v != "" {
|
|
return v
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func envDur(key string, fallback time.Duration) time.Duration {
|
|
v := os.Getenv(key)
|
|
if v == "" {
|
|
return fallback
|
|
}
|
|
d, err := time.ParseDuration(v)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return d
|
|
}
|