From 3975b5a59d5e183c633cb8b31d72aebd87caaae0 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 07:51:23 +0300 Subject: [PATCH 01/16] Fix debros-node service failing due to invalid -port flag - Remove invalid -port flag from systemd service ExecStart command - The node binary doesn't support -port flag, it uses hardcoded port 4001 - Fix variable name RAFT_NODE_PORT to RAFT_PORT in two places - Update check_existing_installation to only check for node binary since bootstrap binary was removed This resolves the service startup failure where the service was exiting with: 'flag provided but not defined: -port' --- scripts/install-debros-network.sh | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/install-debros-network.sh b/scripts/install-debros-network.sh index b6ca814..47a3055 100755 --- a/scripts/install-debros-network.sh +++ b/scripts/install-debros-network.sh @@ -98,7 +98,7 @@ 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 @@ -669,7 +669,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 +693,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 +828,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}" From 480354267a382b5fd38eed8551e79385398d2730 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:02:43 +0300 Subject: [PATCH 02/16] Fix RQLite join address for regular nodes - Regular nodes now join actual bootstrap nodes instead of localhost:4001 - Extract IP from bootstrap peer multiaddrs for RQLite join address - Add fallback to first known bootstrap node (57.129.81.31:4001) - This fixes the 'fatal: http://localhost:4001 is an invalid join address' error Regular nodes will now properly join the RQLite cluster of bootstrap nodes instead of trying to join themselves, which was causing startup failures. --- cmd/node/main.go | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/cmd/node/main.go b/cmd/node/main.go index 56d96d7..9cde66b 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -88,24 +88,40 @@ func main() { 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" - // 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) + } else { + rqliteJoinAddr = "http://57.129.81.31:4001" // Default fallback + } 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) + } else { + rqliteJoinAddr = "http://57.129.81.31:4001" // Default fallback + } logger.Printf("Using environment bootstrap peers: %v", bootstrapPeers) } else { logger.Printf("Warning: No bootstrap peers configured") + rqliteJoinAddr = "http://57.129.81.31:4001" // Default fallback } } + + // 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) } From 5cef68c13219d6510462aed4dcb4aa5ccb4f872e Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:10:28 +0300 Subject: [PATCH 03/16] Fix bootstrap peer ID mismatch for 57.129.81.31 The actual running bootstrap node has peer ID: 12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi But the constants file had the wrong peer ID: 12D3KooWQRK2duw5B5LXi8gA7HBBFiCsLvwyph2ZU9VBmvbE1Nei This mismatch was causing nodes to fail to connect to the bootstrap node, leading to the 'invalid join address' error from RQLite. --- pkg/constants/bootstrap.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/constants/bootstrap.go b/pkg/constants/bootstrap.go index 32417fa..b8dc14b 100644 --- a/pkg/constants/bootstrap.go +++ b/pkg/constants/bootstrap.go @@ -91,11 +91,11 @@ func setDefaultBootstrapConfig() { if env := os.Getenv("ENVIRONMENT"); env == "production" { // Production: only use live production peers BootstrapPeerIDs = []string{ - "12D3KooWQRK2duw5B5LXi8gA7HBBFiCsLvwyph2ZU9VBmvbE1Nei", + "12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi", "12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm", } BootstrapAddresses = []string{ - "/ip4/57.129.81.31/tcp/4001/p2p/12D3KooWQRK2duw5B5LXi8gA7HBBFiCsLvwyph2ZU9VBmvbE1Nei", + "/ip4/57.129.81.31/tcp/4001/p2p/12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi", "/ip4/38.242.250.186/tcp/4001/p2p/12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm", } } else { From 37bf5829324fb69dbd141d0436367600606557bd Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:14:29 +0300 Subject: [PATCH 04/16] Fix bootstrap node detection to prevent self-join in RQLite The VPS at 57.129.81.31 was incorrectly trying to join itself as a regular node instead of being detected as a bootstrap node. Added isLocalIP() function to check if bootstrap peer IPs match local machine IPs using 'ip addr show' and 'hostname -I' commands. This should resolve the 'invalid join address' error where the bootstrap node was trying to join http://57.129.81.31:4001 (itself). --- cmd/node/main.go | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/cmd/node/main.go b/cmd/node/main.go index 9cde66b..4429063 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" @@ -175,8 +176,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 } @@ -200,6 +205,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) From dfa4de33b406161c81d5fe7e801810280ee851df Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:21:21 +0300 Subject: [PATCH 05/16] Fix multi-bootstrap RQLite cluster setup - Primary bootstrap (57.129.81.31): starts new cluster (no join address) - Secondary bootstrap (38.242.250.186): joins primary bootstrap cluster - Regular nodes: join primary bootstrap cluster This allows both VPS servers to be bootstrap nodes while forming a proper RQLite cluster where the secondary bootstrap joins the primary instead of trying to start its own independent cluster. Should resolve the leadership establishment timeout on the second VPS. --- cmd/node/main.go | 27 ++++++++++++++++++++++++--- pkg/database/rqlite.go | 2 +- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/cmd/node/main.go b/cmd/node/main.go index 4429063..fbd8fba 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -85,9 +85,30 @@ 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 { // Configure bootstrap peers for P2P discovery var rqliteJoinAddr string diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 15146d1..2f04a5d 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -47,7 +47,7 @@ func (r *RQLiteManager) Start(ctx context.Context) error { "-raft-addr", fmt.Sprintf("localhost:%d", r.config.RQLiteRaftPort), } - // Add join address if specified (for non-bootstrap nodes) + // Add join address if specified (for non-bootstrap or secondary bootstrap nodes) if r.config.RQLiteJoinAddress != "" { args = append(args, "-join", r.config.RQLiteJoinAddress) } From 79d4a3980f0ea0742466bec0a85d1a2b23c66433 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:39:14 +0300 Subject: [PATCH 06/16] Add peer-id command to CLI for easy peer ID retrieval --- cmd/cli/main.go | 109 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 109 insertions(+) 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 "" +} From cc276ccc22d7379bf35f25a8d7a71b8e4234f3e9 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:39:44 +0300 Subject: [PATCH 07/16] Fix install script service management to only use debros-node - Remove references to debros-bootstrap service in update logic - Standardize on single debros-node service for all node types - Fix service stopping/starting during updates - Clean up legacy service references --- scripts/install-debros-network.sh | 20 ++++++-------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/scripts/install-debros-network.sh b/scripts/install-debros-network.sh index 47a3055..b38b7cf 100755 --- a/scripts/install-debros-network.sh +++ b/scripts/install-debros-network.sh @@ -101,15 +101,9 @@ check_existing_installation() { 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 From 089916b5ddbea4dd91067ec8d94a733bccba9a72 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 08:44:23 +0300 Subject: [PATCH 08/16] Fix port conflict between RQLite and LibP2P CRITICAL FIX: Separate RQLite and LibP2P ports to prevent service startup failures Changes: - LibP2P now uses port 4000 (was conflicting with RQLite on 4001) - RQLite continues to use port 4001 for HTTP API - RQLite Raft uses port 4002 - Updated bootstrap peer configurations to use port 4000 - Updated install script port configurations - Fixed firewall configuration to allow correct ports This resolves the 'bind: address already in use' error that was preventing the debros-node service from starting properly. --- cmd/node/main.go | 4 ++-- pkg/constants/bootstrap.go | 12 ++++++------ pkg/node/node.go | 1 - scripts/install-debros-network.sh | 2 +- 4 files changed, 9 insertions(+), 10 deletions(-) diff --git a/cmd/node/main.go b/cmd/node/main.go index fbd8fba..ca77387 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -48,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 diff --git a/pkg/constants/bootstrap.go b/pkg/constants/bootstrap.go index b8dc14b..51700db 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 @@ -95,8 +95,8 @@ func setDefaultBootstrapConfig() { "12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm", } BootstrapAddresses = []string{ - "/ip4/57.129.81.31/tcp/4001/p2p/12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi", - "/ip4/38.242.250.186/tcp/4001/p2p/12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm", + "/ip4/57.129.81.31/tcp/4000/p2p/12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi", + "/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/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 b38b7cf..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 From b529c3274297e59e317f387319754940c20271a7 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 05:56:45 +0000 Subject: [PATCH 09/16] Update pkg/constants/bootstrap.go --- pkg/constants/bootstrap.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/constants/bootstrap.go b/pkg/constants/bootstrap.go index 51700db..15d9789 100644 --- a/pkg/constants/bootstrap.go +++ b/pkg/constants/bootstrap.go @@ -91,11 +91,11 @@ func setDefaultBootstrapConfig() { if env := os.Getenv("ENVIRONMENT"); env == "production" { // Production: only use live production peers BootstrapPeerIDs = []string{ - "12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi", + "12D3KooWNxt9bNvqftdqXg98JcUHreGxedWSZRUbyqXJ6CW7GaD4", "12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm", } BootstrapAddresses = []string{ - "/ip4/57.129.81.31/tcp/4000/p2p/12D3KooWJvJj94TmNwG1sntDWgAXi7MN3xxLLkoQzgHX6gQ22eKi", + "/ip4/57.129.81.31/tcp/4000/p2p/12D3KooWNxt9bNvqftdqXg98JcUHreGxedWSZRUbyqXJ6CW7GaD4", "/ip4/38.242.250.186/tcp/4000/p2p/12D3KooWGbdnA22bN24X2gyY1o9jozwTBq9wbfvwtJ7G4XQ9JgFm", } } else { From 2181b5ced09d685c298acec35f1fa2a25347f865 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 11:24:24 +0300 Subject: [PATCH 10/16] Fix RQLite bind addresses to allow external connections - Change RQLite HTTP bind from localhost to 0.0.0.0 - Change RQLite Raft bind from localhost to 0.0.0.0 - This allows secondary bootstrap nodes and regular nodes to join the cluster - Resolves 'invalid join address' error for secondary bootstrap nodes --- pkg/database/rqlite.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 2f04a5d..4cd8b00 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -43,8 +43,8 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // 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), } // Add join address if specified (for non-bootstrap or secondary bootstrap nodes) From 79efd7b2c594997a469e317875c895393795678a Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 11:29:06 +0300 Subject: [PATCH 11/16] Fix RQLite advertised addresses for proper cluster formation - Add automatic external IP detection for RQLite advertised addresses - Use 0.0.0.0 for binding but actual IP for advertising to other nodes - Add -http-adv-addr and -raft-adv-addr parameters to RQLite startup - Resolves 'advertised HTTP address is not routable' error - Enables proper RQLite cluster formation between nodes --- pkg/database/rqlite.go | 108 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 108 insertions(+) diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 4cd8b00..2140e46 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,12 +43,25 @@ 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" + } + // Build RQLite command args := []string{ "-http-addr", fmt.Sprintf("0.0.0.0:%d", r.config.RQLitePort), "-raft-addr", fmt.Sprintf("0.0.0.0:%d", r.config.RQLiteRaftPort), } + // 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) @@ -168,3 +183,96 @@ 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") +} From e6a305a8a7d2be5037bbc03194b0ab7628ad73fe Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 13:00:55 +0300 Subject: [PATCH 12/16] Fix RQLite join address validation and improve error handling - Replace hardcoded fallback IP with localhost for better compatibility - Add join address format validation - Improve logging for better troubleshooting - Add detailed RQLite startup logging with full args --- cmd/node/main.go | 13 ++++++++++--- pkg/database/rqlite.go | 17 +++++++++++++++-- 2 files changed, 25 insertions(+), 5 deletions(-) diff --git a/cmd/node/main.go b/cmd/node/main.go index ca77387..3837a3d 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -119,8 +119,10 @@ func main() { bootstrapHost := parseHostFromMultiaddr(*bootstrap) if bootstrapHost != "" { rqliteJoinAddr = fmt.Sprintf("http://%s:4001", bootstrapHost) + logger.Printf("Using extracted bootstrap host %s for RQLite join", bootstrapHost) } else { - rqliteJoinAddr = "http://57.129.81.31:4001" // Default fallback + 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 { @@ -132,13 +134,18 @@ func main() { 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 { - rqliteJoinAddr = "http://57.129.81.31:4001" // Default fallback + 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") - rqliteJoinAddr = "http://57.129.81.31:4001" // Default fallback + // Default to localhost when no peers configured + rqliteJoinAddr = "http://localhost:4001" + logger.Printf("Using localhost fallback for RQLite join") } } diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 2140e46..ac7b4a9 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -43,12 +43,13 @@ 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 +// 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{ @@ -64,7 +65,17 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // 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://") { + 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 @@ -75,6 +86,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 From 56f0a01b791a4a46e241ccb0d702803133a04534 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 13:05:58 +0300 Subject: [PATCH 13/16] Add RQLite join address connectivity testing - Test join address reachability before attempting to join cluster - Fall back to starting new cluster if join address is unreachable - Add comprehensive logging for join address testing - Prevent RQLite fatal errors when bootstrap node is down This fixes the issue where secondary nodes fail with 'invalid join address' when the primary bootstrap node is not accessible on port 4001. --- pkg/database/rqlite.go | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index ac7b4a9..4bc45b9 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -69,7 +69,15 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // Validate join address format before using it if strings.HasPrefix(r.config.RQLiteJoinAddress, "http://") { - args = append(args, "-join", r.config.RQLiteJoinAddress) + // Test if the join address is reachable before attempting to join + if err := r.testJoinAddress(r.config.RQLiteJoinAddress); err != nil { + r.logger.Warn("Join address is not reachable, starting as new cluster instead", + zap.String("join_address", r.config.RQLiteJoinAddress), + zap.Error(err)) + // Don't add the -join parameter, let this node start its own cluster + } else { + 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) @@ -289,3 +297,26 @@ func (r *RQLiteManager) getExternalIP() (string, error) { 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 +} From 16a70a03aaba00bc83857a8441c8333e1b9552a9 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 13:08:27 +0300 Subject: [PATCH 14/16] Add comprehensive network connectivity diagnostics - Add automated network diagnostics for RQLite join addresses - Test port connectivity with netcat, HTTP responses, ping, and DNS - Provide detailed troubleshooting information in logs - Help identify exact causes of RQLite cluster join failures - Test connectivity before attempting RQLite cluster join This will help diagnose the 'invalid join address' error by showing exactly why the connection to 57.129.81.31:4001 is failing. --- cmd/node/main.go | 79 +++++++++++++++++++++++++++++++++++++++++- pkg/database/rqlite.go | 9 ++--- 2 files changed, 83 insertions(+), 5 deletions(-) diff --git a/cmd/node/main.go b/cmd/node/main.go index 3837a3d..ca1ca38 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -140,13 +140,18 @@ func main() { // Try primary production server as fallback rqliteJoinAddr = "http://localhost:4001" } - logger.Printf("Using environment bootstrap peers: %v", bootstrapPeers) + 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 @@ -288,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/database/rqlite.go b/pkg/database/rqlite.go index 4bc45b9..f806eb5 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -69,15 +69,16 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // Validate join address format before using it if strings.HasPrefix(r.config.RQLiteJoinAddress, "http://") { - // Test if the join address is reachable before attempting to join + // 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 is not reachable, starting as new cluster instead", + r.logger.Warn("Join address connectivity test failed, but will still attempt to join", zap.String("join_address", r.config.RQLiteJoinAddress), zap.Error(err)) - // Don't add the -join parameter, let this node start its own cluster } else { - args = append(args, "-join", r.config.RQLiteJoinAddress) + 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) From 3af1b58eb4c007abc34a12fa923ebc6827b10fc6 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 13:29:09 +0300 Subject: [PATCH 15/16] Add comprehensive production security for RQLite clustering Production Security Features: - RQLite authentication with secure user management - Firewall configuration with IP-based restrictions - Automated credential generation and storage - Authenticated cluster join addresses - Credential masking in logs for security - Helper scripts for secure RQLite connections Network Architecture: - Port 4000: Public LibP2P P2P (encrypted) - Port 4001/4002: RQLite cluster (IP-restricted to cluster members) - UFW firewall rules restricting RQLite access to cluster IPs only Security Components: - /opt/debros/configs/rqlite-users.json: User authentication - /opt/debros/keys/rqlite-cluster-auth: Secure credential storage - Automatic credential masking in logs - Production-ready setup script This implements enterprise-grade security for public network deployment while maintaining seamless cluster communication between trusted nodes. --- pkg/database/rqlite.go | 111 +++++++++++++++++++-- scripts/setup-production-security.sh | 140 +++++++++++++++++++++++++++ 2 files changed, 244 insertions(+), 7 deletions(-) create mode 100755 scripts/setup-production-security.sh diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index f806eb5..fc32963 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -55,6 +55,7 @@ func (r *RQLiteManager) Start(ctx context.Context) error { args := []string{ "-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", "/opt/debros/configs/rqlite-users.json", // Enable authentication } // Add advertised addresses if we have an external IP @@ -65,23 +66,33 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // 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)) + r.logger.Info("Joining RQLite cluster", zap.String("join_address", r.maskCredentials(r.config.RQLiteJoinAddress))) + + // Check for authenticated join address with credentials + joinAddress := r.config.RQLiteJoinAddress + if !strings.Contains(joinAddress, "@") { + // Try to load authentication credentials for cluster joining + if authAddr := r.loadAuthenticatedJoinAddress(); authAddr != "" { + joinAddress = authAddr + r.logger.Info("Using authenticated cluster join address") + } + } // Validate join address format before using it - if strings.HasPrefix(r.config.RQLiteJoinAddress, "http://") { + if strings.HasPrefix(joinAddress, "http://") { // Test connectivity and log the results, but always attempt to join - if err := r.testJoinAddress(r.config.RQLiteJoinAddress); err != nil { + if err := r.testJoinAddress(r.stripCredentials(joinAddress)); err != nil { r.logger.Warn("Join address connectivity test failed, but will still attempt to join", - zap.String("join_address", r.config.RQLiteJoinAddress), + zap.String("join_address", r.maskCredentials(joinAddress)), 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) + args = append(args, "-join", joinAddress) } 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) + r.logger.Warn("Invalid join address format, skipping join", zap.String("address", r.maskCredentials(joinAddress))) + return fmt.Errorf("invalid RQLite join address format: %s (must start with http://)", r.maskCredentials(joinAddress)) } } else { r.logger.Info("No join address specified - starting as new cluster") @@ -321,3 +332,89 @@ func (r *RQLiteManager) testJoinAddress(joinAddress string) error { r.logger.Info("Join address is reachable", zap.String("address", joinAddress)) return nil } + +// loadAuthenticatedJoinAddress loads authentication credentials and creates authenticated join URL +func (r *RQLiteManager) loadAuthenticatedJoinAddress() string { + // Try to load authentication credentials from environment file + if data, err := os.ReadFile("/opt/debros/configs/rqlite-env"); err == nil { + lines := strings.Split(string(data), "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "RQLITE_JOIN_ADDRESS_AUTH=") { + authAddr := strings.TrimPrefix(line, "RQLITE_JOIN_ADDRESS_AUTH=") + authAddr = strings.Trim(authAddr, `"`) + if authAddr != "" { + return authAddr + } + } + } + } + + // Fallback: try to construct from separate user/pass environment variables + if data, err := os.ReadFile("/opt/debros/keys/rqlite-cluster-auth"); err == nil { + lines := strings.Split(string(data), "\n") + var user, pass string + for _, line := range lines { + line = strings.TrimSpace(line) + if strings.HasPrefix(line, "RQLITE_CLUSTER_USER=") { + user = strings.TrimPrefix(line, "RQLITE_CLUSTER_USER=") + user = strings.Trim(user, `"`) + } else if strings.HasPrefix(line, "RQLITE_CLUSTER_PASS=") { + pass = strings.TrimPrefix(line, "RQLITE_CLUSTER_PASS=") + pass = strings.Trim(pass, `"`) + } + } + + if user != "" && pass != "" && r.config.RQLiteJoinAddress != "" { + // Extract base URL and add credentials + baseURL := r.config.RQLiteJoinAddress + if strings.HasPrefix(baseURL, "http://") { + // Insert credentials: http://user:pass@host:port + host := strings.TrimPrefix(baseURL, "http://") + return fmt.Sprintf("http://%s:%s@%s", user, pass, host) + } + } + } + + return "" // No credentials found +} + +// maskCredentials masks authentication credentials in URLs for logging +func (r *RQLiteManager) maskCredentials(url string) string { + if strings.Contains(url, "@") { + // URL contains credentials: http://user:pass@host:port + parts := strings.SplitN(url, "@", 2) + if len(parts) == 2 { + protocolAndCreds := parts[0] + hostAndPort := parts[1] + + // Extract protocol + protocolParts := strings.SplitN(protocolAndCreds, "://", 2) + if len(protocolParts) == 2 { + protocol := protocolParts[0] + return fmt.Sprintf("%s://***:***@%s", protocol, hostAndPort) + } + } + } + return url +} + +// stripCredentials removes authentication credentials from URLs for connectivity testing +func (r *RQLiteManager) stripCredentials(url string) string { + if strings.Contains(url, "@") { + // URL contains credentials: http://user:pass@host:port + parts := strings.SplitN(url, "@", 2) + if len(parts) == 2 { + protocolAndCreds := parts[0] + hostAndPort := parts[1] + + // Extract protocol + protocolParts := strings.SplitN(protocolAndCreds, "://", 2) + if len(protocolParts) == 2 { + protocol := protocolParts[0] + return fmt.Sprintf("%s://%s", protocol, hostAndPort) + } + } + } + return url +} 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" From 9de528815ca34e7837ac43662013d96f65c8a8c4 Mon Sep 17 00:00:00 2001 From: johnysigma Date: Wed, 6 Aug 2025 13:35:50 +0300 Subject: [PATCH 16/16] Disable RQLite authentication for testing - Remove -auth flag from RQLite startup command - Remove authentication credential loading logic - Simplify join address handling without credentials - Clean up unused authentication helper functions This allows testing basic cluster functionality without auth complexity. --- pkg/database/rqlite.go | 111 +++-------------------------------------- 1 file changed, 8 insertions(+), 103 deletions(-) diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index fc32963..a8e5750 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -55,7 +55,7 @@ func (r *RQLiteManager) Start(ctx context.Context) error { args := []string{ "-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", "/opt/debros/configs/rqlite-users.json", // Enable authentication + // Auth disabled for testing } // Add advertised addresses if we have an external IP @@ -66,33 +66,23 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // 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.maskCredentials(r.config.RQLiteJoinAddress))) - - // Check for authenticated join address with credentials - joinAddress := r.config.RQLiteJoinAddress - if !strings.Contains(joinAddress, "@") { - // Try to load authentication credentials for cluster joining - if authAddr := r.loadAuthenticatedJoinAddress(); authAddr != "" { - joinAddress = authAddr - r.logger.Info("Using authenticated cluster join address") - } - } + r.logger.Info("Joining RQLite cluster", zap.String("join_address", r.config.RQLiteJoinAddress)) // Validate join address format before using it - if strings.HasPrefix(joinAddress, "http://") { + if strings.HasPrefix(r.config.RQLiteJoinAddress, "http://") { // Test connectivity and log the results, but always attempt to join - if err := r.testJoinAddress(r.stripCredentials(joinAddress)); err != nil { + 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.maskCredentials(joinAddress)), + 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", joinAddress) + args = append(args, "-join", r.config.RQLiteJoinAddress) } else { - r.logger.Warn("Invalid join address format, skipping join", zap.String("address", r.maskCredentials(joinAddress))) - return fmt.Errorf("invalid RQLite join address format: %s (must start with http://)", r.maskCredentials(joinAddress)) + 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") @@ -333,88 +323,3 @@ func (r *RQLiteManager) testJoinAddress(joinAddress string) error { return nil } -// loadAuthenticatedJoinAddress loads authentication credentials and creates authenticated join URL -func (r *RQLiteManager) loadAuthenticatedJoinAddress() string { - // Try to load authentication credentials from environment file - if data, err := os.ReadFile("/opt/debros/configs/rqlite-env"); err == nil { - lines := strings.Split(string(data), "\n") - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "RQLITE_JOIN_ADDRESS_AUTH=") { - authAddr := strings.TrimPrefix(line, "RQLITE_JOIN_ADDRESS_AUTH=") - authAddr = strings.Trim(authAddr, `"`) - if authAddr != "" { - return authAddr - } - } - } - } - - // Fallback: try to construct from separate user/pass environment variables - if data, err := os.ReadFile("/opt/debros/keys/rqlite-cluster-auth"); err == nil { - lines := strings.Split(string(data), "\n") - var user, pass string - for _, line := range lines { - line = strings.TrimSpace(line) - if strings.HasPrefix(line, "RQLITE_CLUSTER_USER=") { - user = strings.TrimPrefix(line, "RQLITE_CLUSTER_USER=") - user = strings.Trim(user, `"`) - } else if strings.HasPrefix(line, "RQLITE_CLUSTER_PASS=") { - pass = strings.TrimPrefix(line, "RQLITE_CLUSTER_PASS=") - pass = strings.Trim(pass, `"`) - } - } - - if user != "" && pass != "" && r.config.RQLiteJoinAddress != "" { - // Extract base URL and add credentials - baseURL := r.config.RQLiteJoinAddress - if strings.HasPrefix(baseURL, "http://") { - // Insert credentials: http://user:pass@host:port - host := strings.TrimPrefix(baseURL, "http://") - return fmt.Sprintf("http://%s:%s@%s", user, pass, host) - } - } - } - - return "" // No credentials found -} - -// maskCredentials masks authentication credentials in URLs for logging -func (r *RQLiteManager) maskCredentials(url string) string { - if strings.Contains(url, "@") { - // URL contains credentials: http://user:pass@host:port - parts := strings.SplitN(url, "@", 2) - if len(parts) == 2 { - protocolAndCreds := parts[0] - hostAndPort := parts[1] - - // Extract protocol - protocolParts := strings.SplitN(protocolAndCreds, "://", 2) - if len(protocolParts) == 2 { - protocol := protocolParts[0] - return fmt.Sprintf("%s://***:***@%s", protocol, hostAndPort) - } - } - } - return url -} - -// stripCredentials removes authentication credentials from URLs for connectivity testing -func (r *RQLiteManager) stripCredentials(url string) string { - if strings.Contains(url, "@") { - // URL contains credentials: http://user:pass@host:port - parts := strings.SplitN(url, "@", 2) - if len(parts) == 2 { - protocolAndCreds := parts[0] - hostAndPort := parts[1] - - // Extract protocol - protocolParts := strings.SplitN(protocolAndCreds, "://", 2) - if len(protocolParts) == 2 { - protocol := protocolParts[0] - return fmt.Sprintf("%s://%s", protocol, hostAndPort) - } - } - } - return url -}