Replaces Python+HAProxy gateway stack with a single Go binary: - pkg/pairing: handshake with lthn.io (pair + heartbeat) - pkg/meter: per-session bandwidth metering - cmd/gateway: main binary with admin API on :8880 - Uses core/api for HTTP, pairs via /v1/gateway/* endpoints Eliminates: Python lvpn, HAProxy config templating, legacy session management. Keeps: WireGuard (managed via go-process when ready). Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
103 lines
2.7 KiB
Go
103 lines
2.7 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
// LetheanGateway is the companion binary for .lthn gateway node operators.
|
|
// It replaces the legacy Python+HAProxy stack with a single Go service that:
|
|
//
|
|
// - Pairs with lthn.io using the node's chain alias identity
|
|
// - Manages WireGuard tunnels for VPN customers
|
|
// - Routes proxy traffic for residential/mobile/SEO services
|
|
// - Meters bandwidth usage per session
|
|
// - Reports heartbeat + stats to lthn.io for dispatch
|
|
//
|
|
// Usage:
|
|
//
|
|
// lethean-gateway --name charon --api https://api.lthn.io --region eu-west
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"net/http"
|
|
"time"
|
|
|
|
core "dappco.re/go/core"
|
|
"lethean.io/gateway/pkg/meter"
|
|
"lethean.io/gateway/pkg/pairing"
|
|
)
|
|
|
|
var (
|
|
apiURL = envDefault("LTHN_API", "https://lthn.io")
|
|
apiKey = envDefault("LTHN_API_KEY", "")
|
|
name = envDefault("LTHN_GATEWAY_NAME", "")
|
|
region = envDefault("LTHN_REGION", "unknown")
|
|
wgPort = envDefault("WG_PORT", "51820")
|
|
wgHost = envDefault("WG_HOST", "0.0.0.0")
|
|
)
|
|
|
|
func envDefault(key, fallback string) string {
|
|
if value := core.Env(key); value != "" {
|
|
return value
|
|
}
|
|
return fallback
|
|
}
|
|
|
|
func main() {
|
|
core.Println("Lethean Gateway")
|
|
core.Println(core.Sprintf(" Name: %s.lthn", name))
|
|
core.Println(core.Sprintf(" API: %s", apiURL))
|
|
core.Println(core.Sprintf(" Region: %s", region))
|
|
core.Println("")
|
|
|
|
if name == "" {
|
|
core.Println("ERROR: LTHN_GATEWAY_NAME required (your .lthn alias)")
|
|
return
|
|
}
|
|
|
|
// Bandwidth meter
|
|
bandwidth := meter.New()
|
|
|
|
// Pair with lthn.io
|
|
client := pairing.NewClient(apiURL, apiKey, name, region, wgHost+":"+wgPort)
|
|
err := client.Pair()
|
|
if err != nil {
|
|
core.Println(core.Sprintf("Pairing failed: %v", err))
|
|
core.Println("Retrying in 30s...")
|
|
} else {
|
|
core.Println("Paired with lthn.io")
|
|
}
|
|
|
|
// Heartbeat loop
|
|
go func() {
|
|
for {
|
|
time.Sleep(60 * time.Second)
|
|
stats := bandwidth.Snapshot()
|
|
heartbeatErr := client.Heartbeat(stats)
|
|
if heartbeatErr != nil {
|
|
core.Println(core.Sprintf("Heartbeat failed: %v — will retry", heartbeatErr))
|
|
}
|
|
}
|
|
}()
|
|
|
|
// Local admin API
|
|
http.HandleFunc("/health", func(writer http.ResponseWriter, request *http.Request) {
|
|
json.NewEncoder(writer).Encode(map[string]interface{}{
|
|
"name": name + ".lthn",
|
|
"region": region,
|
|
"status": "online",
|
|
"sessions": bandwidth.ActiveSessions(),
|
|
"bytes_total": bandwidth.TotalBytes(),
|
|
})
|
|
})
|
|
|
|
http.HandleFunc("/stats", func(writer http.ResponseWriter, request *http.Request) {
|
|
json.NewEncoder(writer).Encode(bandwidth.Snapshot())
|
|
})
|
|
|
|
core.Println("Admin API listening on :8880")
|
|
http.ListenAndServe(":8880", nil)
|
|
}
|
|
|
|
// Placeholder — will be replaced by core/api RouteGroup when ready
|
|
func init() {
|
|
_ = context.Background
|
|
}
|