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>
98 lines
2.7 KiB
Go
98 lines
2.7 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
// Package pairing handles the handshake between LetheanGateway and lthn.io.
|
|
//
|
|
// client := pairing.NewClient("https://lthn.io", "api-key", "charon", "eu-west", "10.69.69.165:51820")
|
|
// client.Pair()
|
|
// client.Heartbeat(stats)
|
|
package pairing
|
|
|
|
import (
|
|
"encoding/json"
|
|
"net/http"
|
|
|
|
core "dappco.re/go/core"
|
|
)
|
|
|
|
// Client manages the pairing lifecycle with lthn.io.
|
|
type Client struct {
|
|
apiURL string
|
|
apiKey string
|
|
name string
|
|
region string
|
|
wireguardEndpoint string
|
|
}
|
|
|
|
// NewClient creates a pairing client for the given gateway.
|
|
//
|
|
// client := pairing.NewClient("https://lthn.io", "key", "charon", "eu-west", "10.0.0.1:51820")
|
|
func NewClient(apiURL, apiKey, name, region, wireguardEndpoint string) *Client {
|
|
return &Client{
|
|
apiURL: apiURL,
|
|
apiKey: apiKey,
|
|
name: name,
|
|
region: region,
|
|
wireguardEndpoint: wireguardEndpoint,
|
|
}
|
|
}
|
|
|
|
// Pair registers this gateway with lthn.io.
|
|
//
|
|
// err := client.Pair()
|
|
func (c *Client) Pair() error {
|
|
body := map[string]interface{}{
|
|
"name": c.name,
|
|
"capabilities": []string{"vpn", "dns", "proxy"},
|
|
"region": c.region,
|
|
"bandwidth_mbps": 100,
|
|
"max_connections": 50,
|
|
"wireguard_endpoint": c.wireguardEndpoint,
|
|
}
|
|
|
|
data, _ := json.Marshal(body)
|
|
request, err := http.NewRequest("POST", c.apiURL+"/v1/gateway/pair", core.NewReader(string(data)))
|
|
if err != nil {
|
|
return core.E("pairing.Pair", "failed to create request", err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
if c.apiKey != "" {
|
|
request.Header.Set("Authorization", "Bearer "+c.apiKey)
|
|
}
|
|
|
|
response, err := http.DefaultClient.Do(request)
|
|
if err != nil {
|
|
return core.E("pairing.Pair", "request failed", err)
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
if response.StatusCode != 200 {
|
|
return core.E("pairing.Pair", core.Sprintf("status %d", response.StatusCode), nil)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// Heartbeat sends alive status + stats to lthn.io.
|
|
//
|
|
// err := client.Heartbeat(map[string]interface{}{"connections": 5, "load": 23})
|
|
func (c *Client) Heartbeat(stats map[string]interface{}) error {
|
|
stats["name"] = c.name
|
|
data, _ := json.Marshal(stats)
|
|
|
|
request, err := http.NewRequest("POST", c.apiURL+"/v1/gateway/heartbeat", core.NewReader(string(data)))
|
|
if err != nil {
|
|
return core.E("pairing.Heartbeat", "failed to create request", err)
|
|
}
|
|
request.Header.Set("Content-Type", "application/json")
|
|
if c.apiKey != "" {
|
|
request.Header.Set("Authorization", "Bearer "+c.apiKey)
|
|
}
|
|
|
|
response, err := http.DefaultClient.Do(request)
|
|
if err != nil {
|
|
return core.E("pairing.Heartbeat", "request failed", err)
|
|
}
|
|
defer response.Body.Close()
|
|
|
|
return nil
|
|
}
|