diff --git a/cmd/cli/main.go b/cmd/cli/main.go index 6a56a3f..d05792e 100644 --- a/cmd/cli/main.go +++ b/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 - Execute database query\n") fmt.Printf(" storage get - Get value from storage\n") fmt.Printf(" storage put - 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 "" +} diff --git a/cmd/node/main.go b/cmd/node/main.go index 56d96d7..ca1ca38 100644 --- a/cmd/node/main.go +++ b/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") + // 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 { - // Regular nodes join the bootstrap node's RQLite cluster - cfg.Database.RQLiteJoinAddress = "http://localhost:4001" - // 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 - logger.Printf("Using environment bootstrap peers: %v", 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 ===") +} diff --git a/pkg/constants/bootstrap.go b/pkg/constants/bootstrap.go index 32417fa..15d9789 100644 --- a/pkg/constants/bootstrap.go +++ b/pkg/constants/bootstrap.go @@ -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 diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 15146d1..a8e5750 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -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 != "" { - args = append(args, "-join", 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 +} + diff --git a/pkg/node/node.go b/pkg/node/node.go index dd1df96..1e23918 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -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 diff --git a/scripts/install-debros-network.sh b/scripts/install-debros-network.sh index b6ca814..3f0c057 100755 --- a/scripts/install-debros-network.sh +++ b/scripts/install-debros-network.sh @@ -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") - fi - done + 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 # 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}" diff --git a/scripts/setup-production-security.sh b/scripts/setup-production-security.sh new file mode 100755 index 0000000..9d2b4fa --- /dev/null +++ b/scripts/setup-production-security.sh @@ -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"