From 7c9851729e9f4a284802a8139db7300e431ca827 Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Sat, 25 Oct 2025 08:19:31 +0300 Subject: [PATCH] chore: update .gitignore and CHANGELOG for new development features - Added .dev/ directory to .gitignore to exclude development process files. - Updated CHANGELOG.md with new entries for the one-command `make dev` target, full stack initialization, and improved configuration management. - Simplified README instructions for generating configuration files and starting the complete network stack. --- .gitignore | 2 + CHANGELOG.md | 23 +++++ Makefile | 145 +++++++++--------------------- README.md | 126 ++++++++++++++------------ cmd/cli/main.go | 225 ++++++++++++++++++++++++++++++++++++++++++++--- pkg/node/node.go | 105 ++++++++++++++-------- 6 files changed, 417 insertions(+), 209 deletions(-) diff --git a/.gitignore b/.gitignore index 4eff86c..01a1c94 100644 --- a/.gitignore +++ b/.gitignore @@ -73,3 +73,5 @@ data/bootstrap/rqlite/ .env.* configs/ + +.dev/ \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index c6f16d3..1e55359 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,14 +8,37 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant ### Added +- One-command `make dev` target to start full development stack (bootstrap + node2 + node3 + gateway in background) +- New `network-cli config init` (no --type) generates complete development stack with all configs and identities +- Full stack initialization with auto-generated peer identities for bootstrap and all nodes +- Explicit control over LibP2P listen addresses for better localhost/development support +- Production/development mode detection for NAT services (disabled for localhost, enabled for production) +- Process management with .dev/pids directory for background process tracking +- Centralized logging to ~/.debros/logs/ for all network services + ### Changed +- Simplified Makefile: removed legacy dev commands, replaced with unified `make dev` target +- Updated README with clearer getting started instructions (single `make dev` command) +- Simplified `network-cli config init` behavior: defaults to generating full stack instead of single node +- `network-cli config init` now handles bootstrap peer discovery and join addresses automatically +- LibP2P configuration: removed always-on NAT services for development environments +- Code formatting in pkg/node/node.go (indentation fixes in bootstrapPeerSource) + ### Deprecated ### Removed +- Removed legacy Makefile targets: run-example, show-bootstrap, run-cli, cli-health, cli-peers, cli-status, cli-storage-test, cli-pubsub-test +- Removed verbose dev-setup, dev-cluster, and old dev workflow targets + ### Fixed +- Fixed indentation in bootstrapPeerSource function for consistency +- Fixed gateway.yaml generation with correct YAML indentation for bootstrap_peers + +### Security + ## [0.51.6] - 2025-10-24 ### Added diff --git a/Makefile b/Makefile index b6d9927..4fa79e3 100644 --- a/Makefile +++ b/Makefile @@ -21,7 +21,7 @@ test-e2e: .PHONY: build clean test run-node run-node2 run-node3 run-example deps tidy fmt vet lint clear-ports -VERSION := 0.51.6-beta +VERSION := 0.51.7-beta COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo unknown) DATE ?= $(shell date -u +%Y-%m-%dT%H:%M:%SZ) LDFLAGS := -X 'main.version=$(VERSION)' -X 'main.commit=$(COMMIT)' -X 'main.date=$(DATE)' @@ -77,110 +77,45 @@ run-gateway: @echo "Generate it with: network-cli config init --type gateway" go run ./cmd/gateway -# Run basic usage example -run-example: - @echo "Running basic usage example..." - go run examples/basic_usage.go - -# Show how to run with flags -show-bootstrap: - @echo "Provide join address via flags, e.g.:" - @echo " make run-node2 JOINADDR=/ip4/127.0.0.1/tcp/5001 HTTP=5002 RAFT=7002 P2P=4002" - -# Run network CLI -run-cli: - @echo "Running network CLI help..." - ./bin/network-cli help - -# Network CLI helper commands -cli-health: - @echo "Checking network health..." - ./bin/network-cli health - -cli-peers: - @echo "Listing network peers..." - ./bin/network-cli peers - -cli-status: - @echo "Getting network status..." - ./bin/network-cli status - -cli-storage-test: - @echo "Testing storage operations..." - @./bin/network-cli storage put test-key "Hello Network" || echo "Storage test requires running network" - @./bin/network-cli storage get test-key || echo "Storage test requires running network" - @./bin/network-cli storage list || echo "Storage test requires running network" - -cli-pubsub-test: - @echo "Testing pub/sub operations..." - @./bin/network-cli pubsub publish test-topic "Hello World" || echo "PubSub test requires running network" - @./bin/network-cli pubsub topics || echo "PubSub test requires running network" - -# Download dependencies -deps: - @echo "Downloading dependencies..." - go mod download - -# Tidy dependencies -tidy: - @echo "Tidying dependencies..." - go mod tidy - -# Format code -fmt: - @echo "Formatting code..." - go fmt ./... - -# Vet code -vet: - @echo "Vetting code..." - go vet ./... - -# Lint alias (lightweight for now) -lint: fmt vet - @echo "Linting complete (fmt + vet)" - -# Clear common development ports -clear-ports: - @echo "Clearing common dev ports (4001/4002, 5001/5002, 7001/7002)..." - @chmod +x scripts/clear-ports.sh || true - @scripts/clear-ports.sh - -# Development setup -dev-setup: deps - @echo "Setting up development environment..." - @mkdir -p data/bootstrap data/node data/node2 data/node3 - @mkdir -p data/test-bootstrap data/test-node1 data/test-node2 - @echo "Development setup complete!" - -# Start development cluster (requires multiple terminals) -dev-cluster: - @echo "To start a development cluster with 3 nodes:" +# One-command dev: Start bootstrap, node2, node3, and gateway in background +# Requires: configs already exist in ~/.debros +dev: build + @echo "🚀 Starting development network stack..." + @mkdir -p .dev/pids + @mkdir -p $$HOME/.debros/logs + @echo "Starting bootstrap node..." + @nohup ./bin/node --config bootstrap.yaml > $$HOME/.debros/logs/bootstrap.log 2>&1 & echo $$! > .dev/pids/bootstrap.pid + @sleep 2 + @echo "Starting node2..." + @nohup ./bin/node --config node2.yaml > $$HOME/.debros/logs/node2.log 2>&1 & echo $$! > .dev/pids/node2.pid + @sleep 1 + @echo "Starting node3..." + @nohup ./bin/node --config node3.yaml > $$HOME/.debros/logs/node3.log 2>&1 & echo $$! > .dev/pids/node3.pid + @sleep 1 + @echo "Starting gateway..." + @nohup ./bin/gateway --config gateway.yaml > $$HOME/.debros/logs/gateway.log 2>&1 & echo $$! > .dev/pids/gateway.pid @echo "" - @echo "1. Generate config files in ~/.debros:" - @echo " make build" - @echo " ./bin/network-cli config init --type bootstrap" - @echo " ./bin/network-cli config init --type node --name node.yaml --bootstrap-peers ''" - @echo " ./bin/network-cli config init --type node --name node2.yaml --bootstrap-peers ''" + @echo "============================================================" + @echo "✅ Development stack started!" + @echo "============================================================" @echo "" - @echo "2. Run in separate terminals:" - @echo " Terminal 1: make run-node # Start bootstrap node (bootstrap.yaml)" - @echo " Terminal 2: make run-node2 # Start node 1 (node.yaml)" - @echo " Terminal 3: make run-node3 # Start node 2 (node2.yaml)" - @echo " Terminal 4: make run-gateway # Start gateway" + @echo "Processes:" + @echo " Bootstrap: PID=$$(cat .dev/pids/bootstrap.pid)" + @echo " Node2: PID=$$(cat .dev/pids/node2.pid)" + @echo " Node3: PID=$$(cat .dev/pids/node3.pid)" + @echo " Gateway: PID=$$(cat .dev/pids/gateway.pid)" @echo "" - @echo "3. Or run custom node with any config file:" - @echo " go run ./cmd/node --config custom-node.yaml" + @echo "Ports:" + @echo " Bootstrap P2P: 4001, HTTP: 5001, Raft: 7001" + @echo " Node2 P2P: 4002, HTTP: 5002, Raft: 7002" + @echo " Node3 P2P: 4003, HTTP: 5003, Raft: 7003" + @echo " Gateway: 6001" @echo "" - @echo "4. Test:" - @echo " make cli-health # Check network health" - @echo " make cli-peers # List peers" - @echo " make cli-storage-test # Test storage" - @echo " make cli-pubsub-test # Test messaging" - -# Full development workflow -dev: clean build test - @echo "Development workflow complete!" + @echo "Press Ctrl+C to stop all processes" + @echo "============================================================" + @echo "" + @trap 'echo "Stopping all processes..."; kill $$(cat .dev/pids/*.pid) 2>/dev/null; rm -f .dev/pids/*.pid; exit 0' INT; \ + tail -f $$HOME/.debros/logs/bootstrap.log $$HOME/.debros/logs/node2.log $$HOME/.debros/logs/node3.log $$HOME/.debros/logs/gateway.log # Help help: @@ -189,12 +124,14 @@ help: @echo " clean - Clean build artifacts" @echo " test - Run tests" @echo "" + @echo "Development:" + @echo " dev - Start full dev stack (bootstrap + 2 nodes + gateway)" + @echo " Requires: configs in ~/.debros (run 'network-cli config init' first)" + @echo "" @echo "Configuration (NEW):" @echo " First, generate config files in ~/.debros with:" @echo " make build # Build CLI first" - @echo " ./bin/network-cli config init --type bootstrap # Generate bootstrap config" - @echo " ./bin/network-cli config init --type node --bootstrap-peers ''" - @echo " ./bin/network-cli config init --type gateway" + @echo " ./bin/network-cli config init # Generate full stack" @echo "" @echo "Network Targets (requires config files in ~/.debros):" @echo " run-node - Start bootstrap node" diff --git a/README.md b/README.md index af7c92f..f0826fd 100644 --- a/README.md +++ b/README.md @@ -174,23 +174,36 @@ cd network make build ``` -### 3. Start a Bootstrap Node +### 3. Generate Configuration Files ```bash -make run-node -# Or manually: -go run ./cmd/node --config configs/node.yaml +# Generate all configs (bootstrap, node2, node3, gateway) with one command +./bin/network-cli config init ``` -### 4. Start Additional Nodes +This creates: +- `~/.debros/bootstrap.yaml` - Bootstrap node +- `~/.debros/node2.yaml` - Regular node 2 +- `~/.debros/node3.yaml` - Regular node 3 +- `~/.debros/gateway.yaml` - HTTP Gateway + +Plus auto-generated identities for each node. + +### 4. Start the Complete Network Stack ```bash -make run-node2 -# Or manually: -go run ./cmd/node --config configs/node.yaml +make dev ``` -### 5. Test with CLI +This starts: +- Bootstrap node (P2P: 4001, RQLite HTTP: 5001, Raft: 7001) +- Node 2 (P2P: 4002, RQLite HTTP: 5002, Raft: 7002) +- Node 3 (P2P: 4003, RQLite HTTP: 5003, Raft: 7003) +- Gateway (HTTP: 6001) + +Logs stream to terminal. Press **Ctrl+C** to stop all processes. + +### 5. Test with CLI (in another terminal) ```bash ./bin/network-cli health @@ -261,100 +274,103 @@ The system will **only** load config from `~/.debros/` and will error if require Use the `network-cli config init` command to generate configuration files: -#### Generate a Node Config +### Generate Complete Stack (Recommended) + +```bash +# Generate bootstrap, node2, node3, and gateway configs in one command +./bin/network-cli config init + +# Force regenerate (overwrites existing configs) +./bin/network-cli config init --force +``` + +This is the **recommended way** to get started with a local development network. + +### Generate Individual Configs (Advanced) + +For custom setups or production deployments, you can generate individual configs: + +#### Generate a Single Node Config ```bash # Generate basic node config with bootstrap peers -network-cli config init --type node --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/QmXxx,/ip4/127.0.0.1/tcp/4002/p2p/QmYyy" +./bin/network-cli config init --type node --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/QmXxx" # With custom ports -network-cli config init --type node --name node2.yaml --listen-port 4002 --rqlite-http-port 5002 --rqlite-raft-port 7002 --join localhost:5001 --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/QmXxx" +./bin/network-cli config init --type node --name node2.yaml \ + --listen-port 4002 --rqlite-http-port 5002 --rqlite-raft-port 7002 \ + --join localhost:5001 --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/QmXxx" # Force overwrite existing config -network-cli config init --type node --force +./bin/network-cli config init --type node --force ``` #### Generate a Bootstrap Node Config ```bash # Generate bootstrap node (no join address required) -network-cli config init --type bootstrap +./bin/network-cli config init --type bootstrap # With custom ports -network-cli config init --type bootstrap --listen-port 4001 --rqlite-http-port 5001 --rqlite-raft-port 7001 +./bin/network-cli config init --type bootstrap --listen-port 4001 --rqlite-http-port 5001 --rqlite-raft-port 7001 ``` #### Generate a Gateway Config ```bash # Generate gateway config -network-cli config init --type gateway +./bin/network-cli config init --type gateway # With bootstrap peers -network-cli config init --type gateway --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/QmXxx" +./bin/network-cli config init --type gateway --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/QmXxx" ``` -### Running Multiple Nodes on the Same Machine +### Running the Network -You can run multiple nodes on a single machine by creating separate configuration files and using the `--config` flag: - -#### Create Multiple Node Configs +Once configs are generated, start the complete stack with: ```bash -# Node 1 -./bin/network-cli config init --type node --name node1.yaml \ - --listen-port 4001 --rqlite-http-port 5001 --rqlite-raft-port 7001 \ - --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/" - -# Node 2 -./bin/network-cli config init --type node --name node2.yaml \ - --listen-port 4002 --rqlite-http-port 5002 --rqlite-raft-port 7002 \ - --join localhost:5001 \ - --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/" - -# Node 3 -./bin/network-cli config init --type node --name node3.yaml \ - --listen-port 4003 --rqlite-http-port 5003 --rqlite-raft-port 7003 \ - --join localhost:5001 \ - --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/" +make dev ``` -#### Run Multiple Nodes in Separate Terminals +Or start individual components (in separate terminals): ```bash # Terminal 1 - Bootstrap node go run ./cmd/node --config bootstrap.yaml -# Terminal 2 - Node 1 -go run ./cmd/node --config node1.yaml - -# Terminal 3 - Node 2 +# Terminal 2 - Node 2 go run ./cmd/node --config node2.yaml -# Terminal 4 - Node 3 +# Terminal 3 - Node 3 go run ./cmd/node --config node3.yaml + +# Terminal 4 - Gateway +go run ./cmd/gateway --config gateway.yaml ``` -#### Or Use Makefile Targets +### Running Multiple Nodes on the Same Machine + +The default `make dev` creates a 3-node setup. For additional nodes, generate individual configs: ```bash -# Terminal 1 -make run-node # Runs: go run ./cmd/node --config bootstrap.yaml +# Generate additional node configs with unique ports +./bin/network-cli config init --type node --name node4.yaml \ + --listen-port 4004 --rqlite-http-port 5004 --rqlite-raft-port 7004 \ + --join localhost:5001 \ + --bootstrap-peers "/ip4/127.0.0.1/tcp/4001/p2p/" -# Terminal 2 -make run-node2 # Runs: go run ./cmd/node --config node.yaml - -# Terminal 3 -make run-node3 # Runs: go run ./cmd/node --config node2.yaml +# Start the additional node +go run ./cmd/node --config node4.yaml ``` #### Key Points for Multiple Nodes - **Each node needs unique ports**: P2P port, RQLite HTTP port, and RQLite Raft port must all be different -- **Join address**: Non-bootstrap nodes need `rqlite_join_address` pointing to the bootstrap or an existing node +- **Join address**: Non-bootstrap nodes need `rqlite_join_address` pointing to the bootstrap or an existing node (use Raft port) - **Bootstrap peers**: All nodes need the bootstrap node's multiaddr in `discovery.bootstrap_peers` - **Config files**: Store all configs in `~/.debros/` with different filenames -- **--config flag**: Specify which config file to load (defaults to `node.yaml`) +- **--config flag**: Specify which config file to load ⚠️ **Common Mistake - Same Ports:** If all nodes use the same ports (e.g., 5001, 7001), they will try to bind to the same addresses and fail to communicate. Verify each node has unique ports: @@ -365,11 +381,11 @@ grep "rqlite_port\|rqlite_raft_port" ~/.debros/bootstrap.yaml # Should show: rqlite_port: 5001, rqlite_raft_port: 7001 # Node 2 -grep "rqlite_port\|rqlite_raft_port" ~/.debros/node.yaml +grep "rqlite_port\|rqlite_raft_port" ~/.debros/node2.yaml # Should show: rqlite_port: 5002, rqlite_raft_port: 7002 # Node 3 -grep "rqlite_port\|rqlite_raft_port" ~/.debros/node2.yaml +grep "rqlite_port\|rqlite_raft_port" ~/.debros/node3.yaml # Should show: rqlite_port: 5003, rqlite_raft_port: 7003 ``` diff --git a/cmd/cli/main.go b/cmd/cli/main.go index eff39f4..a2326a3 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -15,6 +15,7 @@ import ( "github.com/DeBrosOfficial/network/pkg/auth" "github.com/DeBrosOfficial/network/pkg/client" "github.com/DeBrosOfficial/network/pkg/config" + "github.com/DeBrosOfficial/network/pkg/encryption" "github.com/libp2p/go-libp2p/core/crypto" "github.com/libp2p/go-libp2p/core/peer" ) @@ -607,30 +608,35 @@ func showConfigHelp() { fmt.Printf("Config Management Commands\n\n") fmt.Printf("Usage: network-cli config [options]\n\n") fmt.Printf("Subcommands:\n") - fmt.Printf(" init - Generate configuration files in ~/.debros\n") + fmt.Printf(" init - Generate full network stack in ~/.debros (bootstrap + 2 nodes + gateway)\n") fmt.Printf(" validate --name - Validate a config file\n\n") + fmt.Printf("Init Default Behavior (no --type):\n") + fmt.Printf(" Generates bootstrap.yaml, node2.yaml, node3.yaml, gateway.yaml with:\n") + fmt.Printf(" - Auto-generated identities for bootstrap, node2, node3\n") + fmt.Printf(" - Correct bootstrap_peers and join addresses\n") + fmt.Printf(" - Default ports: P2P 4001-4003, HTTP 5001-5003, Raft 7001-7003\n\n") fmt.Printf("Init Options:\n") - fmt.Printf(" --type - Config type: node, bootstrap, gateway (default: node)\n") - fmt.Printf(" --name - Output filename (default: node.yaml)\n") + fmt.Printf(" --type - Single config type: node, bootstrap, gateway (skips stack generation)\n") + fmt.Printf(" --name - Output filename (default: depends on --type or 'stack' for full stack)\n") + fmt.Printf(" --force - Overwrite existing config/stack files\n\n") + fmt.Printf("Single Config Options (with --type):\n") fmt.Printf(" --id - Node ID for bootstrap peers\n") fmt.Printf(" --listen-port - LibP2P listen port (default: 4001)\n") fmt.Printf(" --rqlite-http-port - RQLite HTTP port (default: 5001)\n") fmt.Printf(" --rqlite-raft-port - RQLite Raft port (default: 7001)\n") fmt.Printf(" --join - RQLite address to join (required for non-bootstrap)\n") - fmt.Printf(" --bootstrap-peers - Comma-separated bootstrap peer multiaddrs\n") - fmt.Printf(" --force - Overwrite existing config\n\n") + fmt.Printf(" --bootstrap-peers - Comma-separated bootstrap peer multiaddrs\n\n") fmt.Printf("Examples:\n") - fmt.Printf(" network-cli config init\n") - fmt.Printf(" network-cli config init --type node --bootstrap-peers /ip4/127.0.0.1/tcp/4001/p2p/QmXxx,/ip4/127.0.0.1/tcp/4002/p2p/QmYyy\n") - fmt.Printf(" network-cli config init --type bootstrap\n") - fmt.Printf(" network-cli config init --type gateway\n") + fmt.Printf(" network-cli config init # Generate full stack\n") + fmt.Printf(" network-cli config init --force # Overwrite existing stack\n") + fmt.Printf(" network-cli config init --type bootstrap # Single bootstrap config (legacy)\n") fmt.Printf(" network-cli config validate --name node.yaml\n") } func handleConfigInit(args []string) { // Parse flags var ( - cfgType = "node" + cfgType = "" name = "" // Will be set based on type if not provided id string listenPort = 4001 @@ -694,6 +700,13 @@ func handleConfigInit(args []string) { } } + // If --type is not specified, generate full stack + if cfgType == "" { + initFullStack(force) + return + } + + // Otherwise, continue with single-file generation // Validate type if cfgType != "node" && cfgType != "bootstrap" && cfgType != "gateway" { fmt.Fprintf(os.Stderr, "Invalid --type: %s (expected: node, bootstrap, or gateway)\n", cfgType) @@ -799,6 +812,192 @@ func handleConfigValidate(args []string) { fmt.Printf("✅ Config is valid: %s\n", configPath) } +func initFullStack(force bool) { + fmt.Printf("🚀 Initializing full network stack...\n") + + // Ensure ~/.debros directory exists + homeDir, err := os.UserHomeDir() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to get home directory: %v\n", err) + os.Exit(1) + } + debrosDir := filepath.Join(homeDir, ".debros") + if err := os.MkdirAll(debrosDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Failed to create ~/.debros directory: %v\n", err) + os.Exit(1) + } + + // Step 1: Generate bootstrap identity + bootstrapIdentityDir := filepath.Join(debrosDir, "bootstrap") + bootstrapIdentityPath := filepath.Join(bootstrapIdentityDir, "identity.key") + + if !force { + if _, err := os.Stat(bootstrapIdentityPath); err == nil { + fmt.Fprintf(os.Stderr, "Bootstrap identity already exists at %s (use --force to overwrite)\n", bootstrapIdentityPath) + os.Exit(1) + } + } + + bootstrapInfo, err := encryption.GenerateIdentity() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to generate bootstrap identity: %v\n", err) + os.Exit(1) + } + if err := os.MkdirAll(bootstrapIdentityDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Failed to create bootstrap data directory: %v\n", err) + os.Exit(1) + } + if err := encryption.SaveIdentity(bootstrapInfo, bootstrapIdentityPath); err != nil { + fmt.Fprintf(os.Stderr, "Failed to save bootstrap identity: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated bootstrap identity: %s (Peer ID: %s)\n", bootstrapIdentityPath, bootstrapInfo.PeerID.String()) + + // Construct bootstrap multiaddr + bootstrapMultiaddr := fmt.Sprintf("/ip4/127.0.0.1/tcp/4001/p2p/%s", bootstrapInfo.PeerID.String()) + fmt.Printf(" Bootstrap multiaddr: %s\n", bootstrapMultiaddr) + + // Step 2: Generate bootstrap.yaml + bootstrapName := "bootstrap.yaml" + bootstrapPath := filepath.Join(debrosDir, bootstrapName) + if !force { + if _, err := os.Stat(bootstrapPath); err == nil { + fmt.Fprintf(os.Stderr, "Bootstrap config already exists at %s (use --force to overwrite)\n", bootstrapPath) + os.Exit(1) + } + } + bootstrapContent := generateBootstrapConfig(bootstrapName, "", 4001, 5001, 7001) + if err := os.WriteFile(bootstrapPath, []byte(bootstrapContent), 0644); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write bootstrap config: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated bootstrap config: %s\n", bootstrapPath) + + // Step 3: Generate node2 identity and config + node2IdentityDir := filepath.Join(debrosDir, "node2") + node2IdentityPath := filepath.Join(node2IdentityDir, "identity.key") + + if !force { + if _, err := os.Stat(node2IdentityPath); err == nil { + fmt.Fprintf(os.Stderr, "Node2 identity already exists at %s (use --force to overwrite)\n", node2IdentityPath) + os.Exit(1) + } + } + + node2Info, err := encryption.GenerateIdentity() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to generate node2 identity: %v\n", err) + os.Exit(1) + } + if err := os.MkdirAll(node2IdentityDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Failed to create node2 data directory: %v\n", err) + os.Exit(1) + } + if err := encryption.SaveIdentity(node2Info, node2IdentityPath); err != nil { + fmt.Fprintf(os.Stderr, "Failed to save node2 identity: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated node2 identity: %s (Peer ID: %s)\n", node2IdentityPath, node2Info.PeerID.String()) + + node2Name := "node2.yaml" + node2Path := filepath.Join(debrosDir, node2Name) + if !force { + if _, err := os.Stat(node2Path); err == nil { + fmt.Fprintf(os.Stderr, "Node2 config already exists at %s (use --force to overwrite)\n", node2Path) + os.Exit(1) + } + } + node2Content := generateNodeConfig(node2Name, "", 4002, 5002, 7002, "127.0.0.1:7001", bootstrapMultiaddr) + if err := os.WriteFile(node2Path, []byte(node2Content), 0644); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write node2 config: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated node2 config: %s\n", node2Path) + + // Step 4: Generate node3 identity and config + node3IdentityDir := filepath.Join(debrosDir, "node3") + node3IdentityPath := filepath.Join(node3IdentityDir, "identity.key") + + if !force { + if _, err := os.Stat(node3IdentityPath); err == nil { + fmt.Fprintf(os.Stderr, "Node3 identity already exists at %s (use --force to overwrite)\n", node3IdentityPath) + os.Exit(1) + } + } + + node3Info, err := encryption.GenerateIdentity() + if err != nil { + fmt.Fprintf(os.Stderr, "Failed to generate node3 identity: %v\n", err) + os.Exit(1) + } + if err := os.MkdirAll(node3IdentityDir, 0755); err != nil { + fmt.Fprintf(os.Stderr, "Failed to create node3 data directory: %v\n", err) + os.Exit(1) + } + if err := encryption.SaveIdentity(node3Info, node3IdentityPath); err != nil { + fmt.Fprintf(os.Stderr, "Failed to save node3 identity: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated node3 identity: %s (Peer ID: %s)\n", node3IdentityPath, node3Info.PeerID.String()) + + node3Name := "node3.yaml" + node3Path := filepath.Join(debrosDir, node3Name) + if !force { + if _, err := os.Stat(node3Path); err == nil { + fmt.Fprintf(os.Stderr, "Node3 config already exists at %s (use --force to overwrite)\n", node3Path) + os.Exit(1) + } + } + node3Content := generateNodeConfig(node3Name, "", 4003, 5003, 7003, "127.0.0.1:7001", bootstrapMultiaddr) + if err := os.WriteFile(node3Path, []byte(node3Content), 0644); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write node3 config: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated node3 config: %s\n", node3Path) + + // Step 5: Generate gateway.yaml + gatewayName := "gateway.yaml" + gatewayPath := filepath.Join(debrosDir, gatewayName) + if !force { + if _, err := os.Stat(gatewayPath); err == nil { + fmt.Fprintf(os.Stderr, "Gateway config already exists at %s (use --force to overwrite)\n", gatewayPath) + os.Exit(1) + } + } + gatewayContent := generateGatewayConfig(bootstrapMultiaddr) + if err := os.WriteFile(gatewayPath, []byte(gatewayContent), 0644); err != nil { + fmt.Fprintf(os.Stderr, "Failed to write gateway config: %v\n", err) + os.Exit(1) + } + fmt.Printf("✅ Generated gateway config: %s\n", gatewayPath) + + // Print summary + fmt.Printf("\n" + strings.Repeat("=", 60) + "\n") + fmt.Printf("✅ Full network stack initialized successfully!\n") + fmt.Printf(strings.Repeat("=", 60) + "\n\n") + fmt.Printf("Configuration files created in: %s\n\n", debrosDir) + fmt.Printf("Bootstrap Node:\n") + fmt.Printf(" Config: %s\n", bootstrapPath) + fmt.Printf(" Peer ID: %s\n", bootstrapInfo.PeerID.String()) + fmt.Printf(" Ports: P2P=4001, HTTP=5001, Raft=7001\n\n") + fmt.Printf("Node2:\n") + fmt.Printf(" Config: %s\n", node2Path) + fmt.Printf(" Ports: P2P=4002, HTTP=5002, Raft=7002\n") + fmt.Printf(" Join: 127.0.0.1:7001\n\n") + fmt.Printf("Node3:\n") + fmt.Printf(" Config: %s\n", node3Path) + fmt.Printf(" Ports: P2P=4003, HTTP=5003, Raft=7003\n") + fmt.Printf(" Join: 127.0.0.1:7001\n\n") + fmt.Printf("Gateway:\n") + fmt.Printf(" Config: %s\n\n", gatewayPath) + fmt.Printf("To start the network:\n") + fmt.Printf(" Terminal 1: ./bin/node --config bootstrap.yaml\n") + fmt.Printf(" Terminal 2: ./bin/node --config node2.yaml\n") + fmt.Printf(" Terminal 3: ./bin/node --config node3.yaml\n") + fmt.Printf(" Terminal 4: ./bin/gateway --config gateway.yaml\n") + fmt.Printf("\n" + strings.Repeat("=", 60) + "\n") +} + func generateNodeConfig(name, id string, listenPort, rqliteHTTPPort, rqliteRaftPort int, joinAddr, bootstrapPeers string) string { nodeID := id if nodeID == "" { @@ -923,11 +1122,11 @@ func generateGatewayConfig(bootstrapPeers string) string { var peersYAML strings.Builder if len(peers) == 0 { - peersYAML.WriteString(" bootstrap_peers: []") + peersYAML.WriteString("bootstrap_peers: []") } else { - peersYAML.WriteString(" bootstrap_peers:\n") + peersYAML.WriteString("bootstrap_peers:\n") for _, p := range peers { - fmt.Fprintf(&peersYAML, " - \"%s\"\n", p) + fmt.Fprintf(&peersYAML, " - \"%s\"\n", p) } } diff --git a/pkg/node/node.go b/pkg/node/node.go index 2bf1f02..12327e2 100644 --- a/pkg/node/node.go +++ b/pkg/node/node.go @@ -84,35 +84,35 @@ func (n *Node) startRQLite(ctx context.Context) error { // bootstrapPeerSource returns a PeerSource that yields peers from BootstrapPeers. func bootstrapPeerSource(bootstrapAddrs []string, logger *zap.Logger) func(context.Context, int) <-chan peer.AddrInfo { - return func(ctx context.Context, num int) <-chan peer.AddrInfo { - out := make(chan peer.AddrInfo, num) - go func() { - defer close(out) - count := 0 - for _, s := range bootstrapAddrs { - if count >= num { - return - } - ma, err := multiaddr.NewMultiaddr(s) - if err != nil { - logger.Debug("invalid bootstrap multiaddr", zap.String("addr", s), zap.Error(err)) - continue - } - ai, err := peer.AddrInfoFromP2pAddr(ma) - if err != nil { - logger.Debug("failed to parse bootstrap peer", zap.String("addr", s), zap.Error(err)) - continue - } - select { - case out <- *ai: - count++ - case <-ctx.Done(): - return - } - } - }() - return out - } + return func(ctx context.Context, num int) <-chan peer.AddrInfo { + out := make(chan peer.AddrInfo, num) + go func() { + defer close(out) + count := 0 + for _, s := range bootstrapAddrs { + if count >= num { + return + } + ma, err := multiaddr.NewMultiaddr(s) + if err != nil { + logger.Debug("invalid bootstrap multiaddr", zap.String("addr", s), zap.Error(err)) + continue + } + ai, err := peer.AddrInfoFromP2pAddr(ma) + if err != nil { + logger.Debug("failed to parse bootstrap peer", zap.String("addr", s), zap.Error(err)) + continue + } + select { + case out <- *ai: + count++ + case <-ctx.Done(): + return + } + } + }() + return out + } } // hasBootstrapConnections checks if we're connected to any bootstrap peers @@ -250,21 +250,52 @@ func (n *Node) startLibP2P() error { return fmt.Errorf("failed to load identity: %w", err) } - // Create LibP2P host with NAT traversal support + // Create LibP2P host with explicit listen addresses var opts []libp2p.Option opts = append(opts, libp2p.Identity(identity), libp2p.Security(noise.ID, noise.New), libp2p.DefaultMuxers, - libp2p.EnableNATService(), - libp2p.EnableAutoNATv2(), - libp2p.EnableRelay(), - libp2p.NATPortMap(), - libp2p.EnableAutoRelayWithPeerSource( - bootstrapPeerSource(n.config.Discovery.BootstrapPeers, n.logger.Logger), - ), ) + // Add explicit listen addresses from config + if len(n.config.Node.ListenAddresses) > 0 { + listenAddrs := make([]multiaddr.Multiaddr, 0, len(n.config.Node.ListenAddresses)) + for _, addr := range n.config.Node.ListenAddresses { + ma, err := multiaddr.NewMultiaddr(addr) + if err != nil { + return fmt.Errorf("invalid listen address %s: %w", addr, err) + } + listenAddrs = append(listenAddrs, ma) + } + opts = append(opts, libp2p.ListenAddrs(listenAddrs...)) + n.logger.ComponentInfo(logging.ComponentLibP2P, "Configured listen addresses", + zap.Strings("addrs", n.config.Node.ListenAddresses)) + } + + // For localhost/development, disable NAT services + // For production, these would be enabled + isLocalhost := len(n.config.Node.ListenAddresses) > 0 && + (strings.Contains(n.config.Node.ListenAddresses[0], "127.0.0.1") || + strings.Contains(n.config.Node.ListenAddresses[0], "localhost")) + + if isLocalhost { + n.logger.ComponentInfo(logging.ComponentLibP2P, "Localhost detected - disabling NAT services for local development") + // Don't add NAT/AutoRelay options for localhost + } else { + // Production: enable NAT traversal + n.logger.ComponentInfo(logging.ComponentLibP2P, "Production mode - enabling NAT services") + opts = append(opts, + libp2p.EnableNATService(), + libp2p.EnableAutoNATv2(), + libp2p.EnableRelay(), + libp2p.NATPortMap(), + libp2p.EnableAutoRelayWithPeerSource( + bootstrapPeerSource(n.config.Discovery.BootstrapPeers, n.logger.Logger), + ), + ) + } + h, err := libp2p.New(opts...) if err != nil { return err