mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-10-06 12:09:07 +00:00
Merge branch 'main' of https://git.debros.io/DeBros/network
This commit is contained in:
commit
f62b9c6423
109
cmd/cli/main.go
109
cmd/cli/main.go
@ -10,6 +10,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/libp2p/go-libp2p/core/crypto"
|
||||
"github.com/libp2p/go-libp2p/core/peer"
|
||||
"git.debros.io/DeBros/network/pkg/client"
|
||||
"git.debros.io/DeBros/network/pkg/constants"
|
||||
)
|
||||
@ -56,6 +58,8 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
handleConnect(args[0])
|
||||
case "peer-id":
|
||||
handlePeerID()
|
||||
case "help", "--help", "-h":
|
||||
showHelp()
|
||||
default:
|
||||
@ -351,6 +355,72 @@ func handleConnect(peerAddr string) {
|
||||
fmt.Printf("✅ Connected to peer: %s\n", peerAddr)
|
||||
}
|
||||
|
||||
func handlePeerID() {
|
||||
// Try to get peer ID from running network first
|
||||
client, err := createClient()
|
||||
if err == nil {
|
||||
defer client.Disconnect()
|
||||
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||
defer cancel()
|
||||
|
||||
if status, err := client.Network().GetStatus(ctx); err == nil {
|
||||
if format == "json" {
|
||||
printJSON(map[string]string{"peer_id": status.NodeID})
|
||||
} else {
|
||||
fmt.Printf("🆔 Peer ID: %s\n", status.NodeID)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try to extract from local identity files
|
||||
identityPaths := []string{
|
||||
"/opt/debros/data/node/identity.key",
|
||||
"/opt/debros/data/bootstrap/identity.key",
|
||||
"/opt/debros/keys/node/identity.key",
|
||||
"./data/node/identity.key",
|
||||
"./data/bootstrap/identity.key",
|
||||
}
|
||||
|
||||
for _, path := range identityPaths {
|
||||
if peerID := extractPeerIDFromFile(path); peerID != "" {
|
||||
if format == "json" {
|
||||
printJSON(map[string]string{"peer_id": peerID, "source": "local_identity"})
|
||||
} else {
|
||||
fmt.Printf("🆔 Peer ID: %s\n", peerID)
|
||||
fmt.Printf("📂 Source: %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Check peer.info files as last resort
|
||||
peerInfoPaths := []string{
|
||||
"/opt/debros/data/node/peer.info",
|
||||
"/opt/debros/data/bootstrap/peer.info",
|
||||
"./data/node/peer.info",
|
||||
"./data/bootstrap/peer.info",
|
||||
}
|
||||
|
||||
for _, path := range peerInfoPaths {
|
||||
if data, err := os.ReadFile(path); err == nil {
|
||||
multiaddr := strings.TrimSpace(string(data))
|
||||
if peerID := extractPeerIDFromMultiaddr(multiaddr); peerID != "" {
|
||||
if format == "json" {
|
||||
printJSON(map[string]string{"peer_id": peerID, "source": "peer_info"})
|
||||
} else {
|
||||
fmt.Printf("🆔 Peer ID: %s\n", peerID)
|
||||
fmt.Printf("📂 Source: %s\n", path)
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fmt.Fprintf(os.Stderr, "❌ Could not find peer ID. Make sure the node is running or identity files exist.\n")
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
func createClient() (client.NetworkClient, error) {
|
||||
var bootstrapPeers []string
|
||||
|
||||
@ -445,6 +515,7 @@ func showHelp() {
|
||||
fmt.Printf(" health - Check network health\n")
|
||||
fmt.Printf(" peers - List connected peers\n")
|
||||
fmt.Printf(" status - Show network status\n")
|
||||
fmt.Printf(" peer-id - Show this node's peer ID\n")
|
||||
fmt.Printf(" query <sql> - Execute database query\n")
|
||||
fmt.Printf(" storage get <key> - Get value from storage\n")
|
||||
fmt.Printf(" storage put <key> <value> - Store value in storage\n")
|
||||
@ -461,6 +532,8 @@ func showHelp() {
|
||||
fmt.Printf(" --production - Connect to production bootstrap peers\n\n")
|
||||
fmt.Printf("Examples:\n")
|
||||
fmt.Printf(" network-cli health\n")
|
||||
fmt.Printf(" network-cli peer-id\n")
|
||||
fmt.Printf(" network-cli peer-id --format json\n")
|
||||
fmt.Printf(" network-cli peers --format json\n")
|
||||
fmt.Printf(" network-cli peers --production\n")
|
||||
fmt.Printf(" network-cli storage put user:123 '{\"name\":\"Alice\"}'\n")
|
||||
@ -594,3 +667,39 @@ func formatBytes(bytes int64) string {
|
||||
}
|
||||
return fmt.Sprintf("%.1f %cB", float64(bytes)/float64(div), "KMGTPE"[exp])
|
||||
}
|
||||
|
||||
// extractPeerIDFromFile extracts peer ID from an identity key file
|
||||
func extractPeerIDFromFile(keyFile string) string {
|
||||
// Read the identity key file
|
||||
data, err := os.ReadFile(keyFile)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Unmarshal the private key
|
||||
priv, err := crypto.UnmarshalPrivateKey(data)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
// Get the public key
|
||||
pub := priv.GetPublic()
|
||||
|
||||
// Get the peer ID
|
||||
peerID, err := peer.IDFromPublicKey(pub)
|
||||
if err != nil {
|
||||
return ""
|
||||
}
|
||||
|
||||
return peerID.String()
|
||||
}
|
||||
|
||||
// extractPeerIDFromMultiaddr extracts the peer ID from a multiaddr string
|
||||
func extractPeerIDFromMultiaddr(multiaddr string) string {
|
||||
// Look for /p2p/ followed by the peer ID
|
||||
parts := strings.Split(multiaddr, "/p2p/")
|
||||
if len(parts) >= 2 {
|
||||
return parts[1]
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
168
cmd/node/main.go
168
cmd/node/main.go
@ -6,6 +6,7 @@ import (
|
||||
"fmt"
|
||||
"log"
|
||||
"os"
|
||||
"os/exec"
|
||||
"os/signal"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
@ -47,8 +48,8 @@ func main() {
|
||||
}
|
||||
}
|
||||
|
||||
// All nodes use port 4001 for consistency
|
||||
port := 4001
|
||||
// LibP2P uses port 4000, RQLite uses 4001
|
||||
port := 4000
|
||||
|
||||
// Create logger with appropriate component type
|
||||
var logger *logging.StandardLogger
|
||||
@ -84,28 +85,77 @@ func main() {
|
||||
cfg.Database.RQLiteRaftPort = 4002
|
||||
|
||||
if isBootstrap {
|
||||
// Bootstrap node doesn't join anyone - it starts the cluster
|
||||
cfg.Database.RQLiteJoinAddress = ""
|
||||
logger.Printf("Bootstrap node - starting new RQLite cluster")
|
||||
} else {
|
||||
// Regular nodes join the bootstrap node's RQLite cluster
|
||||
cfg.Database.RQLiteJoinAddress = "http://localhost:4001"
|
||||
// Check if this is the primary bootstrap node (first in list) or secondary
|
||||
bootstrapPeers := constants.GetBootstrapPeers()
|
||||
isSecondaryBootstrap := false
|
||||
if len(bootstrapPeers) > 1 {
|
||||
// Check if this machine matches any bootstrap peer other than the first
|
||||
for i := 1; i < len(bootstrapPeers); i++ {
|
||||
host := parseHostFromMultiaddr(bootstrapPeers[i])
|
||||
if host != "" && isLocalIP(host) {
|
||||
isSecondaryBootstrap = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if isSecondaryBootstrap {
|
||||
// Secondary bootstrap nodes join the primary bootstrap
|
||||
primaryBootstrapHost := parseHostFromMultiaddr(bootstrapPeers[0])
|
||||
cfg.Database.RQLiteJoinAddress = fmt.Sprintf("http://%s:4001", primaryBootstrapHost)
|
||||
logger.Printf("Secondary bootstrap node - joining primary bootstrap at: %s", cfg.Database.RQLiteJoinAddress)
|
||||
} else {
|
||||
// Primary bootstrap node doesn't join anyone - it starts the cluster
|
||||
cfg.Database.RQLiteJoinAddress = ""
|
||||
logger.Printf("Primary bootstrap node - starting new RQLite cluster")
|
||||
}
|
||||
} else {
|
||||
// Configure bootstrap peers for P2P discovery
|
||||
var rqliteJoinAddr string
|
||||
if *bootstrap != "" {
|
||||
// Use command line bootstrap if provided
|
||||
cfg.Discovery.BootstrapPeers = []string{*bootstrap}
|
||||
// Extract IP from bootstrap peer for RQLite join
|
||||
bootstrapHost := parseHostFromMultiaddr(*bootstrap)
|
||||
if bootstrapHost != "" {
|
||||
rqliteJoinAddr = fmt.Sprintf("http://%s:4001", bootstrapHost)
|
||||
logger.Printf("Using extracted bootstrap host %s for RQLite join", bootstrapHost)
|
||||
} else {
|
||||
logger.Printf("Warning: Could not extract host from bootstrap peer %s, using localhost fallback", *bootstrap)
|
||||
rqliteJoinAddr = "http://localhost:4001" // Use localhost fallback instead
|
||||
}
|
||||
logger.Printf("Using command line bootstrap peer: %s", *bootstrap)
|
||||
} else {
|
||||
// Use environment-configured bootstrap peers
|
||||
bootstrapPeers := constants.GetBootstrapPeers()
|
||||
if len(bootstrapPeers) > 0 {
|
||||
cfg.Discovery.BootstrapPeers = bootstrapPeers
|
||||
// Use the first bootstrap peer for RQLite join
|
||||
bootstrapHost := parseHostFromMultiaddr(bootstrapPeers[0])
|
||||
if bootstrapHost != "" {
|
||||
rqliteJoinAddr = fmt.Sprintf("http://%s:4001", bootstrapHost)
|
||||
logger.Printf("Using extracted bootstrap host %s for RQLite join", bootstrapHost)
|
||||
} else {
|
||||
logger.Printf("Warning: Could not extract host from bootstrap peer %s", bootstrapPeers[0])
|
||||
// Try primary production server as fallback
|
||||
rqliteJoinAddr = "http://localhost:4001"
|
||||
}
|
||||
logger.Printf("Using environment bootstrap peers: %v", bootstrapPeers)
|
||||
} else {
|
||||
logger.Printf("Warning: No bootstrap peers configured")
|
||||
// Default to localhost when no peers configured
|
||||
rqliteJoinAddr = "http://localhost:4001"
|
||||
logger.Printf("Using localhost fallback for RQLite join")
|
||||
}
|
||||
|
||||
// Log network connectivity diagnostics
|
||||
logger.Printf("=== NETWORK DIAGNOSTICS ===")
|
||||
logger.Printf("Target RQLite join address: %s", rqliteJoinAddr)
|
||||
runNetworkDiagnostics(rqliteJoinAddr, logger)
|
||||
}
|
||||
|
||||
// Regular nodes join the bootstrap node's RQLite cluster
|
||||
cfg.Database.RQLiteJoinAddress = rqliteJoinAddr
|
||||
logger.Printf("Regular node - joining RQLite cluster at: %s", cfg.Database.RQLiteJoinAddress)
|
||||
}
|
||||
|
||||
@ -159,8 +209,12 @@ func isBootstrapNode() bool {
|
||||
return true // In development, assume we're running the bootstrap
|
||||
}
|
||||
|
||||
// Check if this is a production bootstrap server
|
||||
// You could add more sophisticated host matching here
|
||||
// Check if this is a production bootstrap server by IP
|
||||
if host != "" && isLocalIP(host) {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check if this is a production bootstrap server by hostname
|
||||
if hostname != "" && strings.Contains(peerAddr, hostname) {
|
||||
return true
|
||||
}
|
||||
@ -184,6 +238,28 @@ func parseHostFromMultiaddr(multiaddr string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// isLocalIP checks if the given IP address belongs to this machine
|
||||
func isLocalIP(ip string) bool {
|
||||
// Try to run ip command to get local IPs
|
||||
if output, err := exec.Command("ip", "addr", "show").Output(); err == nil {
|
||||
if strings.Contains(string(output), ip) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: try hostname -I command
|
||||
if output, err := exec.Command("hostname", "-I").Output(); err == nil {
|
||||
ips := strings.Fields(strings.TrimSpace(string(output)))
|
||||
for _, localIP := range ips {
|
||||
if localIP == ip {
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func startNode(ctx context.Context, cfg *config.Config, port int, isBootstrap bool, logger *logging.StandardLogger) error {
|
||||
// Create and start node using the unified node implementation
|
||||
n, err := node.NewNode(cfg)
|
||||
@ -217,3 +293,75 @@ func startNode(ctx context.Context, cfg *config.Config, port int, isBootstrap bo
|
||||
// Stop node
|
||||
return n.Stop()
|
||||
}
|
||||
|
||||
// runNetworkDiagnostics performs network connectivity tests
|
||||
func runNetworkDiagnostics(rqliteJoinAddr string, logger *logging.StandardLogger) {
|
||||
// Extract host and port from the join address
|
||||
if !strings.HasPrefix(rqliteJoinAddr, "http://") {
|
||||
logger.Printf("Invalid join address format: %s", rqliteJoinAddr)
|
||||
return
|
||||
}
|
||||
|
||||
// Parse URL to extract host:port
|
||||
url := strings.TrimPrefix(rqliteJoinAddr, "http://")
|
||||
parts := strings.Split(url, ":")
|
||||
if len(parts) != 2 {
|
||||
logger.Printf("Cannot parse host:port from %s", rqliteJoinAddr)
|
||||
return
|
||||
}
|
||||
|
||||
host := parts[0]
|
||||
port := parts[1]
|
||||
|
||||
logger.Printf("Testing connectivity to %s:%s", host, port)
|
||||
|
||||
// Test 1: Basic connectivity with netcat or telnet
|
||||
if output, err := exec.Command("timeout", "5", "nc", "-z", "-v", host, port).CombinedOutput(); err == nil {
|
||||
logger.Printf("✅ Port %s:%s is reachable", host, port)
|
||||
logger.Printf("netcat output: %s", strings.TrimSpace(string(output)))
|
||||
} else {
|
||||
logger.Printf("❌ Port %s:%s is NOT reachable", host, port)
|
||||
logger.Printf("netcat error: %v", err)
|
||||
logger.Printf("netcat output: %s", strings.TrimSpace(string(output)))
|
||||
}
|
||||
|
||||
// Test 2: HTTP connectivity test
|
||||
if output, err := exec.Command("timeout", "5", "curl", "-s", "-o", "/dev/null", "-w", "%{http_code}", rqliteJoinAddr+"/status").Output(); err == nil {
|
||||
httpCode := strings.TrimSpace(string(output))
|
||||
if httpCode == "200" {
|
||||
logger.Printf("✅ HTTP service is responding correctly (status: %s)", httpCode)
|
||||
} else {
|
||||
logger.Printf("⚠️ HTTP service responded with status: %s", httpCode)
|
||||
}
|
||||
} else {
|
||||
logger.Printf("❌ HTTP request failed: %v", err)
|
||||
}
|
||||
|
||||
// Test 3: Ping test
|
||||
if output, err := exec.Command("ping", "-c", "3", "-W", "2", host).Output(); err == nil {
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "packet loss") {
|
||||
logger.Printf("🏓 Ping result: %s", strings.TrimSpace(line))
|
||||
break
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Printf("❌ Ping test failed: %v", err)
|
||||
}
|
||||
|
||||
// Test 4: DNS resolution
|
||||
if output, err := exec.Command("nslookup", host).Output(); err == nil {
|
||||
logger.Printf("🔍 DNS resolution successful")
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "Address:") && !strings.Contains(line, "#53") {
|
||||
logger.Printf("DNS result: %s", strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
logger.Printf("❌ DNS resolution failed: %v", err)
|
||||
}
|
||||
|
||||
logger.Printf("=== END DIAGNOSTICS ===")
|
||||
}
|
||||
|
@ -18,8 +18,8 @@ var (
|
||||
// BootstrapAddresses are the full multiaddrs for bootstrap nodes
|
||||
BootstrapAddresses []string
|
||||
|
||||
// BootstrapPort is the default port for bootstrap nodes
|
||||
BootstrapPort int = 4001
|
||||
// BootstrapPort is the default port for bootstrap nodes (LibP2P)
|
||||
BootstrapPort int = 4000
|
||||
)
|
||||
|
||||
// Load environment variables and initialize bootstrap configuration
|
||||
@ -91,12 +91,12 @@ func setDefaultBootstrapConfig() {
|
||||
if env := os.Getenv("ENVIRONMENT"); env == "production" {
|
||||
// Production: only use live production peers
|
||||
BootstrapPeerIDs = []string{
|
||||
"12D3KooWQRK2duw5B5LXi8gA7HBBFiCsLvwyph2ZU9VBmvbE1Nei",
|
||||
"12D3KooWNxt9bNvqftdqXg98JcUHreGxedWSZRUbyqXJ6CW7GaD4",
|
||||
"12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm",
|
||||
}
|
||||
BootstrapAddresses = []string{
|
||||
"/ip4/57.129.81.31/tcp/4001/p2p/12D3KooWQRK2duw5B5LXi8gA7HBBFiCsLvwyph2ZU9VBmvbE1Nei",
|
||||
"/ip4/38.242.250.186/tcp/4001/p2p/12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm",
|
||||
"/ip4/57.129.81.31/tcp/4000/p2p/12D3KooWNxt9bNvqftdqXg98JcUHreGxedWSZRUbyqXJ6CW7GaD4",
|
||||
"/ip4/38.242.250.186/tcp/4000/p2p/12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm",
|
||||
}
|
||||
} else {
|
||||
// Development: only use localhost bootstrap
|
||||
@ -104,10 +104,10 @@ func setDefaultBootstrapConfig() {
|
||||
"12D3KooWBQAr9Lj9Z3918wBT523tJaRiPN6zRywAtttvPrwcZfJb",
|
||||
}
|
||||
BootstrapAddresses = []string{
|
||||
"/ip4/127.0.0.1/tcp/4001/p2p/12D3KooWBQAr9Lj9Z3918wBT523tJaRiPN6zRywAtttvPrwcZfJb",
|
||||
"/ip4/127.0.0.1/tcp/4000/p2p/12D3KooWBQAr9Lj9Z3918wBT523tJaRiPN6zRywAtttvPrwcZfJb",
|
||||
}
|
||||
}
|
||||
BootstrapPort = 4001
|
||||
BootstrapPort = 4000
|
||||
}
|
||||
|
||||
// extractPeerIDFromMultiaddr extracts the peer ID from a multiaddr string
|
||||
|
@ -3,10 +3,12 @@ package database
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/rqlite/gorqlite"
|
||||
@ -41,15 +43,49 @@ func (r *RQLiteManager) Start(ctx context.Context) error {
|
||||
return fmt.Errorf("failed to create RQLite data directory: %w", err)
|
||||
}
|
||||
|
||||
// Get the external IP address for advertising
|
||||
externalIP, err := r.getExternalIP()
|
||||
if err != nil {
|
||||
r.logger.Warn("Failed to get external IP, using localhost", zap.Error(err))
|
||||
externalIP = "localhost"
|
||||
}
|
||||
r.logger.Info("Using external IP for RQLite advertising", zap.String("ip", externalIP))
|
||||
|
||||
// Build RQLite command
|
||||
args := []string{
|
||||
"-http-addr", fmt.Sprintf("localhost:%d", r.config.RQLitePort),
|
||||
"-raft-addr", fmt.Sprintf("localhost:%d", r.config.RQLiteRaftPort),
|
||||
"-http-addr", fmt.Sprintf("0.0.0.0:%d", r.config.RQLitePort),
|
||||
"-raft-addr", fmt.Sprintf("0.0.0.0:%d", r.config.RQLiteRaftPort),
|
||||
// Auth disabled for testing
|
||||
}
|
||||
|
||||
// Add join address if specified (for non-bootstrap nodes)
|
||||
// Add advertised addresses if we have an external IP
|
||||
if externalIP != "localhost" {
|
||||
args = append(args, "-http-adv-addr", fmt.Sprintf("%s:%d", externalIP, r.config.RQLitePort))
|
||||
args = append(args, "-raft-adv-addr", fmt.Sprintf("%s:%d", externalIP, r.config.RQLiteRaftPort))
|
||||
}
|
||||
|
||||
// Add join address if specified (for non-bootstrap or secondary bootstrap nodes)
|
||||
if r.config.RQLiteJoinAddress != "" {
|
||||
r.logger.Info("Joining RQLite cluster", zap.String("join_address", r.config.RQLiteJoinAddress))
|
||||
|
||||
// Validate join address format before using it
|
||||
if strings.HasPrefix(r.config.RQLiteJoinAddress, "http://") {
|
||||
// Test connectivity and log the results, but always attempt to join
|
||||
if err := r.testJoinAddress(r.config.RQLiteJoinAddress); err != nil {
|
||||
r.logger.Warn("Join address connectivity test failed, but will still attempt to join",
|
||||
zap.String("join_address", r.config.RQLiteJoinAddress),
|
||||
zap.Error(err))
|
||||
} else {
|
||||
r.logger.Info("Join address is reachable, proceeding with cluster join")
|
||||
}
|
||||
// Always add the join parameter - let RQLite handle retries
|
||||
args = append(args, "-join", r.config.RQLiteJoinAddress)
|
||||
} else {
|
||||
r.logger.Warn("Invalid join address format, skipping join", zap.String("address", r.config.RQLiteJoinAddress))
|
||||
return fmt.Errorf("invalid RQLite join address format: %s (must start with http://)", r.config.RQLiteJoinAddress)
|
||||
}
|
||||
} else {
|
||||
r.logger.Info("No join address specified - starting as new cluster")
|
||||
}
|
||||
|
||||
// Add data directory as positional argument
|
||||
@ -60,6 +96,8 @@ func (r *RQLiteManager) Start(ctx context.Context) error {
|
||||
zap.Int("http_port", r.config.RQLitePort),
|
||||
zap.Int("raft_port", r.config.RQLiteRaftPort),
|
||||
zap.String("join_address", r.config.RQLiteJoinAddress),
|
||||
zap.String("external_ip", externalIP),
|
||||
zap.Strings("full_args", args),
|
||||
)
|
||||
|
||||
// Start RQLite process
|
||||
@ -168,3 +206,120 @@ func (r *RQLiteManager) Stop() error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// getExternalIP attempts to get the external IP address of this machine
|
||||
func (r *RQLiteManager) getExternalIP() (string, error) {
|
||||
// Method 1: Try using `ip route get` to find the IP used to reach the internet
|
||||
if output, err := exec.Command("ip", "route", "get", "8.8.8.8").Output(); err == nil {
|
||||
lines := strings.Split(string(output), "\n")
|
||||
for _, line := range lines {
|
||||
if strings.Contains(line, "src") {
|
||||
parts := strings.Fields(line)
|
||||
for i, part := range parts {
|
||||
if part == "src" && i+1 < len(parts) {
|
||||
ip := parts[i+1]
|
||||
if net.ParseIP(ip) != nil {
|
||||
r.logger.Debug("Found external IP via ip route", zap.String("ip", ip))
|
||||
return ip, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Get all network interfaces and find non-localhost, non-private IPs
|
||||
interfaces, err := net.Interfaces()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Prefer public IPs over private IPs
|
||||
if ip.To4() != nil && !ip.IsPrivate() {
|
||||
r.logger.Debug("Found public IP", zap.String("ip", ip.String()))
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Fall back to private IPs if no public IP found
|
||||
for _, iface := range interfaces {
|
||||
if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
|
||||
continue
|
||||
}
|
||||
|
||||
addrs, err := iface.Addrs()
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
for _, addr := range addrs {
|
||||
var ip net.IP
|
||||
switch v := addr.(type) {
|
||||
case *net.IPNet:
|
||||
ip = v.IP
|
||||
case *net.IPAddr:
|
||||
ip = v.IP
|
||||
}
|
||||
|
||||
if ip == nil || ip.IsLoopback() {
|
||||
continue
|
||||
}
|
||||
|
||||
// Use any IPv4 address
|
||||
if ip.To4() != nil {
|
||||
r.logger.Debug("Found private IP", zap.String("ip", ip.String()))
|
||||
return ip.String(), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("no suitable IP address found")
|
||||
}
|
||||
|
||||
// testJoinAddress tests if a join address is reachable
|
||||
func (r *RQLiteManager) testJoinAddress(joinAddress string) error {
|
||||
// Test connection to the join address with a short timeout
|
||||
client := &http.Client{Timeout: 5 * time.Second}
|
||||
|
||||
// Try to connect to the status endpoint
|
||||
statusURL := joinAddress + "/status"
|
||||
r.logger.Debug("Testing join address", zap.String("url", statusURL))
|
||||
|
||||
resp, err := client.Get(statusURL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to connect to join address %s: %w", joinAddress, err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return fmt.Errorf("join address %s returned status %d", joinAddress, resp.StatusCode)
|
||||
}
|
||||
|
||||
r.logger.Info("Join address is reachable", zap.String("address", joinAddress))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -58,7 +58,6 @@ func NewNode(cfg *config.Config) (*Node, error) {
|
||||
func (n *Node) Start(ctx context.Context) error {
|
||||
n.logger.ComponentInfo(logging.ComponentNode, "Starting network node",
|
||||
zap.String("data_dir", n.config.Node.DataDir),
|
||||
zap.String("type", "bootstrap"),
|
||||
)
|
||||
|
||||
// Create data directory
|
||||
|
@ -15,7 +15,7 @@ NOCOLOR='\033[0m'
|
||||
INSTALL_DIR="/opt/debros"
|
||||
REPO_URL="https://git.debros.io/DeBros/network.git"
|
||||
MIN_GO_VERSION="1.19"
|
||||
NODE_PORT="4001"
|
||||
NODE_PORT="4000" # LibP2P port for peer-to-peer communication
|
||||
RQLITE_PORT="4001" # All nodes use same RQLite port to join same cluster
|
||||
RAFT_PORT="4002" # All nodes use same Raft port
|
||||
UPDATE_MODE=false
|
||||
@ -98,18 +98,12 @@ detect_os() {
|
||||
|
||||
# Check if DeBros Network is already installed
|
||||
check_existing_installation() {
|
||||
if [ -d "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/bin/bootstrap" ] && [ -f "$INSTALL_DIR/bin/node" ]; then
|
||||
if [ -d "$INSTALL_DIR" ] && [ -f "$INSTALL_DIR/bin/node" ]; then
|
||||
log "Found existing DeBros Network installation at $INSTALL_DIR"
|
||||
|
||||
# Check if services are running
|
||||
BOOTSTRAP_RUNNING=false
|
||||
# Check if service is running
|
||||
NODE_RUNNING=false
|
||||
|
||||
if systemctl is-active --quiet debros-bootstrap.service 2>/dev/null; then
|
||||
BOOTSTRAP_RUNNING=true
|
||||
log "Bootstrap service is currently running"
|
||||
fi
|
||||
|
||||
if systemctl is-active --quiet debros-node.service 2>/dev/null; then
|
||||
NODE_RUNNING=true
|
||||
log "Node service is currently running"
|
||||
@ -578,13 +572,11 @@ build_binaries() {
|
||||
if [ "$UPDATE_MODE" = true ]; then
|
||||
log "Update mode: checking for running services before binary update..."
|
||||
|
||||
for service in debros-bootstrap debros-node; do
|
||||
if systemctl is-active --quiet $service.service 2>/dev/null; then
|
||||
log "Stopping $service service to update binaries..."
|
||||
sudo systemctl stop $service.service
|
||||
services_were_running+=("$service")
|
||||
if systemctl is-active --quiet debros-node.service 2>/dev/null; then
|
||||
log "Stopping debros-node service to update binaries..."
|
||||
sudo systemctl stop debros-node.service
|
||||
services_were_running+=("debros-node")
|
||||
fi
|
||||
done
|
||||
|
||||
# Give services a moment to fully stop
|
||||
if [ ${#services_were_running[@]} -gt 0 ]; then
|
||||
@ -669,7 +661,7 @@ configure_firewall() {
|
||||
log "Required ports to allow:"
|
||||
log " - Port $NODE_PORT (Node)"
|
||||
log " - Port $RQLITE_PORT (RQLite)"
|
||||
log " - Port $RAFT_NODE_PORT (Raft)"
|
||||
log " - Port $RAFT_PORT (Raft)"
|
||||
fi
|
||||
fi
|
||||
}
|
||||
@ -693,7 +685,7 @@ create_systemd_service() {
|
||||
|
||||
# Determine the correct ExecStart command based on node type
|
||||
local exec_start=""
|
||||
exec_start="$INSTALL_DIR/bin/node -data $INSTALL_DIR/data/node -port $NODE_PORT"
|
||||
exec_start="$INSTALL_DIR/bin/node -data $INSTALL_DIR/data/node"
|
||||
|
||||
cat > /tmp/debros-$NODE_TYPE.service << EOF
|
||||
[Unit]
|
||||
@ -828,7 +820,7 @@ main() {
|
||||
|
||||
log "${GREEN}Node Port:${NOCOLOR} ${CYAN}$NODE_PORT${NOCOLOR}"
|
||||
log "${GREEN}RQLite Port:${NOCOLOR} ${CYAN}$RQLITE_PORT${NOCOLOR}"
|
||||
log "${GREEN}Raft Port:${NOCOLOR} ${CYAN}$RAFT_NODE_PORT${NOCOLOR}"
|
||||
log "${GREEN}Raft Port:${NOCOLOR} ${CYAN}$RAFT_PORT${NOCOLOR}"
|
||||
|
||||
log "${BLUE}==================================================${NOCOLOR}"
|
||||
log "${GREEN}Management Commands:${NOCOLOR}"
|
||||
|
140
scripts/setup-production-security.sh
Executable file
140
scripts/setup-production-security.sh
Executable file
@ -0,0 +1,140 @@
|
||||
#!/bin/bash
|
||||
set -euo pipefail
|
||||
|
||||
# DeBros Network Production Security Setup
|
||||
# This script configures secure RQLite clustering with authentication
|
||||
|
||||
DEBROS_DIR="/opt/debros"
|
||||
CONFIG_DIR="$DEBROS_DIR/configs"
|
||||
KEYS_DIR="$DEBROS_DIR/keys"
|
||||
|
||||
echo "🔐 Setting up DeBros Network Production Security..."
|
||||
|
||||
# Create security directories
|
||||
sudo mkdir -p "$CONFIG_DIR" "$KEYS_DIR"
|
||||
sudo chown debros:debros "$CONFIG_DIR" "$KEYS_DIR"
|
||||
sudo chmod 750 "$KEYS_DIR"
|
||||
|
||||
# Generate cluster authentication credentials
|
||||
CLUSTER_USER="debros_cluster"
|
||||
CLUSTER_PASS=$(openssl rand -base64 32)
|
||||
API_USER="debros_api"
|
||||
API_PASS=$(openssl rand -base64 32)
|
||||
|
||||
echo "🔑 Generated cluster credentials:"
|
||||
echo " Cluster User: $CLUSTER_USER"
|
||||
echo " API User: $API_USER"
|
||||
|
||||
# Create RQLite users configuration
|
||||
cat > "$CONFIG_DIR/rqlite-users.json" << EOF
|
||||
[
|
||||
{
|
||||
"username": "$CLUSTER_USER",
|
||||
"password": "$CLUSTER_PASS",
|
||||
"perms": ["*"]
|
||||
},
|
||||
{
|
||||
"username": "$API_USER",
|
||||
"password": "$API_PASS",
|
||||
"perms": ["status", "ready", "nodes", "db:*"]
|
||||
}
|
||||
]
|
||||
EOF
|
||||
|
||||
sudo chown debros:debros "$CONFIG_DIR/rqlite-users.json"
|
||||
sudo chmod 600 "$CONFIG_DIR/rqlite-users.json"
|
||||
|
||||
# Store credentials securely
|
||||
cat > "$KEYS_DIR/rqlite-cluster-auth" << EOF
|
||||
RQLITE_CLUSTER_USER="$CLUSTER_USER"
|
||||
RQLITE_CLUSTER_PASS="$CLUSTER_PASS"
|
||||
RQLITE_API_USER="$API_USER"
|
||||
RQLITE_API_PASS="$API_PASS"
|
||||
EOF
|
||||
|
||||
sudo chown debros:debros "$KEYS_DIR/rqlite-cluster-auth"
|
||||
sudo chmod 600 "$KEYS_DIR/rqlite-cluster-auth"
|
||||
|
||||
# Configure firewall for production
|
||||
echo "🛡️ Configuring production firewall rules..."
|
||||
|
||||
# Reset UFW to defaults
|
||||
sudo ufw --force reset
|
||||
|
||||
# Default policies
|
||||
sudo ufw default deny incoming
|
||||
sudo ufw default allow outgoing
|
||||
|
||||
# SSH (adjust port as needed)
|
||||
sudo ufw allow 22/tcp comment "SSH"
|
||||
|
||||
# LibP2P P2P networking (public, encrypted)
|
||||
sudo ufw allow 4000/tcp comment "LibP2P P2P"
|
||||
sudo ufw allow 4000/udp comment "LibP2P QUIC"
|
||||
|
||||
# RQLite ports (restrict to cluster IPs only)
|
||||
BOOTSTRAP_IPS=("57.129.81.31" "38.242.250.186")
|
||||
for ip in "${BOOTSTRAP_IPS[@]}"; do
|
||||
sudo ufw allow from "$ip" to any port 4001 comment "RQLite HTTP from $ip"
|
||||
sudo ufw allow from "$ip" to any port 4002 comment "RQLite Raft from $ip"
|
||||
done
|
||||
|
||||
# Enable firewall
|
||||
sudo ufw --force enable
|
||||
|
||||
echo "🔧 Configuring RQLite cluster authentication..."
|
||||
|
||||
# Update RQLite join addresses with authentication
|
||||
AUTHENTICATED_JOIN_ADDRESS="http://$CLUSTER_USER:$CLUSTER_PASS@57.129.81.31:4001"
|
||||
|
||||
# Create environment file for authenticated connections
|
||||
cat > "$CONFIG_DIR/rqlite-env" << EOF
|
||||
# RQLite cluster authentication
|
||||
RQLITE_JOIN_AUTH_USER="$CLUSTER_USER"
|
||||
RQLITE_JOIN_AUTH_PASS="$CLUSTER_PASS"
|
||||
RQLITE_JOIN_ADDRESS_AUTH="$AUTHENTICATED_JOIN_ADDRESS"
|
||||
EOF
|
||||
|
||||
sudo chown debros:debros "$CONFIG_DIR/rqlite-env"
|
||||
sudo chmod 600 "$CONFIG_DIR/rqlite-env"
|
||||
|
||||
# Create connection helper script
|
||||
cat > "$DEBROS_DIR/bin/rqlite-connect" << 'EOF'
|
||||
#!/bin/bash
|
||||
# Helper script for authenticated RQLite connections
|
||||
|
||||
source /opt/debros/keys/rqlite-cluster-auth
|
||||
|
||||
if [ "$1" = "cluster" ]; then
|
||||
rqlite -H localhost -p 4001 -u "$RQLITE_CLUSTER_USER" -p "$RQLITE_CLUSTER_PASS"
|
||||
elif [ "$1" = "api" ]; then
|
||||
rqlite -H localhost -p 4001 -u "$RQLITE_API_USER" -p "$RQLITE_API_PASS"
|
||||
else
|
||||
echo "Usage: $0 {cluster|api}"
|
||||
exit 1
|
||||
fi
|
||||
EOF
|
||||
|
||||
sudo chown debros:debros "$DEBROS_DIR/bin/rqlite-connect"
|
||||
sudo chmod 755 "$DEBROS_DIR/bin/rqlite-connect"
|
||||
|
||||
echo "✅ Production security setup complete!"
|
||||
echo ""
|
||||
echo "📋 Security Summary:"
|
||||
echo " - RQLite authentication enabled"
|
||||
echo " - Firewall configured with IP restrictions"
|
||||
echo " - Cluster credentials generated and stored"
|
||||
echo " - Port 4000: Public LibP2P (encrypted P2P)"
|
||||
echo " - Port 4001/4002: RQLite cluster (IP-restricted)"
|
||||
echo ""
|
||||
echo "🔐 Credentials stored in:"
|
||||
echo " - Users: $CONFIG_DIR/rqlite-users.json"
|
||||
echo " - Auth: $KEYS_DIR/rqlite-cluster-auth"
|
||||
echo ""
|
||||
echo "🔌 Connect to RQLite:"
|
||||
echo " - Cluster admin: $DEBROS_DIR/bin/rqlite-connect cluster"
|
||||
echo " - API access: $DEBROS_DIR/bin/rqlite-connect api"
|
||||
echo ""
|
||||
echo "⚠️ IMPORTANT: Save these credentials securely!"
|
||||
echo " Cluster User: $CLUSTER_USER"
|
||||
echo " Cluster Pass: $CLUSTER_PASS"
|
Loading…
x
Reference in New Issue
Block a user