diff --git a/Makefile b/Makefile index d01320a..1811288 100644 --- a/Makefile +++ b/Makefile @@ -26,21 +26,21 @@ test: # Run bootstrap node explicitly run-node: @echo "Starting BOOTSTRAP node (role=bootstrap)..." - go run ./cmd/node -role bootstrap -data ./data/bootstrap -advertise localhost -p2p-port $${P2P:-4001} + go run ./cmd/node -role bootstrap -data ./data/bootstrap -advertise localhost -p2p-port $${P2P:-4001} -dev-local # Run second node (regular) - requires BOOTSTRAP multiaddr # Usage: make run-node2 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ HTTP=5002 RAFT=7002 P2P=4002 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 P2P=4002]"; exit 1; fi - go run ./cmd/node -role node -id node2 -data ./data/node2 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5002} -rqlite-raft-port $${RAFT:-7002} -p2p-port $${P2P:-4002} -advertise $${ADVERTISE:-localhost} + go run ./cmd/node -role node -id node2 -data ./data/node2 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5002} -rqlite-raft-port $${RAFT:-7002} -p2p-port $${P2P:-4002} -advertise $${ADVERTISE:-localhost} -dev-local # Run third node (regular) - requires BOOTSTRAP multiaddr # Usage: make run-node3 BOOTSTRAP=/ip4/127.0.0.1/tcp/4001/p2p/ HTTP=5003 RAFT=7003 P2P=4003 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 P2P=4003]"; exit 1; fi - go run ./cmd/node -role node -id node3 -data ./data/node3 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5003} -rqlite-raft-port $${RAFT:-7003} -p2p-port $${P2P:-4003} -advertise $${ADVERTISE:-localhost} + go run ./cmd/node -role node -id node3 -data ./data/node3 -bootstrap $(BOOTSTRAP) -rqlite-http-port $${HTTP:-5003} -rqlite-raft-port $${RAFT:-7003} -p2p-port $${P2P:-4003} -advertise $${ADVERTISE:-localhost} -dev-local # Run basic usage example run-example: diff --git a/README.md b/README.md index 181be71..5abdb52 100644 --- a/README.md +++ b/README.md @@ -187,7 +187,7 @@ make build ```bash # Start an explicit bootstrap node (LibP2P 4001, RQLite 5001/7001) -go run ./cmd/node -role bootstrap -data ./data/bootstrap +go run ./cmd/node -role bootstrap -data ./data/bootstrap -dev-local ``` **Terminal 2 - Regular Node:** @@ -200,7 +200,8 @@ go run ./cmd/node \ -data ./data/node2 \ -bootstrap /ip4/127.0.0.1/tcp/4001/p2p/ \ -rqlite-http-port 5002 \ - -rqlite-raft-port 7002 + -rqlite-raft-port 7002 \ + -dev-local ``` **Terminal 3 - Another Node (optional):** @@ -421,6 +422,7 @@ For more advanced configuration options and development setup, see the sections - **Bootstrap node**: `-role bootstrap` - **Regular node**: `-role node -bootstrap ` +- **Development localhost defaults**: `-dev-local` (sets `NETWORK_DEV_LOCAL=1` in-process); use this for local-only testing so the library returns localhost DB endpoints and bootstrap peers. - **RQLite ports**: `-rqlite-http-port` (default 5001), `-rqlite-raft-port` (default 7001) Examples are shown in Quick Start above for local multi-node on a single machine. diff --git a/cmd/node/configmap.go b/cmd/node/configmap.go index 2f2b454..dab9e5f 100644 --- a/cmd/node/configmap.go +++ b/cmd/node/configmap.go @@ -2,9 +2,11 @@ package main import ( "fmt" + "os" "strings" "git.debros.io/DeBros/network/pkg/config" + "git.debros.io/DeBros/network/pkg/client" "git.debros.io/DeBros/network/pkg/constants" "git.debros.io/DeBros/network/pkg/logging" ) @@ -21,6 +23,17 @@ type NodeFlagValues struct { Advertise string } +// isTruthyEnv returns true if the environment variable is set to a common truthy value +func isTruthyEnv(key string) bool { + v := strings.ToLower(strings.TrimSpace(os.Getenv(key))) + switch v { + case "1", "true", "yes", "on": + return true + default: + return false + } +} + // MapFlagsAndEnvToConfig applies environment overrides and CLI flags to cfg. // Precedence: flags > env > defaults. Behavior mirrors previous inline logic in main.go. // Returns the derived RQLite Raft join address for non-bootstrap nodes (empty for bootstrap nodes). @@ -28,6 +41,9 @@ func MapFlagsAndEnvToConfig(cfg *config.Config, fv NodeFlagValues, isBootstrap b // Apply environment variable overrides first so that flags can override them after config.ApplyEnvOverrides(cfg) + // Detect dev-local mode (set via -dev-local -> NETWORK_DEV_LOCAL=1) + devLocal := isTruthyEnv("NETWORK_DEV_LOCAL") + // Determine data directory if not provided if fv.DataDir == "" { if isBootstrap { @@ -56,6 +72,14 @@ func MapFlagsAndEnvToConfig(cfg *config.Config, fv NodeFlagValues, isBootstrap b // Bootstrap-specific vs regular-node logic if isBootstrap { + if devLocal { + // In dev-local, run a primary bootstrap locally + cfg.Database.RQLiteJoinAddress = "" + // Also prefer localhost bootstrap peers for any consumers reading cfg + cfg.Discovery.BootstrapPeers = client.DefaultBootstrapPeers() + logger.Printf("Dev-local: Primary bootstrap node - localhost defaults enabled") + return "" + } bootstrapPeers := constants.GetBootstrapPeers() isSecondaryBootstrap := false if len(bootstrapPeers) > 1 { @@ -103,6 +127,11 @@ func MapFlagsAndEnvToConfig(cfg *config.Config, fv NodeFlagValues, isBootstrap b logger.Printf("Using command line bootstrap peer: %s", fv.Bootstrap) } else { bootstrapPeers := cfg.Discovery.BootstrapPeers + if devLocal { + // Force localhost bootstrap for development + bootstrapPeers = client.DefaultBootstrapPeers() + logger.Printf("Dev-local: overriding bootstrap peers to %v", bootstrapPeers) + } if len(bootstrapPeers) == 0 { bootstrapPeers = constants.GetBootstrapPeers() } diff --git a/cmd/node/main.go b/cmd/node/main.go index e5bd9f4..5b0466b 100644 --- a/cmd/node/main.go +++ b/cmd/node/main.go @@ -29,6 +29,7 @@ func main() { 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") + devLocal = flag.Bool("dev-local", false, "Enable development localhost defaults for the client library (sets NETWORK_DEV_LOCAL=1)") help = flag.Bool("help", false, "Show help") ) flag.Parse() @@ -38,6 +39,12 @@ func main() { return } + // Enable development localhost defaults for the client library if requested + if *devLocal { + os.Setenv("NETWORK_DEV_LOCAL", "1") + log.Printf("Development local mode enabled (NETWORK_DEV_LOCAL=1)") + } + // Determine node role var isBootstrap bool switch strings.ToLower(*role) { diff --git a/pkg/client/defaults.go b/pkg/client/defaults.go index 4c814f0..09ad302 100644 --- a/pkg/client/defaults.go +++ b/pkg/client/defaults.go @@ -2,6 +2,7 @@ package client import ( "os" + "path/filepath" "strconv" "strings" @@ -16,6 +17,10 @@ func DefaultBootstrapPeers() []string { if ma := os.Getenv("LOCAL_BOOTSTRAP_MULTIADDR"); ma != "" { return []string{ma} } + // Try to auto-resolve local bootstrap peer multiaddr from peer.info + if ma, ok := readLocalPeerInfoMultiaddr(); ok { + return []string{ma} + } // Fallback to localhost transport without peer ID (connect will warn and skip) return []string{"/ip4/127.0.0.1/tcp/4001"} } @@ -84,27 +89,27 @@ func MapAddrsToDBEndpoints(addrs []multiaddr.Multiaddr, dbPort int) []string { } func endpointFromMultiaddr(ma multiaddr.Multiaddr, port int) string { - var host string - // Prefer DNS if present, then IP - if v, err := ma.ValueForProtocol(multiaddr.P_DNS); err == nil && v != "" { - host = v - } - if host == "" { - if v, err := ma.ValueForProtocol(multiaddr.P_DNS4); err == nil && v != "" { host = v } - } - if host == "" { - if v, err := ma.ValueForProtocol(multiaddr.P_DNS6); err == nil && v != "" { host = v } - } - if host == "" { - if v, err := ma.ValueForProtocol(multiaddr.P_IP4); err == nil && v != "" { host = v } - } - if host == "" { - if v, err := ma.ValueForProtocol(multiaddr.P_IP6); err == nil && v != "" { host = v } - } - if host == "" { - host = "localhost" - } - return "http://" + host + ":" + strconv.Itoa(port) + var host string + // Prefer DNS if present, then IP + if v, err := ma.ValueForProtocol(multiaddr.P_DNS); err == nil && v != "" { + host = v + } + if host == "" { + if v, err := ma.ValueForProtocol(multiaddr.P_DNS4); err == nil && v != "" { host = v } + } + if host == "" { + if v, err := ma.ValueForProtocol(multiaddr.P_DNS6); err == nil && v != "" { host = v } + } + if host == "" { + if v, err := ma.ValueForProtocol(multiaddr.P_IP4); err == nil && v != "" { host = v } + } + if host == "" { + if v, err := ma.ValueForProtocol(multiaddr.P_IP6); err == nil && v != "" { host = v } + } + if host == "" { + host = "localhost" + } + return "http://" + host + ":" + strconv.Itoa(port) } func dedupeStrings(in []string) []string { @@ -123,3 +128,32 @@ func dedupeStrings(in []string) []string { } return out } + +// readLocalPeerInfoMultiaddr attempts to read the local bootstrap peer multiaddr from common dev paths. +// It checks LOCAL_BOOTSTRAP_INFO (path), then ./data/bootstrap/peer.info, then ./data/peer.info. +func readLocalPeerInfoMultiaddr() (string, bool) { + candidates := make([]string, 0, 3) + if p := strings.TrimSpace(os.Getenv("LOCAL_BOOTSTRAP_INFO")); p != "" { + candidates = append(candidates, p) + } + candidates = append(candidates, + filepath.Join(".", "data", "bootstrap", "peer.info"), + filepath.Join(".", "data", "peer.info"), + ) + + for _, p := range candidates { + b, err := os.ReadFile(p) + if err != nil { + continue + } + s := strings.TrimSpace(string(b)) + if s == "" { + continue + } + // expect a full multiaddr with /p2p/ + if strings.Contains(s, "/p2p/") { + return s, true + } + } + return "", false +}