Fix ensure only nameservers nodes added on schema for caddy load balancing

This commit is contained in:
anonpenguin23 2026-02-02 11:17:54 +02:00
parent e95ecfb12a
commit 79a489d650
4 changed files with 79 additions and 9 deletions

View File

@ -19,6 +19,7 @@ type Credentials struct {
IssuedAt time.Time `json:"issued_at"` IssuedAt time.Time `json:"issued_at"`
LastUsedAt time.Time `json:"last_used_at,omitempty"` LastUsedAt time.Time `json:"last_used_at,omitempty"`
Plan string `json:"plan,omitempty"` Plan string `json:"plan,omitempty"`
NamespaceURL string `json:"namespace_url,omitempty"`
} }
// CredentialStore manages credentials for multiple gateways // CredentialStore manages credentials for multiple gateways

View File

@ -72,6 +72,12 @@ func PerformSimpleAuthentication(gatewayURL, wallet, namespace string) (*Credent
return nil, fmt.Errorf("failed to request API key: %w", err) return nil, fmt.Errorf("failed to request API key: %w", err)
} }
// Build namespace gateway URL from the gateway URL
namespaceURL := ""
if domain := extractDomainFromURL(gatewayURL); domain != "" {
namespaceURL = fmt.Sprintf("https://ns-%s.%s", namespace, domain)
}
// Create credentials // Create credentials
creds := &Credentials{ creds := &Credentials{
APIKey: apiKey, APIKey: apiKey,
@ -79,6 +85,7 @@ func PerformSimpleAuthentication(gatewayURL, wallet, namespace string) (*Credent
UserID: wallet, UserID: wallet,
Wallet: wallet, Wallet: wallet,
IssuedAt: time.Now(), IssuedAt: time.Now(),
NamespaceURL: namespaceURL,
} }
fmt.Printf("\n🎉 Authentication successful!\n") fmt.Printf("\n🎉 Authentication successful!\n")

View File

@ -156,6 +156,9 @@ func handleAuthLogin(wallet, namespace string) {
fmt.Printf("🎯 Wallet: %s\n", creds.Wallet) fmt.Printf("🎯 Wallet: %s\n", creds.Wallet)
fmt.Printf("🏢 Namespace: %s\n", creds.Namespace) fmt.Printf("🏢 Namespace: %s\n", creds.Namespace)
fmt.Printf("🔑 API Key: %s\n", creds.APIKey) fmt.Printf("🔑 API Key: %s\n", creds.APIKey)
if creds.NamespaceURL != "" {
fmt.Printf("🌐 Namespace URL: %s\n", creds.NamespaceURL)
}
} }
func handleAuthLogout() { func handleAuthLogout() {
@ -184,6 +187,9 @@ func handleAuthWhoami() {
fmt.Println("✅ Authenticated") fmt.Println("✅ Authenticated")
fmt.Printf(" Wallet: %s\n", creds.Wallet) fmt.Printf(" Wallet: %s\n", creds.Wallet)
fmt.Printf(" Namespace: %s\n", creds.Namespace) fmt.Printf(" Namespace: %s\n", creds.Namespace)
if creds.NamespaceURL != "" {
fmt.Printf(" NS Gateway: %s\n", creds.NamespaceURL)
}
fmt.Printf(" Issued At: %s\n", creds.IssuedAt.Format("2006-01-02 15:04:05")) fmt.Printf(" Issued At: %s\n", creds.IssuedAt.Format("2006-01-02 15:04:05"))
if !creds.ExpiresAt.IsZero() { if !creds.ExpiresAt.IsZero() {
fmt.Printf(" Expires At: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05")) fmt.Printf(" Expires At: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05"))
@ -231,6 +237,9 @@ func handleAuthStatus() {
fmt.Println(" Status: ✅ Authenticated") fmt.Println(" Status: ✅ Authenticated")
fmt.Printf(" Wallet: %s\n", creds.Wallet) fmt.Printf(" Wallet: %s\n", creds.Wallet)
fmt.Printf(" Namespace: %s\n", creds.Namespace) fmt.Printf(" Namespace: %s\n", creds.Namespace)
if creds.NamespaceURL != "" {
fmt.Printf(" NS Gateway: %s\n", creds.NamespaceURL)
}
if !creds.ExpiresAt.IsZero() { if !creds.ExpiresAt.IsZero() {
fmt.Printf(" Expires: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05")) fmt.Printf(" Expires: %s\n", creds.ExpiresAt.Format("2006-01-02 15:04:05"))
} }

View File

@ -4,6 +4,9 @@ import (
"context" "context"
"fmt" "fmt"
"net" "net"
"os"
"path/filepath"
"strings"
"time" "time"
"github.com/DeBrosOfficial/network/pkg/logging" "github.com/DeBrosOfficial/network/pkg/logging"
@ -143,8 +146,10 @@ func (n *Node) ensureBaseDNSRecords(ctx context.Context) error {
value string value string
} }
// Base domain records (e.g., dbrs.space, *.dbrs.space) — for round-robin across all nodes // Base domain records (e.g., dbrs.space, *.dbrs.space) — only for nameserver nodes.
if baseDomain != "" { // Only nameserver nodes run Caddy (HTTPS), so only they should appear in base domain
// round-robin. Non-nameserver nodes would cause TLS failures for clients.
if baseDomain != "" && n.isNameserverNode(ctx) {
records = append(records, records = append(records,
struct{ fqdn, value string }{baseDomain + ".", ipAddress}, struct{ fqdn, value string }{baseDomain + ".", ipAddress},
struct{ fqdn, value string }{"*." + baseDomain + ".", ipAddress}, struct{ fqdn, value string }{"*." + baseDomain + ".", ipAddress},
@ -176,8 +181,9 @@ func (n *Node) ensureBaseDNSRecords(ctx context.Context) error {
n.ensureSOAAndNSRecords(ctx, baseDomain) n.ensureSOAAndNSRecords(ctx, baseDomain)
} }
// Claim an NS slot for the base domain (ns1/ns2/ns3) // Claim an NS slot for the base domain (ns1/ns2/ns3) — only if this node
if baseDomain != "" { // was installed with --nameserver (i.e. runs Caddy + CoreDNS).
if baseDomain != "" && n.isNameserverPreference() {
n.claimNameserverSlot(ctx, baseDomain, ipAddress) n.claimNameserverSlot(ctx, baseDomain, ipAddress)
} }
@ -369,6 +375,37 @@ func (n *Node) cleanupStaleNodeRecords(ctx context.Context) {
} }
} }
// isNameserverPreference checks if this node was installed with --nameserver flag
// by reading the preferences.yaml file. Only nameserver nodes should claim NS slots.
func (n *Node) isNameserverPreference() bool {
oramaDir := filepath.Join(os.ExpandEnv(n.config.Node.DataDir), "..")
prefsPath := filepath.Join(oramaDir, "preferences.yaml")
data, err := os.ReadFile(prefsPath)
if err != nil {
return false
}
// Simple check: look for "nameserver: true" in the YAML
return strings.Contains(string(data), "nameserver: true")
}
// isNameserverNode checks if this node has claimed a nameserver slot (ns1/ns2/ns3).
// Only nameserver nodes run Caddy for HTTPS, so only they should be in base domain DNS.
func (n *Node) isNameserverNode(ctx context.Context) bool {
if n.rqliteAdapter == nil {
return false
}
nodeID := n.GetPeerID()
if nodeID == "" {
return false
}
db := n.rqliteAdapter.GetSQLDB()
var count int
err := db.QueryRowContext(ctx,
`SELECT COUNT(*) FROM dns_nameservers WHERE node_id = ?`, nodeID,
).Scan(&count)
return err == nil && count > 0
}
// getWireGuardIP returns the IPv4 address assigned to the wg0 interface, if any // getWireGuardIP returns the IPv4 address assigned to the wg0 interface, if any
func (n *Node) getWireGuardIP() (string, error) { func (n *Node) getWireGuardIP() (string, error) {
iface, err := net.InterfaceByName("wg0") iface, err := net.InterfaceByName("wg0")
@ -411,5 +448,21 @@ func (n *Node) getNodeIPAddress() (string, error) {
defer conn.Close() defer conn.Close()
localAddr := conn.LocalAddr().(*net.UDPAddr) localAddr := conn.LocalAddr().(*net.UDPAddr)
if localAddr.IP.IsPrivate() || localAddr.IP.IsLoopback() {
// UDP dial returned a private/loopback IP (e.g. WireGuard 10.0.0.x).
// Fall back to scanning interfaces for a public IPv4.
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", fmt.Errorf("private IP detected (%s) and failed to list interfaces: %w", localAddr.IP, err)
}
for _, addr := range addrs {
if ipnet, ok := addr.(*net.IPNet); ok && !ipnet.IP.IsLoopback() && !ipnet.IP.IsPrivate() {
if ipnet.IP.To4() != nil {
return ipnet.IP.String(), nil
}
}
}
return "", fmt.Errorf("private IP detected (%s) and no public IPv4 found on interfaces", localAddr.IP)
}
return localAddr.IP.String(), nil return localAddr.IP.String(), nil
} }