From a59d0f1fd6d2173a897276071c86f2300ff1d2f2 Mon Sep 17 00:00:00 2001 From: anonpenguin Date: Sat, 9 Aug 2025 08:19:18 +0300 Subject: [PATCH] feat: add configurable RQLite host advertising modes (auto/localhost/ip) --- Makefile | 10 +++++----- clear-ports.sh | 2 +- cmd/node/main.go | 7 +++++-- pkg/config/config.go | 2 ++ pkg/database/rqlite.go | 37 +++++++++++++++++++++++++------------ 5 files changed, 38 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index a5ad0da..0719e4a 100644 --- a/Makefile +++ b/Makefile @@ -26,26 +26,26 @@ test: # Run bootstrap node explicitly run-node: @echo "Starting BOOTSTRAP node (role=bootstrap)..." - go run cmd/node/main.go -role bootstrap -data ./data/bootstrap + go run cmd/node/main.go -role bootstrap -data ./data/bootstrap -advertise localhost # Run second node (regular) - requires BOOTSTRAP multiaddr # Usage: make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ HTTP=5002 RAFT=7002 run-node2: @echo "Starting REGULAR node2 (role=node)..." @if [ -z "$(BOOTSTRAP)" ]; then echo "ERROR: Provide BOOTSTRAP multiaddr: make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ [HTTP=5002 RAFT=7002]"; exit 1; fi - go run cmd/node/main.go -role node -id node2 -data ./data/node2 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5002} -rqlite-raft-port $${RAFT:-7002} + go run cmd/node/main.go -role node -id node2 -data ./data/node2 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5002} -rqlite-raft-port $${RAFT:-7002} -advertise $${ADVERTISE:-localhost} # Run third node (regular) - requires BOOTSTRAP multiaddr # Usage: make run-node3 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ HTTP=5003 RAFT=7003 run-node3: @echo "Starting REGULAR node3 (role=node)..." @if [ -z "$(BOOTSTRAP)" ]; then echo "ERROR: Provide BOOTSTRAP multiaddr: make run-node3 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ [HTTP=5003 RAFT=7003]"; exit 1; fi - go run cmd/node/main.go -role node -id node3 -data ./data/node3 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5003} -rqlite-raft-port $${RAFT:-7003} + go run cmd/node/main.go -role node -id node3 -data ./data/node3 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5003} -rqlite-raft-port $${RAFT:-7003} -advertise $${ADVERTISE:-localhost} # Show how to run with flags show-bootstrap: - @echo "Provide bootstrap via flags, e.g.:" - @echo " make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ HTTP=5002 RAFT=7002" + @echo "Provide bootstrap via flags, e.g.:" + @echo " make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ HTTP=5002 RAFT=7002" # Run network CLI run-cli: diff --git a/clear-ports.sh b/clear-ports.sh index f246c23..7869e03 100755 --- a/clear-ports.sh +++ b/clear-ports.sh @@ -10,7 +10,7 @@ set -euo pipefail # Collect ports from args or use defaults PORTS=("$@") if [ ${#PORTS[@]} -eq 0 ]; then - PORTS=(4001 5001 7001) + PORTS=(4001 5001 7001 7002) fi echo "Gracefully terminating listeners on: ${PORTS[*]}" diff --git a/cmd/node/main.go b/cmd/node/main.go index d9c6d28..e6dc5ac 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -27,6 +27,7 @@ func main() { role = flag.String("role", "auto", "Node role: auto|bootstrap|node (auto detects based on config)") rqlHTTP = flag.Int("rqlite-http-port", 5001, "RQLite HTTP API port") rqlRaft = flag.Int("rqlite-raft-port", 7001, "RQLite Raft port") + advertise = flag.String("advertise", "auto", "Advertise mode: auto|localhost|ip") help = flag.Bool("help", false, "Show help") ) flag.Parse() @@ -96,6 +97,8 @@ func main() { // RQLite ports (overridable for local multi-node on one host) cfg.Database.RQLitePort = *rqlHTTP cfg.Database.RQLiteRaftPort = *rqlRaft + cfg.Database.AdvertiseMode = strings.ToLower(*advertise) + logger.Printf("RQLite advertise mode: %s", cfg.Database.AdvertiseMode) if isBootstrap { // Check if this is the primary bootstrap node (first in list) or secondary @@ -131,8 +134,8 @@ func main() { // Extract IP from bootstrap peer for RQLite bootstrapHost := parseHostFromMultiaddr(*bootstrap) if bootstrapHost != "" { - // If user provided localhost for libp2p, translate to this host's primary IP so rqlite can join the correct process. - if bootstrapHost == "127.0.0.1" || strings.EqualFold(bootstrapHost, "localhost") { + // Only translate localhost to external IP when not explicitly in localhost advertise mode + if (bootstrapHost == "127.0.0.1" || strings.EqualFold(bootstrapHost, "localhost")) && cfg.Database.AdvertiseMode != "localhost" { if extIP, err := getPreferredLocalIP(); err == nil && extIP != "" { logger.Printf("Translating localhost bootstrap to external IP %s for RQLite join", extIP) bootstrapHost = extIP diff --git a/pkg/config/config.go b/pkg/config/config.go index 19cc9e3..c91c914 100644 --- a/pkg/config/config.go +++ b/pkg/config/config.go @@ -39,6 +39,7 @@ type DatabaseConfig struct { RQLitePort int `yaml:"rqlite_port"` // RQLite HTTP API port RQLiteRaftPort int `yaml:"rqlite_raft_port"` // RQLite Raft consensus port RQLiteJoinAddress string `yaml:"rqlite_join_address"` // Address to join RQLite cluster + AdvertiseMode string `yaml:"advertise_mode"` // Advertise mode: "auto" (default), "localhost", or "ip" } // DiscoveryConfig contains peer discovery configuration @@ -124,6 +125,7 @@ func DefaultConfig() *Config { RQLitePort: 5001, RQLiteRaftPort: 7001, RQLiteJoinAddress: "", // Empty for bootstrap node + AdvertiseMode: "auto", }, Discovery: DiscoveryConfig{ BootstrapPeers: []string{}, diff --git a/pkg/database/rqlite.go b/pkg/database/rqlite.go index 53458ee..74715f7 100644 --- a/pkg/database/rqlite.go +++ b/pkg/database/rqlite.go @@ -43,13 +43,28 @@ 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" + // Determine advertise host based on configuration + advertiseHost := "127.0.0.1" // default + mode := strings.ToLower(r.config.AdvertiseMode) + switch mode { + case "localhost": + advertiseHost = "127.0.0.1" + r.logger.Info("Using localhost for RQLite advertising (dev mode)") + case "ip": + if ip, err := r.getExternalIP(); err == nil && ip != "" { + advertiseHost = ip + r.logger.Info("Using external IP for RQLite advertising (forced)", zap.String("ip", ip)) + } else { + r.logger.Warn("Failed to get external IP, falling back to localhost", zap.Error(err)) + } + default: // auto + if ip, err := r.getExternalIP(); err == nil && ip != "" { + advertiseHost = ip + r.logger.Info("Using external IP for RQLite advertising (auto)", zap.String("ip", ip)) + } else { + r.logger.Info("No external IP found, using localhost for RQLite advertising (auto)") + } } - r.logger.Info("Using external IP for RQLite advertising", zap.String("ip", externalIP)) // Build RQLite command args := []string{ @@ -58,11 +73,9 @@ func (r *RQLiteManager) Start(ctx context.Context) error { // Auth disabled for testing } - // 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)) - } + // Always set advertised addresses explicitly to avoid 0.0.0.0 announcements + args = append(args, "-http-adv-addr", fmt.Sprintf("%s:%d", advertiseHost, r.config.RQLitePort)) + args = append(args, "-raft-adv-addr", fmt.Sprintf("%s:%d", advertiseHost, r.config.RQLiteRaftPort)) // Add join address if specified (for non-bootstrap or secondary bootstrap nodes) if r.config.RQLiteJoinAddress != "" { @@ -99,7 +112,7 @@ 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.String("advertise_host", advertiseHost), zap.Strings("full_args", args), )