feat: add configurable RQLite host advertising modes (auto/localhost/ip)

This commit is contained in:
anonpenguin 2025-08-09 08:19:18 +03:00
parent 05798471dd
commit a59d0f1fd6
5 changed files with 38 additions and 20 deletions

View File

@ -26,26 +26,26 @@ test:
# Run bootstrap node explicitly # Run bootstrap node explicitly
run-node: run-node:
@echo "Starting BOOTSTRAP node (role=bootstrap)..." @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 # Run second node (regular) - requires BOOTSTRAP multiaddr
# Usage: make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<ID> HTTP=5002 RAFT=7002 # Usage: make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<ID> HTTP=5002 RAFT=7002
run-node2: run-node2:
@echo "Starting REGULAR node2 (role=node)..." @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/<ID> [HTTP=5002 RAFT=7002]"; exit 1; fi @if [ -z "$(BOOTSTRAP)" ]; then echo "ERROR: Provide BOOTSTRAP multiaddr: make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<ID> [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 # Run third node (regular) - requires BOOTSTRAP multiaddr
# Usage: make run-node3 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<ID> HTTP=5003 RAFT=7003 # Usage: make run-node3 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<ID> HTTP=5003 RAFT=7003
run-node3: run-node3:
@echo "Starting REGULAR node3 (role=node)..." @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/<ID> [HTTP=5003 RAFT=7003]"; exit 1; fi @if [ -z "$(BOOTSTRAP)" ]; then echo "ERROR: Provide BOOTSTRAP multiaddr: make run-node3 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<ID> [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 how to run with flags
show-bootstrap: show-bootstrap:
@echo "Provide bootstrap via flags, e.g.:" @echo "Provide bootstrap via flags, e.g.:"
@echo " make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<PEER_ID> HTTP=5002 RAFT=7002" @echo " make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/<PEER_ID> HTTP=5002 RAFT=7002"
# Run network CLI # Run network CLI
run-cli: run-cli:

View File

@ -10,7 +10,7 @@ set -euo pipefail
# Collect ports from args or use defaults # Collect ports from args or use defaults
PORTS=("$@") PORTS=("$@")
if [ ${#PORTS[@]} -eq 0 ]; then if [ ${#PORTS[@]} -eq 0 ]; then
PORTS=(4001 5001 7001) PORTS=(4001 5001 7001 7002)
fi fi
echo "Gracefully terminating listeners on: ${PORTS[*]}" echo "Gracefully terminating listeners on: ${PORTS[*]}"

View File

@ -27,6 +27,7 @@ func main() {
role = flag.String("role", "auto", "Node role: auto|bootstrap|node (auto detects based on config)") 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") rqlHTTP = flag.Int("rqlite-http-port", 5001, "RQLite HTTP API port")
rqlRaft = flag.Int("rqlite-raft-port", 7001, "RQLite Raft 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") help = flag.Bool("help", false, "Show help")
) )
flag.Parse() flag.Parse()
@ -96,6 +97,8 @@ func main() {
// RQLite ports (overridable for local multi-node on one host) // RQLite ports (overridable for local multi-node on one host)
cfg.Database.RQLitePort = *rqlHTTP cfg.Database.RQLitePort = *rqlHTTP
cfg.Database.RQLiteRaftPort = *rqlRaft cfg.Database.RQLiteRaftPort = *rqlRaft
cfg.Database.AdvertiseMode = strings.ToLower(*advertise)
logger.Printf("RQLite advertise mode: %s", cfg.Database.AdvertiseMode)
if isBootstrap { if isBootstrap {
// Check if this is the primary bootstrap node (first in list) or secondary // 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 // Extract IP from bootstrap peer for RQLite
bootstrapHost := parseHostFromMultiaddr(*bootstrap) bootstrapHost := parseHostFromMultiaddr(*bootstrap)
if bootstrapHost != "" { if bootstrapHost != "" {
// If user provided localhost for libp2p, translate to this host's primary IP so rqlite can join the correct process. // Only translate localhost to external IP when not explicitly in localhost advertise mode
if bootstrapHost == "127.0.0.1" || strings.EqualFold(bootstrapHost, "localhost") { if (bootstrapHost == "127.0.0.1" || strings.EqualFold(bootstrapHost, "localhost")) && cfg.Database.AdvertiseMode != "localhost" {
if extIP, err := getPreferredLocalIP(); err == nil && extIP != "" { if extIP, err := getPreferredLocalIP(); err == nil && extIP != "" {
logger.Printf("Translating localhost bootstrap to external IP %s for RQLite join", extIP) logger.Printf("Translating localhost bootstrap to external IP %s for RQLite join", extIP)
bootstrapHost = extIP bootstrapHost = extIP

View File

@ -39,6 +39,7 @@ type DatabaseConfig struct {
RQLitePort int `yaml:"rqlite_port"` // RQLite HTTP API port RQLitePort int `yaml:"rqlite_port"` // RQLite HTTP API port
RQLiteRaftPort int `yaml:"rqlite_raft_port"` // RQLite Raft consensus port RQLiteRaftPort int `yaml:"rqlite_raft_port"` // RQLite Raft consensus port
RQLiteJoinAddress string `yaml:"rqlite_join_address"` // Address to join RQLite cluster 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 // DiscoveryConfig contains peer discovery configuration
@ -124,6 +125,7 @@ func DefaultConfig() *Config {
RQLitePort: 5001, RQLitePort: 5001,
RQLiteRaftPort: 7001, RQLiteRaftPort: 7001,
RQLiteJoinAddress: "", // Empty for bootstrap node RQLiteJoinAddress: "", // Empty for bootstrap node
AdvertiseMode: "auto",
}, },
Discovery: DiscoveryConfig{ Discovery: DiscoveryConfig{
BootstrapPeers: []string{}, BootstrapPeers: []string{},

View File

@ -43,13 +43,28 @@ func (r *RQLiteManager) Start(ctx context.Context) error {
return fmt.Errorf("failed to create RQLite data directory: %w", err) return fmt.Errorf("failed to create RQLite data directory: %w", err)
} }
// Get the external IP address for advertising // Determine advertise host based on configuration
externalIP, err := r.getExternalIP() advertiseHost := "127.0.0.1" // default
if err != nil { mode := strings.ToLower(r.config.AdvertiseMode)
r.logger.Warn("Failed to get external IP, using localhost", zap.Error(err)) switch mode {
externalIP = "localhost" 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 // Build RQLite command
args := []string{ args := []string{
@ -58,11 +73,9 @@ func (r *RQLiteManager) Start(ctx context.Context) error {
// Auth disabled for testing // Auth disabled for testing
} }
// Add advertised addresses if we have an external IP // Always set advertised addresses explicitly to avoid 0.0.0.0 announcements
if externalIP != "localhost" { args = append(args, "-http-adv-addr", fmt.Sprintf("%s:%d", advertiseHost, r.config.RQLitePort))
args = append(args, "-http-adv-addr", fmt.Sprintf("%s:%d", externalIP, r.config.RQLitePort)) args = append(args, "-raft-adv-addr", fmt.Sprintf("%s:%d", advertiseHost, r.config.RQLiteRaftPort))
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) // Add join address if specified (for non-bootstrap or secondary bootstrap nodes)
if r.config.RQLiteJoinAddress != "" { 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("http_port", r.config.RQLitePort),
zap.Int("raft_port", r.config.RQLiteRaftPort), zap.Int("raft_port", r.config.RQLiteRaftPort),
zap.String("join_address", r.config.RQLiteJoinAddress), zap.String("join_address", r.config.RQLiteJoinAddress),
zap.String("external_ip", externalIP), zap.String("advertise_host", advertiseHost),
zap.Strings("full_args", args), zap.Strings("full_args", args),
) )