diff --git a/CHANGELOG.md b/CHANGELOG.md index 8de2ddc..c56bb54 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,22 @@ The format is based on [Keep a Changelog][keepachangelog] and adheres to [Semant ### Deprecated ### Fixed +## [0.60.0] - 2025-11-09 + +### Added +- Introduced comprehensive `network-cli dev` commands for managing the local development environment (start, stop, status, logs). +- Added `network-cli prod` commands for streamlined production installation, upgrade, and service management on Linux systems (requires root). + +### Changed +- Refactored `Makefile` targets (`dev` and `kill`) to use the new `network-cli dev up` and `network-cli dev down` commands, significantly simplifying the development workflow. +- Removed deprecated `network-cli config`, `network-cli setup`, `network-cli service`, and `network-cli rqlite` commands, consolidating functionality under `dev` and `prod`. + +### Deprecated + +### Removed + +### Fixed +\n ## [0.59.2] - 2025-11-08 ### Added diff --git a/Makefile b/Makefile index de2e029..6ee06dc 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 install-hooks kill -VERSION := 0.59.2 +VERSION := 0.60.0 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)' @@ -82,336 +82,14 @@ run-gateway: @echo "Generate it with: network-cli config init --type gateway" go run ./cmd/gateway -# One-command dev: Start bootstrap, node2, node3, gateway, and anon in background -# Requires: configs already exist in ~/.debros +# Development environment target +# Uses network-cli dev up to start full stack with dependency and port checking dev: build - @echo "๐Ÿš€ Starting development network stack..." - @mkdir -p .dev/pids - @mkdir -p $$HOME/.debros/logs - @echo "Starting Anyone client (anon proxy)..." - @if [ "$$(uname)" = "Darwin" ]; then \ - echo " Detected macOS - using npx anyone-client"; \ - if command -v npx >/dev/null 2>&1; then \ - nohup npx anyone-client > $$HOME/.debros/logs/anon.log 2>&1 & echo $$! > .dev/pids/anon.pid; \ - echo " Anyone client started (PID: $$(cat .dev/pids/anon.pid))"; \ - else \ - echo " โš ๏ธ npx not found - skipping Anyone client"; \ - echo " Install with: npm install -g npm"; \ - fi; \ - elif [ "$$(uname)" = "Linux" ]; then \ - echo " Detected Linux - checking systemctl"; \ - if systemctl is-active --quiet anon 2>/dev/null; then \ - echo " โœ“ Anon service already running"; \ - elif command -v systemctl >/dev/null 2>&1; then \ - echo " Starting anon service..."; \ - sudo systemctl start anon 2>/dev/null || echo " โš ๏ธ Failed to start anon service"; \ - else \ - echo " โš ๏ธ systemctl not found - skipping Anon"; \ - fi; \ - fi - @echo "Initializing IPFS and Cluster for all nodes..." - @if command -v ipfs >/dev/null 2>&1 && command -v ipfs-cluster-service >/dev/null 2>&1; then \ - CLUSTER_SECRET=$$HOME/.debros/cluster-secret; \ - if [ ! -f $$CLUSTER_SECRET ]; then \ - echo " Generating shared cluster secret..."; \ - ipfs-cluster-service --version >/dev/null 2>&1 && openssl rand -hex 32 > $$CLUSTER_SECRET || echo "0000000000000000000000000000000000000000000000000000000000000000" > $$CLUSTER_SECRET; \ - fi; \ - SECRET=$$(cat $$CLUSTER_SECRET); \ - SWARM_KEY=$$HOME/.debros/swarm.key; \ - if [ ! -f $$SWARM_KEY ]; then \ - echo " Generating private swarm key..."; \ - KEY_HEX=$$(openssl rand -hex 32 | tr '[:lower:]' '[:upper:]'); \ - printf "/key/swarm/psk/1.0.0/\n/base16/\n%s\n" "$$KEY_HEX" > $$SWARM_KEY; \ - chmod 600 $$SWARM_KEY; \ - fi; \ - echo " Setting up bootstrap node (IPFS: 5001, Cluster: 9094)..."; \ - if [ ! -d $$HOME/.debros/bootstrap/ipfs/repo ]; then \ - echo " Initializing IPFS..."; \ - mkdir -p $$HOME/.debros/bootstrap/ipfs; \ - IPFS_PATH=$$HOME/.debros/bootstrap/ipfs/repo ipfs init --profile=server 2>&1 | grep -v "generating" | grep -v "peer identity" || true; \ - cp $$SWARM_KEY $$HOME/.debros/bootstrap/ipfs/repo/swarm.key; \ - IPFS_PATH=$$HOME/.debros/bootstrap/ipfs/repo ipfs config --json Addresses.API '["/ip4/127.0.0.1/tcp/5001"]' 2>&1 | grep -v "generating" || true; \ - IPFS_PATH=$$HOME/.debros/bootstrap/ipfs/repo ipfs config --json Addresses.Gateway '["/ip4/127.0.0.1/tcp/8080"]' 2>&1 | grep -v "generating" || true; \ - IPFS_PATH=$$HOME/.debros/bootstrap/ipfs/repo ipfs config --json Addresses.Swarm '["/ip4/0.0.0.0/tcp/4101","/ip6/::/tcp/4101"]' 2>&1 | grep -v "generating" || true; \ - else \ - if [ ! -f $$HOME/.debros/bootstrap/ipfs/repo/swarm.key ]; then \ - cp $$SWARM_KEY $$HOME/.debros/bootstrap/ipfs/repo/swarm.key; \ - fi; \ - fi; \ - echo " Creating IPFS Cluster directories (config will be managed by Go code)..."; \ - mkdir -p $$HOME/.debros/bootstrap/ipfs-cluster; \ - echo " Setting up node2 (IPFS: 5002, Cluster: 9104)..."; \ - if [ ! -d $$HOME/.debros/node2/ipfs/repo ]; then \ - echo " Initializing IPFS..."; \ - mkdir -p $$HOME/.debros/node2/ipfs; \ - IPFS_PATH=$$HOME/.debros/node2/ipfs/repo ipfs init --profile=server 2>&1 | grep -v "generating" | grep -v "peer identity" || true; \ - cp $$SWARM_KEY $$HOME/.debros/node2/ipfs/repo/swarm.key; \ - IPFS_PATH=$$HOME/.debros/node2/ipfs/repo ipfs config --json Addresses.API '["/ip4/127.0.0.1/tcp/5002"]' 2>&1 | grep -v "generating" || true; \ - IPFS_PATH=$$HOME/.debros/node2/ipfs/repo ipfs config --json Addresses.Gateway '["/ip4/127.0.0.1/tcp/8081"]' 2>&1 | grep -v "generating" || true; \ - IPFS_PATH=$$HOME/.debros/node2/ipfs/repo ipfs config --json Addresses.Swarm '["/ip4/0.0.0.0/tcp/4102","/ip6/::/tcp/4102"]' 2>&1 | grep -v "generating" || true; \ - else \ - if [ ! -f $$HOME/.debros/node2/ipfs/repo/swarm.key ]; then \ - cp $$SWARM_KEY $$HOME/.debros/node2/ipfs/repo/swarm.key; \ - fi; \ - fi; \ - echo " Creating IPFS Cluster directories (config will be managed by Go code)..."; \ - mkdir -p $$HOME/.debros/node2/ipfs-cluster; \ - echo " Setting up node3 (IPFS: 5003, Cluster: 9114)..."; \ - if [ ! -d $$HOME/.debros/node3/ipfs/repo ]; then \ - echo " Initializing IPFS..."; \ - mkdir -p $$HOME/.debros/node3/ipfs; \ - IPFS_PATH=$$HOME/.debros/node3/ipfs/repo ipfs init --profile=server 2>&1 | grep -v "generating" | grep -v "peer identity" || true; \ - cp $$SWARM_KEY $$HOME/.debros/node3/ipfs/repo/swarm.key; \ - IPFS_PATH=$$HOME/.debros/node3/ipfs/repo ipfs config --json Addresses.API '["/ip4/127.0.0.1/tcp/5003"]' 2>&1 | grep -v "generating" || true; \ - IPFS_PATH=$$HOME/.debros/node3/ipfs/repo ipfs config --json Addresses.Gateway '["/ip4/127.0.0.1/tcp/8082"]' 2>&1 | grep -v "generating" || true; \ - IPFS_PATH=$$HOME/.debros/node3/ipfs/repo ipfs config --json Addresses.Swarm '["/ip4/0.0.0.0/tcp/4103","/ip6/::/tcp/4103"]' 2>&1 | grep -v "generating" || true; \ - else \ - if [ ! -f $$HOME/.debros/node3/ipfs/repo/swarm.key ]; then \ - cp $$SWARM_KEY $$HOME/.debros/node3/ipfs/repo/swarm.key; \ - fi; \ - fi; \ - echo " Creating IPFS Cluster directories (config will be managed by Go code)..."; \ - mkdir -p $$HOME/.debros/node3/ipfs-cluster; \ - echo "Starting IPFS daemons..."; \ - if [ ! -f .dev/pids/ipfs-bootstrap.pid ] || ! kill -0 $$(cat .dev/pids/ipfs-bootstrap.pid) 2>/dev/null; then \ - IPFS_PATH=$$HOME/.debros/bootstrap/ipfs/repo nohup ipfs daemon --enable-pubsub-experiment > $$HOME/.debros/logs/ipfs-bootstrap.log 2>&1 & echo $$! > .dev/pids/ipfs-bootstrap.pid; \ - echo " Bootstrap IPFS started (PID: $$(cat .dev/pids/ipfs-bootstrap.pid), API: 5001)"; \ - sleep 3; \ - else \ - echo " โœ“ Bootstrap IPFS already running"; \ - fi; \ - if [ ! -f .dev/pids/ipfs-node2.pid ] || ! kill -0 $$(cat .dev/pids/ipfs-node2.pid) 2>/dev/null; then \ - IPFS_PATH=$$HOME/.debros/node2/ipfs/repo nohup ipfs daemon --enable-pubsub-experiment > $$HOME/.debros/logs/ipfs-node2.log 2>&1 & echo $$! > .dev/pids/ipfs-node2.pid; \ - echo " Node2 IPFS started (PID: $$(cat .dev/pids/ipfs-node2.pid), API: 5002)"; \ - sleep 3; \ - else \ - echo " โœ“ Node2 IPFS already running"; \ - fi; \ - if [ ! -f .dev/pids/ipfs-node3.pid ] || ! kill -0 $$(cat .dev/pids/ipfs-node3.pid) 2>/dev/null; then \ - IPFS_PATH=$$HOME/.debros/node3/ipfs/repo nohup ipfs daemon --enable-pubsub-experiment > $$HOME/.debros/logs/ipfs-node3.log 2>&1 & echo $$! > .dev/pids/ipfs-node3.pid; \ - echo " Node3 IPFS started (PID: $$(cat .dev/pids/ipfs-node3.pid), API: 5003)"; \ - sleep 3; \ - else \ - echo " โœ“ Node3 IPFS already running"; \ - fi; \ - else \ - echo " โš ๏ธ ipfs or ipfs-cluster-service not found - skipping IPFS setup"; \ - echo " Install with: https://docs.ipfs.tech/install/ and https://ipfscluster.io/documentation/guides/install/"; \ - fi - @sleep 2 - @echo "Starting bootstrap node..." - @nohup ./bin/node --config bootstrap.yaml > $$HOME/.debros/logs/bootstrap.log 2>&1 & echo $$! > .dev/pids/bootstrap.pid - @sleep 3 - @echo "Starting node2..." - @nohup ./bin/node --config node2.yaml > $$HOME/.debros/logs/node2.log 2>&1 & echo $$! > .dev/pids/node2.pid - @sleep 2 - @echo "Starting node3..." - @nohup ./bin/node --config node3.yaml > $$HOME/.debros/logs/node3.log 2>&1 & echo $$! > .dev/pids/node3.pid - @sleep 3 - @echo "Starting IPFS Cluster daemons (after Go nodes have configured them)..." - @if command -v ipfs-cluster-service >/dev/null 2>&1; then \ - if [ ! -f .dev/pids/ipfs-cluster-bootstrap.pid ] || ! kill -0 $$(cat .dev/pids/ipfs-cluster-bootstrap.pid) 2>/dev/null; then \ - if [ -f $$HOME/.debros/bootstrap/ipfs-cluster/service.json ]; then \ - env IPFS_CLUSTER_PATH=$$HOME/.debros/bootstrap/ipfs-cluster nohup ipfs-cluster-service daemon > $$HOME/.debros/logs/ipfs-cluster-bootstrap.log 2>&1 & echo $$! > .dev/pids/ipfs-cluster-bootstrap.pid; \ - echo " Bootstrap Cluster started (PID: $$(cat .dev/pids/ipfs-cluster-bootstrap.pid), API: 9094)"; \ - echo " Waiting for bootstrap cluster to be ready..."; \ - for i in $$(seq 1 30); do \ - if curl -s http://localhost:9094/peers >/dev/null 2>&1; then \ - break; \ - fi; \ - sleep 1; \ - done; \ - sleep 2; \ - else \ - echo " โš ๏ธ Bootstrap cluster config not ready yet"; \ - fi; \ - else \ - echo " โœ“ Bootstrap Cluster already running"; \ - fi; \ - if [ ! -f .dev/pids/ipfs-cluster-node2.pid ] || ! kill -0 $$(cat .dev/pids/ipfs-cluster-node2.pid) 2>/dev/null; then \ - if [ -f $$HOME/.debros/node2/ipfs-cluster/service.json ]; then \ - env IPFS_CLUSTER_PATH=$$HOME/.debros/node2/ipfs-cluster nohup ipfs-cluster-service daemon > $$HOME/.debros/logs/ipfs-cluster-node2.log 2>&1 & echo $$! > .dev/pids/ipfs-cluster-node2.pid; \ - echo " Node2 Cluster started (PID: $$(cat .dev/pids/ipfs-cluster-node2.pid), API: 9104)"; \ - sleep 3; \ - else \ - echo " โš ๏ธ Node2 cluster config not ready yet"; \ - fi; \ - else \ - echo " โœ“ Node2 Cluster already running"; \ - fi; \ - if [ ! -f .dev/pids/ipfs-cluster-node3.pid ] || ! kill -0 $$(cat .dev/pids/ipfs-cluster-node3.pid) 2>/dev/null; then \ - if [ -f $$HOME/.debros/node3/ipfs-cluster/service.json ]; then \ - env IPFS_CLUSTER_PATH=$$HOME/.debros/node3/ipfs-cluster nohup ipfs-cluster-service daemon > $$HOME/.debros/logs/ipfs-cluster-node3.log 2>&1 & echo $$! > .dev/pids/ipfs-cluster-node3.pid; \ - echo " Node3 Cluster started (PID: $$(cat .dev/pids/ipfs-cluster-node3.pid), API: 9114)"; \ - sleep 3; \ - else \ - echo " โš ๏ธ Node3 cluster config not ready yet"; \ - fi; \ - else \ - echo " โœ“ Node3 Cluster already running"; \ - fi; \ - else \ - echo " โš ๏ธ ipfs-cluster-service not found - skipping cluster daemon startup"; \ - fi - @sleep 1 - @echo "Starting Olric cache server..." - @if command -v olric-server >/dev/null 2>&1; then \ - if [ ! -f $$HOME/.debros/olric-config.yaml ]; then \ - echo " Creating Olric config..."; \ - mkdir -p $$HOME/.debros; \ - fi; \ - if ! pgrep -f "olric-server" >/dev/null 2>&1; then \ - OLRIC_SERVER_CONFIG=$$HOME/.debros/olric-config.yaml nohup olric-server > $$HOME/.debros/logs/olric.log 2>&1 & echo $$! > .dev/pids/olric.pid; \ - echo " Olric cache server started (PID: $$(cat .dev/pids/olric.pid))"; \ - sleep 3; \ - else \ - echo " โœ“ Olric cache server already running"; \ - fi; \ - else \ - echo " โš ๏ธ olric-server command not found - skipping Olric (cache endpoints will be disabled)"; \ - echo " Install with: go install github.com/olric-data/olric/cmd/olric-server@v0.7.0"; \ - fi - @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 "============================================================" - @echo "โœ… Development stack started!" - @echo "============================================================" - @echo "" - @echo "Processes:" - @if [ -f .dev/pids/anon.pid ]; then \ - echo " Anon: PID=$$(cat .dev/pids/anon.pid) (SOCKS: 9050)"; \ - fi - @if [ -f .dev/pids/ipfs-bootstrap.pid ]; then \ - echo " Bootstrap IPFS: PID=$$(cat .dev/pids/ipfs-bootstrap.pid) (API: 5001)"; \ - fi - @if [ -f .dev/pids/ipfs-node2.pid ]; then \ - echo " Node2 IPFS: PID=$$(cat .dev/pids/ipfs-node2.pid) (API: 5002)"; \ - fi - @if [ -f .dev/pids/ipfs-node3.pid ]; then \ - echo " Node3 IPFS: PID=$$(cat .dev/pids/ipfs-node3.pid) (API: 5003)"; \ - fi - @if [ -f .dev/pids/ipfs-cluster-bootstrap.pid ]; then \ - echo " Bootstrap Cluster: PID=$$(cat .dev/pids/ipfs-cluster-bootstrap.pid) (API: 9094)"; \ - fi - @if [ -f .dev/pids/ipfs-cluster-node2.pid ]; then \ - echo " Node2 Cluster: PID=$$(cat .dev/pids/ipfs-cluster-node2.pid) (API: 9104)"; \ - fi - @if [ -f .dev/pids/ipfs-cluster-node3.pid ]; then \ - echo " Node3 Cluster: PID=$$(cat .dev/pids/ipfs-cluster-node3.pid) (API: 9114)"; \ - fi - @if [ -f .dev/pids/olric.pid ]; then \ - echo " Olric: PID=$$(cat .dev/pids/olric.pid) (API: 3320)"; \ - fi - @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 "Ports:" - @echo " Anon SOCKS: 9050 (proxy endpoint: POST /v1/proxy/anon)" - @if [ -f .dev/pids/ipfs-bootstrap.pid ]; then \ - echo " Bootstrap IPFS API: 5001"; \ - echo " Node2 IPFS API: 5002"; \ - echo " Node3 IPFS API: 5003"; \ - echo " Bootstrap Cluster: 9094 (pin management)"; \ - echo " Node2 Cluster: 9104 (pin management)"; \ - echo " Node3 Cluster: 9114 (pin management)"; \ - fi - @if [ -f .dev/pids/olric.pid ]; then \ - echo " Olric: 3320 (cache API)"; \ - fi - @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 "Press Ctrl+C to stop all processes" - @echo "============================================================" - @echo "" - @LOGS="$$HOME/.debros/logs/bootstrap.log $$HOME/.debros/logs/node2.log $$HOME/.debros/logs/node3.log $$HOME/.debros/logs/gateway.log"; \ - if [ -f .dev/pids/anon.pid ]; then \ - LOGS="$$LOGS $$HOME/.debros/logs/anon.log"; \ - fi; \ - if [ -f .dev/pids/ipfs-bootstrap.pid ]; then \ - LOGS="$$LOGS $$HOME/.debros/logs/ipfs-bootstrap.log $$HOME/.debros/logs/ipfs-node2.log $$HOME/.debros/logs/ipfs-node3.log"; \ - fi; \ - if [ -f .dev/pids/ipfs-cluster-bootstrap.pid ]; then \ - LOGS="$$LOGS $$HOME/.debros/logs/ipfs-cluster-bootstrap.log $$HOME/.debros/logs/ipfs-cluster-node2.log $$HOME/.debros/logs/ipfs-cluster-node3.log"; \ - fi; \ - if [ -f .dev/pids/olric.pid ]; then \ - LOGS="$$LOGS $$HOME/.debros/logs/olric.log"; \ - fi; \ - trap 'echo "Stopping all processes..."; kill $$(cat .dev/pids/*.pid) 2>/dev/null; rm -f .dev/pids/*.pid; exit 0' INT; \ - tail -f $$LOGS + @./bin/network-cli dev up -# Kill all processes +# Kill all processes using network-cli dev down kill: - @echo "๐Ÿ›‘ Stopping all DeBros network services..." - @echo "" - @echo "Stopping DeBros nodes and gateway..." - @if [ -f .dev/pids/gateway.pid ]; then \ - kill -TERM $$(cat .dev/pids/gateway.pid) 2>/dev/null && echo " โœ“ Gateway stopped" || echo " โœ— Gateway not running"; \ - rm -f .dev/pids/gateway.pid; \ - fi - @if [ -f .dev/pids/bootstrap.pid ]; then \ - kill -TERM $$(cat .dev/pids/bootstrap.pid) 2>/dev/null && echo " โœ“ Bootstrap node stopped" || echo " โœ— Bootstrap not running"; \ - rm -f .dev/pids/bootstrap.pid; \ - fi - @if [ -f .dev/pids/node2.pid ]; then \ - kill -TERM $$(cat .dev/pids/node2.pid) 2>/dev/null && echo " โœ“ Node2 stopped" || echo " โœ— Node2 not running"; \ - rm -f .dev/pids/node2.pid; \ - fi - @if [ -f .dev/pids/node3.pid ]; then \ - kill -TERM $$(cat .dev/pids/node3.pid) 2>/dev/null && echo " โœ“ Node3 stopped" || echo " โœ— Node3 not running"; \ - rm -f .dev/pids/node3.pid; \ - fi - @echo "" - @echo "Stopping IPFS Cluster peers..." - @if [ -f .dev/pids/ipfs-cluster-bootstrap.pid ]; then \ - kill -TERM $$(cat .dev/pids/ipfs-cluster-bootstrap.pid) 2>/dev/null && echo " โœ“ Bootstrap Cluster stopped" || echo " โœ— Bootstrap Cluster not running"; \ - rm -f .dev/pids/ipfs-cluster-bootstrap.pid; \ - fi - @if [ -f .dev/pids/ipfs-cluster-node2.pid ]; then \ - kill -TERM $$(cat .dev/pids/ipfs-cluster-node2.pid) 2>/dev/null && echo " โœ“ Node2 Cluster stopped" || echo " โœ— Node2 Cluster not running"; \ - rm -f .dev/pids/ipfs-cluster-node2.pid; \ - fi - @if [ -f .dev/pids/ipfs-cluster-node3.pid ]; then \ - kill -TERM $$(cat .dev/pids/ipfs-cluster-node3.pid) 2>/dev/null && echo " โœ“ Node3 Cluster stopped" || echo " โœ— Node3 Cluster not running"; \ - rm -f .dev/pids/ipfs-cluster-node3.pid; \ - fi - @echo "" - @echo "Stopping IPFS daemons..." - @if [ -f .dev/pids/ipfs-bootstrap.pid ]; then \ - kill -TERM $$(cat .dev/pids/ipfs-bootstrap.pid) 2>/dev/null && echo " โœ“ Bootstrap IPFS stopped" || echo " โœ— Bootstrap IPFS not running"; \ - rm -f .dev/pids/ipfs-bootstrap.pid; \ - fi - @if [ -f .dev/pids/ipfs-node2.pid ]; then \ - kill -TERM $$(cat .dev/pids/ipfs-node2.pid) 2>/dev/null && echo " โœ“ Node2 IPFS stopped" || echo " โœ— Node2 IPFS not running"; \ - rm -f .dev/pids/ipfs-node2.pid; \ - fi - @if [ -f .dev/pids/ipfs-node3.pid ]; then \ - kill -TERM $$(cat .dev/pids/ipfs-node3.pid) 2>/dev/null && echo " โœ“ Node3 IPFS stopped" || echo " โœ— Node3 IPFS not running"; \ - rm -f .dev/pids/ipfs-node3.pid; \ - fi - @echo "" - @echo "Stopping Olric cache..." - @if [ -f .dev/pids/olric.pid ]; then \ - kill -TERM $$(cat .dev/pids/olric.pid) 2>/dev/null && echo " โœ“ Olric stopped" || echo " โœ— Olric not running"; \ - rm -f .dev/pids/olric.pid; \ - fi - @echo "" - @echo "Stopping Anon proxy..." - @if [ -f .dev/pids/anyone.pid ]; then \ - kill -TERM $$(cat .dev/pids/anyone.pid) 2>/dev/null && echo " โœ“ Anon proxy stopped" || echo " โœ— Anon proxy not running"; \ - rm -f .dev/pids/anyone.pid; \ - fi - @echo "" - @echo "Cleaning up any remaining processes on ports..." - @lsof -ti:7001,7002,7003,5001,5002,5003,6001,4001,4002,4003,9050,3320,3322,9094,9095,9096,9097,9104,9105,9106,9107,9114,9115,9116,9117,8080,8081,8082 2>/dev/null | xargs kill -9 2>/dev/null && echo " โœ“ Cleaned up remaining port bindings" || echo " โœ“ No lingering processes found" - @echo "" - @echo "โœ… All services stopped!" + @./bin/network-cli dev down # Help help: @@ -420,42 +98,25 @@ 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 "Local Development (Recommended):" + @echo " make dev - Start full development stack with one command" + @echo " - Checks dependencies and available ports" + @echo " - Generates configs (bootstrap + node2 + node3 + gateway)" + @echo " - Starts IPFS, RQLite, Olric, nodes, and gateway" + @echo " - Validates cluster health (IPFS peers, RQLite, LibP2P)" + @echo " - Stops all services if health checks fail" + @echo " - Includes comprehensive logging" + @echo " make kill - Stop all development services" @echo "" - @echo "Configuration (NEW):" - @echo " First, generate config files in ~/.debros with:" - @echo " make build # Build CLI first" - @echo " ./bin/network-cli config init # Generate full stack" + @echo "Development Management (via network-cli):" + @echo " ./bin/network-cli dev status - Show status of all dev services" + @echo " ./bin/network-cli dev logs [--follow]" @echo "" - @echo "Network Targets (requires config files in ~/.debros):" - @echo " run-node - Start bootstrap node" - @echo " run-node2 - Start second node" - @echo " run-node3 - Start third node" - @echo " run-gateway - Start HTTP gateway" - @echo " run-example - Run usage example" - @echo "" - @echo "Running Multiple Nodes:" - @echo " Nodes use --config flag to select which YAML file in ~/.debros to load:" - @echo " go run ./cmd/node --config bootstrap.yaml" - @echo " go run ./cmd/node --config node.yaml" - @echo " go run ./cmd/node --config node2.yaml" - @echo " Generate configs with: ./bin/network-cli config init --name " - @echo "" - @echo "CLI Commands:" - @echo " run-cli - Run network CLI help" - @echo " cli-health - Check network health" - @echo " cli-peers - List network peers" - @echo " cli-status - Get network status" - @echo " cli-storage-test - Test storage operations" - @echo " cli-pubsub-test - Test pub/sub operations" - @echo "" - @echo "Development:" - @echo " test-multinode - Full multi-node test with 1 bootstrap + 2 nodes" - @echo " test-peer-discovery - Test peer discovery (requires running nodes)" - @echo " test-replication - Test data replication (requires running nodes)" - @echo " test-consensus - Test database consensus (requires running nodes)" + @echo "Individual Node Targets (advanced):" + @echo " run-node - Start bootstrap node directly" + @echo " run-node2 - Start second node directly" + @echo " run-node3 - Start third node directly" + @echo " run-gateway - Start HTTP gateway directly" @echo "" @echo "Maintenance:" @echo " deps - Download dependencies" @@ -463,9 +124,4 @@ help: @echo " fmt - Format code" @echo " vet - Vet code" @echo " lint - Lint code (fmt + vet)" - @echo " clear-ports - Clear common dev ports" - @echo " kill - Stop all running services (nodes, IPFS, cluster, gateway, olric)" - @echo " dev-setup - Setup development environment" - @echo " dev-cluster - Show cluster startup commands" - @echo " dev - Full development workflow" @echo " help - Show this help" diff --git a/README.md b/README.md index dd2e561..451141e 100644 --- a/README.md +++ b/README.md @@ -43,7 +43,7 @@ DeBros Network is a decentralized peer-to-peer data platform built in Go. It com make dev ``` - This starts three nodes and the HTTP gateway. Stop with `Ctrl+C`. + This starts three nodes and the HTTP gateway. **The command will not complete successfully until all services pass health checks** (IPFS peer connectivity, RQLite cluster formation, and LibP2P connectivity). If health checks fail, all services are stopped automatically. Stop with `Ctrl+C`. 4. Validate the network from another terminal: diff --git a/cmd/cli/main.go b/cmd/cli/main.go index e396013..c9db844 100644 --- a/cmd/cli/main.go +++ b/cmd/cli/main.go @@ -64,20 +64,18 @@ func main() { os.Exit(1) } - // Setup and service commands - case "setup": - cli.HandleSetupCommand(args) - case "service": - cli.HandleServiceCommand(args) + // Development environment commands + case "dev": + cli.HandleDevCommand(args) + + // Production environment commands + case "prod": + cli.HandleProdCommand(args) // Authentication commands case "auth": cli.HandleAuthCommand(args) - // Config commands - case "config": - cli.HandleConfigCommand(args) - // Basic network commands case "health": cli.HandleHealthCommand(format, timeout) @@ -108,10 +106,6 @@ func main() { } cli.HandleConnectCommand(args[0], timeout) - // RQLite commands - case "rqlite": - cli.HandleRQLiteCommand(args) - // Help case "help", "--help", "-h": showHelp() @@ -151,13 +145,18 @@ func showHelp() { fmt.Printf(" devnet enable - Shorthand for switching to devnet\n") fmt.Printf(" testnet enable - Shorthand for switching to testnet\n\n") - fmt.Printf("๐Ÿš€ Setup & Services:\n") - fmt.Printf(" setup [--force] - Interactive VPS setup (Linux only, requires root)\n") - fmt.Printf(" service start - Start service (node, gateway, all)\n") - fmt.Printf(" service stop - Stop service\n") - fmt.Printf(" service restart - Restart service\n") - fmt.Printf(" service status [target] - Show service status\n") - fmt.Printf(" service logs [opts] - View service logs (--follow, --since=1h)\n\n") + fmt.Printf("๐Ÿ’ป Local Development:\n") + fmt.Printf(" dev up - Start full local dev environment\n") + fmt.Printf(" dev down - Stop all dev services\n") + fmt.Printf(" dev status - Show status of dev services\n") + fmt.Printf(" dev logs - View dev component logs\n\n") + + fmt.Printf("๐Ÿš€ Production Deployment:\n") + fmt.Printf(" prod install [--bootstrap] - Full production bootstrap (requires root)\n") + fmt.Printf(" prod upgrade - Upgrade existing installation\n") + fmt.Printf(" prod status - Show production service status\n") + fmt.Printf(" prod logs - View production service logs\n") + fmt.Printf(" prod uninstall - Remove production services (preserves data)\n\n") fmt.Printf("๐Ÿ” Authentication:\n") fmt.Printf(" auth login - Authenticate with wallet\n") @@ -165,10 +164,6 @@ func showHelp() { fmt.Printf(" auth whoami - Show current authentication\n") fmt.Printf(" auth status - Show detailed auth info\n\n") - fmt.Printf("โš™๏ธ Configuration:\n") - fmt.Printf(" config init [--type ] - Generate configs (full stack or single)\n") - fmt.Printf(" config validate --name - Validate config file\n\n") - fmt.Printf("๐ŸŒ Network Commands:\n") fmt.Printf(" health - Check network health\n") fmt.Printf(" peers - List connected peers\n") @@ -179,9 +174,6 @@ func showHelp() { fmt.Printf("๐Ÿ—„๏ธ Database:\n") fmt.Printf(" query ๐Ÿ” Execute database query\n\n") - fmt.Printf("๐Ÿ”ง RQLite:\n") - fmt.Printf(" rqlite fix ๐Ÿ”ง Fix misconfigured join address and clean raft state\n\n") - fmt.Printf("๐Ÿ“ก PubSub:\n") fmt.Printf(" pubsub publish ๐Ÿ” Publish message\n") fmt.Printf(" pubsub subscribe ๐Ÿ” Subscribe to topic\n") diff --git a/pkg/cli/config_commands.go b/pkg/cli/config_commands.go deleted file mode 100644 index 6f5ea4b..0000000 --- a/pkg/cli/config_commands.go +++ /dev/null @@ -1,552 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "path/filepath" - "strconv" - "strings" - "time" - - "github.com/DeBrosOfficial/network/pkg/config" - "github.com/DeBrosOfficial/network/pkg/encryption" -) - -// HandleConfigCommand handles config management commands -func HandleConfigCommand(args []string) { - if len(args) == 0 { - showConfigHelp() - return - } - - subcommand := args[0] - subargs := args[1:] - - switch subcommand { - case "init": - handleConfigInit(subargs) - case "validate": - handleConfigValidate(subargs) - case "help": - showConfigHelp() - default: - fmt.Fprintf(os.Stderr, "Unknown config subcommand: %s\n", subcommand) - showConfigHelp() - os.Exit(1) - } -} - -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 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 - 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\n") - fmt.Printf("Examples:\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 = "" - name = "" // Will be set based on type if not provided - id string - listenPort = 4001 - rqliteHTTPPort = 5001 - rqliteRaftPort = 7001 - joinAddr string - bootstrapPeers string - force bool - ) - - for i := 0; i < len(args); i++ { - switch args[i] { - case "--type": - if i+1 < len(args) { - cfgType = args[i+1] - i++ - } - case "--name": - if i+1 < len(args) { - name = args[i+1] - i++ - } - case "--id": - if i+1 < len(args) { - id = args[i+1] - i++ - } - case "--listen-port": - if i+1 < len(args) { - if p, err := strconv.Atoi(args[i+1]); err == nil { - listenPort = p - } - i++ - } - case "--rqlite-http-port": - if i+1 < len(args) { - if p, err := strconv.Atoi(args[i+1]); err == nil { - rqliteHTTPPort = p - } - i++ - } - case "--rqlite-raft-port": - if i+1 < len(args) { - if p, err := strconv.Atoi(args[i+1]); err == nil { - rqliteRaftPort = p - } - i++ - } - case "--join": - if i+1 < len(args) { - joinAddr = args[i+1] - i++ - } - case "--bootstrap-peers": - if i+1 < len(args) { - bootstrapPeers = args[i+1] - i++ - } - case "--force": - force = true - } - } - - // 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) - os.Exit(1) - } - - // Set default name based on type if not provided - if name == "" { - switch cfgType { - case "bootstrap": - name = "bootstrap.yaml" - case "gateway": - name = "gateway.yaml" - default: - name = "node.yaml" - } - } - - // Ensure config directory exists - configDir, err := config.EnsureConfigDir() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to ensure config directory: %v\n", err) - os.Exit(1) - } - - configPath := filepath.Join(configDir, name) - - // Check if file exists - if !force { - if _, err := os.Stat(configPath); err == nil { - fmt.Fprintf(os.Stderr, "Config file already exists at %s (use --force to overwrite)\n", configPath) - os.Exit(1) - } - } - - // Generate config based on type - var configContent string - switch cfgType { - case "node": - configContent = GenerateNodeConfig(name, id, listenPort, rqliteHTTPPort, rqliteRaftPort, joinAddr, bootstrapPeers) - case "bootstrap": - configContent = GenerateBootstrapConfig(name, id, listenPort, rqliteHTTPPort, rqliteRaftPort) - case "gateway": - configContent = GenerateGatewayConfig(bootstrapPeers) - } - - // Write config file - if err := os.WriteFile(configPath, []byte(configContent), 0644); err != nil { - fmt.Fprintf(os.Stderr, "Failed to write config file: %v\n", err) - os.Exit(1) - } - - fmt.Printf("โœ… Configuration file created: %s\n", configPath) - fmt.Printf(" Type: %s\n", cfgType) - fmt.Printf("\nYou can now start the %s using the generated config.\n", cfgType) -} - -func handleConfigValidate(args []string) { - var name string - for i := 0; i < len(args); i++ { - if args[i] == "--name" && i+1 < len(args) { - name = args[i+1] - i++ - } - } - - if name == "" { - fmt.Fprintf(os.Stderr, "Missing --name flag\n") - showConfigHelp() - os.Exit(1) - } - - configDir, err := config.ConfigDir() - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to get config directory: %v\n", err) - os.Exit(1) - } - - configPath := filepath.Join(configDir, name) - file, err := os.Open(configPath) - if err != nil { - fmt.Fprintf(os.Stderr, "Failed to open config file: %v\n", err) - os.Exit(1) - } - defer file.Close() - - var cfg config.Config - if err := config.DecodeStrict(file, &cfg); err != nil { - fmt.Fprintf(os.Stderr, "Failed to parse config: %v\n", err) - os.Exit(1) - } - - // Run validation - errs := cfg.Validate() - if len(errs) > 0 { - fmt.Fprintf(os.Stderr, "\nโŒ Configuration errors (%d):\n", len(errs)) - for _, err := range errs { - fmt.Fprintf(os.Stderr, " - %s\n", err) - } - os.Exit(1) - } - - 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) - - // Generate configs for all nodes... - // (rest of the implementation - similar to what was in main.go) - // I'll keep it similar to the original for consistency - - // 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.yaml - 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, "localhost:5001", 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.yaml - 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, "localhost:5001", 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) - - fmt.Printf("\n" + strings.Repeat("=", 60) + "\n") - fmt.Printf("โœ… Full network stack initialized successfully!\n") - fmt.Printf(strings.Repeat("=", 60) + "\n") - fmt.Printf("\nBootstrap Peer ID: %s\n", bootstrapInfo.PeerID.String()) - fmt.Printf("Bootstrap Multiaddr: %s\n", bootstrapMultiaddr) - fmt.Printf("\nGenerated configs:\n") - fmt.Printf(" - %s\n", bootstrapPath) - fmt.Printf(" - %s\n", node2Path) - fmt.Printf(" - %s\n", node3Path) - fmt.Printf(" - %s\n", gatewayPath) - fmt.Printf("\nStart the network with: make dev\n") -} - -// GenerateNodeConfig generates a node configuration -func GenerateNodeConfig(name, id string, listenPort, rqliteHTTPPort, rqliteRaftPort int, joinAddr, bootstrapPeers string) string { - nodeID := id - if nodeID == "" { - nodeID = fmt.Sprintf("node-%d", time.Now().Unix()) - } - - // Parse bootstrap peers - var peers []string - if bootstrapPeers != "" { - for _, p := range strings.Split(bootstrapPeers, ",") { - if p = strings.TrimSpace(p); p != "" { - peers = append(peers, p) - } - } - } - - // Construct data_dir from name stem (remove .yaml) - dataDir := strings.TrimSuffix(name, ".yaml") - dataDir = filepath.Join(os.ExpandEnv("~"), ".debros", dataDir) - - var peersYAML strings.Builder - if len(peers) == 0 { - peersYAML.WriteString(" bootstrap_peers: []") - } else { - peersYAML.WriteString(" bootstrap_peers:\n") - for _, p := range peers { - fmt.Fprintf(&peersYAML, " - \"%s\"\n", p) - } - } - - if joinAddr == "" { - joinAddr = "localhost:5001" - } - - // Calculate IPFS cluster API port (9094 for bootstrap, 9104+ for nodes) - // Pattern: Bootstrap (5001) -> 9094, Node2 (5002) -> 9104, Node3 (5003) -> 9114 - clusterAPIPort := 9094 + (rqliteHTTPPort-5001)*10 - - return fmt.Sprintf(`node: - id: "%s" - type: "node" - listen_addresses: - - "/ip4/0.0.0.0/tcp/%d" - data_dir: "%s" - max_connections: 50 - -database: - data_dir: "%s/rqlite" - replication_factor: 3 - shard_count: 16 - max_database_size: 1073741824 - backup_interval: "24h" - rqlite_port: %d - rqlite_raft_port: %d - rqlite_join_address: "%s" - cluster_sync_interval: "30s" - peer_inactivity_limit: "24h" - min_cluster_size: 1 - ipfs: - # IPFS Cluster API endpoint for pin management (leave empty to disable) - cluster_api_url: "http://localhost:%d" - # IPFS HTTP API endpoint for content retrieval - api_url: "http://localhost:%d" - # Timeout for IPFS operations - timeout: "60s" - # Replication factor for pinned content - replication_factor: 3 - # Enable client-side encryption before upload - enable_encryption: true - -discovery: -%s - discovery_interval: "15s" - bootstrap_port: %d - http_adv_address: "localhost:%d" - raft_adv_address: "localhost:%d" - node_namespace: "default" - -security: - enable_tls: false - -logging: - level: "info" - format: "console" -`, nodeID, listenPort, dataDir, dataDir, rqliteHTTPPort, rqliteRaftPort, joinAddr, clusterAPIPort, rqliteHTTPPort, peersYAML.String(), 4001, rqliteHTTPPort, rqliteRaftPort) -} - -// GenerateBootstrapConfig generates a bootstrap configuration -func GenerateBootstrapConfig(name, id string, listenPort, rqliteHTTPPort, rqliteRaftPort int) string { - nodeID := id - if nodeID == "" { - nodeID = "bootstrap" - } - - dataDir := filepath.Join(os.ExpandEnv("~"), ".debros", "bootstrap") - - return fmt.Sprintf(`node: - id: "%s" - type: "bootstrap" - listen_addresses: - - "/ip4/0.0.0.0/tcp/%d" - data_dir: "%s" - max_connections: 50 - -database: - data_dir: "%s/rqlite" - replication_factor: 3 - shard_count: 16 - max_database_size: 1073741824 - backup_interval: "24h" - rqlite_port: %d - rqlite_raft_port: %d - rqlite_join_address: "" - cluster_sync_interval: "30s" - peer_inactivity_limit: "24h" - min_cluster_size: 1 - ipfs: - # IPFS Cluster API endpoint for pin management (leave empty to disable) - cluster_api_url: "http://localhost:9094" - # IPFS HTTP API endpoint for content retrieval - api_url: "http://localhost:%d" - # Timeout for IPFS operations - timeout: "60s" - # Replication factor for pinned content - replication_factor: 3 - # Enable client-side encryption before upload - enable_encryption: true - -discovery: - bootstrap_peers: [] - discovery_interval: "15s" - bootstrap_port: %d - http_adv_address: "localhost:%d" - raft_adv_address: "localhost:%d" - node_namespace: "default" - -security: - enable_tls: false - -logging: - level: "info" - format: "console" -`, nodeID, listenPort, dataDir, dataDir, rqliteHTTPPort, rqliteRaftPort, rqliteHTTPPort, 4001, rqliteHTTPPort, rqliteRaftPort) -} - -// GenerateGatewayConfig generates a gateway configuration -func GenerateGatewayConfig(bootstrapPeers string) string { - var peers []string - if bootstrapPeers != "" { - for _, p := range strings.Split(bootstrapPeers, ",") { - if p = strings.TrimSpace(p); p != "" { - peers = append(peers, p) - } - } - } - - var peersYAML strings.Builder - if len(peers) == 0 { - peersYAML.WriteString("bootstrap_peers: []") - } else { - peersYAML.WriteString("bootstrap_peers:\n") - for _, p := range peers { - fmt.Fprintf(&peersYAML, " - \"%s\"\n", p) - } - } - - return fmt.Sprintf(`listen_addr: ":6001" -client_namespace: "default" -rqlite_dsn: "" -%s -olric_servers: - - "127.0.0.1:3320" -olric_timeout: "10s" -ipfs_cluster_api_url: "http://localhost:9094" -ipfs_api_url: "http://localhost:5001" -ipfs_timeout: "60s" -ipfs_replication_factor: 3 -`, peersYAML.String()) -} diff --git a/pkg/cli/dev_commands.go b/pkg/cli/dev_commands.go new file mode 100644 index 0000000..8d087b3 --- /dev/null +++ b/pkg/cli/dev_commands.go @@ -0,0 +1,191 @@ +package cli + +import ( + "context" + "fmt" + "os" + "os/exec" + "path/filepath" + + "github.com/DeBrosOfficial/network/pkg/environments/development" +) + +// HandleDevCommand handles the dev command group +func HandleDevCommand(args []string) { + if len(args) == 0 { + showDevHelp() + return + } + + subcommand := args[0] + subargs := args[1:] + + switch subcommand { + case "up": + handleDevUp(subargs) + case "down": + handleDevDown(subargs) + case "status": + handleDevStatus(subargs) + case "logs": + handleDevLogs(subargs) + case "help": + showDevHelp() + default: + fmt.Fprintf(os.Stderr, "Unknown dev subcommand: %s\n", subcommand) + showDevHelp() + os.Exit(1) + } +} + +func showDevHelp() { + fmt.Printf("๐Ÿš€ Development Environment Commands\n\n") + fmt.Printf("Usage: network-cli dev [options]\n\n") + fmt.Printf("Subcommands:\n") + fmt.Printf(" up - Start development environment (bootstrap + 2 nodes + gateway)\n") + fmt.Printf(" down - Stop all development services\n") + fmt.Printf(" status - Show status of running services\n") + fmt.Printf(" logs - Tail logs for a component\n") + fmt.Printf(" help - Show this help\n\n") + fmt.Printf("Examples:\n") + fmt.Printf(" network-cli dev up\n") + fmt.Printf(" network-cli dev down\n") + fmt.Printf(" network-cli dev status\n") + fmt.Printf(" network-cli dev logs bootstrap --follow\n") +} + +func handleDevUp(args []string) { + ctx := context.Background() + + // Get home directory and .debros path + 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") + + // Step 1: Check dependencies + fmt.Printf("๐Ÿ“‹ Checking dependencies...\n\n") + checker := development.NewDependencyChecker() + if _, err := checker.CheckAll(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ %v\n", err) + os.Exit(1) + } + fmt.Printf("โœ“ All required dependencies available\n\n") + + // Step 2: Check ports + fmt.Printf("๐Ÿ”Œ Checking port availability...\n\n") + portChecker := development.NewPortChecker() + if _, err := portChecker.CheckAll(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ %v\n\n", err) + fmt.Fprintf(os.Stderr, "Port mapping:\n") + for port, service := range development.PortMap() { + fmt.Fprintf(os.Stderr, " %d - %s\n", port, service) + } + fmt.Fprintf(os.Stderr, "\n") + os.Exit(1) + } + fmt.Printf("โœ“ All required ports available\n\n") + + // Step 3: Ensure configs + fmt.Printf("โš™๏ธ Preparing configuration files...\n\n") + ensurer := development.NewConfigEnsurer(debrosDir) + if err := ensurer.EnsureAll(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Failed to prepare configs: %v\n", err) + os.Exit(1) + } + fmt.Printf("\n") + + // Step 4: Start services + pm := development.NewProcessManager(debrosDir, os.Stdout) + if err := pm.StartAll(ctx); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Error starting services: %v\n", err) + os.Exit(1) + } + + // Step 5: Show summary + fmt.Printf("๐ŸŽ‰ Development environment is running!\n\n") + fmt.Printf("Key endpoints:\n") + fmt.Printf(" Gateway: http://localhost:6001\n") + fmt.Printf(" Bootstrap IPFS: http://localhost:4501\n") + fmt.Printf(" Node2 IPFS: http://localhost:4502\n") + fmt.Printf(" Node3 IPFS: http://localhost:4503\n") + fmt.Printf(" Anon SOCKS: 127.0.0.1:9050\n") + fmt.Printf(" Olric Cache: http://localhost:3320\n\n") + fmt.Printf("Useful commands:\n") + fmt.Printf(" network-cli dev status - Show status\n") + fmt.Printf(" network-cli dev logs bootstrap - Bootstrap logs\n") + fmt.Printf(" network-cli dev down - Stop all services\n\n") + fmt.Printf("Logs directory: %s/logs\n\n", debrosDir) +} + +func handleDevDown(args []string) { + 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") + + pm := development.NewProcessManager(debrosDir, os.Stdout) + ctx := context.Background() + + if err := pm.StopAll(ctx); err != nil { + fmt.Fprintf(os.Stderr, "โš ๏ธ Error stopping services: %v\n", err) + } +} + +func handleDevStatus(args []string) { + 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") + + pm := development.NewProcessManager(debrosDir, os.Stdout) + ctx := context.Background() + + pm.Status(ctx) +} + +func handleDevLogs(args []string) { + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "Usage: network-cli dev logs [--follow]\n") + fmt.Fprintf(os.Stderr, "\nComponents: bootstrap, node2, node3, gateway, ipfs-bootstrap, ipfs-node2, ipfs-node3, olric, anon\n") + os.Exit(1) + } + + component := args[0] + follow := len(args) > 1 && args[1] == "--follow" + + 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") + + logPath := filepath.Join(debrosDir, "logs", fmt.Sprintf("%s.log", component)) + if _, err := os.Stat(logPath); os.IsNotExist(err) { + fmt.Fprintf(os.Stderr, "โŒ Log file not found: %s\n", logPath) + os.Exit(1) + } + + if follow { + // Run tail -f + tailCmd := fmt.Sprintf("tail -f %s", logPath) + fmt.Printf("Following %s (press Ctrl+C to stop)...\n\n", logPath) + // syscall.Exec doesn't work in all environments, use exec.Command instead + cmd := exec.Command("sh", "-c", tailCmd) + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + cmd.Run() + } else { + // Cat the file + data, _ := os.ReadFile(logPath) + fmt.Print(string(data)) + } +} diff --git a/pkg/cli/prod_commands.go b/pkg/cli/prod_commands.go new file mode 100644 index 0000000..262317f --- /dev/null +++ b/pkg/cli/prod_commands.go @@ -0,0 +1,313 @@ +package cli + +import ( + "bufio" + "fmt" + "os" + "os/exec" + "path/filepath" + "strings" + + "github.com/DeBrosOfficial/network/pkg/environments/production" +) + +// HandleProdCommand handles production environment commands +func HandleProdCommand(args []string) { + if len(args) == 0 { + showProdHelp() + return + } + + subcommand := args[0] + subargs := args[1:] + + switch subcommand { + case "install": + handleProdInstall(subargs) + case "upgrade": + handleProdUpgrade(subargs) + case "status": + handleProdStatus() + case "logs": + handleProdLogs(subargs) + case "uninstall": + handleProdUninstall() + case "help": + showProdHelp() + default: + fmt.Fprintf(os.Stderr, "Unknown prod subcommand: %s\n", subcommand) + showProdHelp() + os.Exit(1) + } +} + +func showProdHelp() { + fmt.Printf("Production Environment Commands\n\n") + fmt.Printf("Usage: network-cli prod [options]\n\n") + fmt.Printf("Subcommands:\n") + fmt.Printf(" install - Full production bootstrap (requires root/sudo)\n") + fmt.Printf(" Options:\n") + fmt.Printf(" --force - Reconfigure all settings\n") + fmt.Printf(" --bootstrap - Install as bootstrap node\n") + fmt.Printf(" --peers ADDRS - Comma-separated bootstrap peers (for non-bootstrap)\n") + fmt.Printf(" --vps-ip IP - VPS public IP address\n") + fmt.Printf(" --domain DOMAIN - Domain for HTTPS (optional)\n") + fmt.Printf(" upgrade - Upgrade existing installation (requires root/sudo)\n") + fmt.Printf(" status - Show status of production services\n") + fmt.Printf(" logs - View production service logs\n") + fmt.Printf(" Options:\n") + fmt.Printf(" --follow - Follow logs in real-time\n") + fmt.Printf(" uninstall - Remove production services (requires root/sudo)\n\n") + fmt.Printf("Examples:\n") + fmt.Printf(" sudo network-cli prod install --bootstrap\n") + fmt.Printf(" sudo network-cli prod install --peers /ip4/1.2.3.4/tcp/4001/p2p/Qm...\n") + fmt.Printf(" network-cli prod status\n") + fmt.Printf(" network-cli prod logs node --follow\n") +} + +func handleProdInstall(args []string) { + // Parse arguments + force := false + isBootstrap := false + var vpsIP, domain, peersStr string + + for i, arg := range args { + switch arg { + case "--force": + force = true + case "--bootstrap": + isBootstrap = true + case "--peers": + if i+1 < len(args) { + peersStr = args[i+1] + } + case "--vps-ip": + if i+1 < len(args) { + vpsIP = args[i+1] + } + case "--domain": + if i+1 < len(args) { + domain = args[i+1] + } + } + } + + // Parse bootstrap peers if provided + var bootstrapPeers []string + if peersStr != "" { + bootstrapPeers = strings.Split(peersStr, ",") + } + + // Validate setup requirements + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "โŒ Production install must be run as root (use sudo)\n") + os.Exit(1) + } + + debrosHome := "/home/debros" + setup := production.NewProductionSetup(debrosHome, os.Stdout, force) + + // Phase 1: Check prerequisites + fmt.Printf("\n๐Ÿ“‹ Phase 1: Checking prerequisites...\n") + if err := setup.Phase1CheckPrerequisites(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Prerequisites check failed: %v\n", err) + os.Exit(1) + } + + // Phase 2: Provision environment + fmt.Printf("\n๐Ÿ› ๏ธ Phase 2: Provisioning environment...\n") + if err := setup.Phase2ProvisionEnvironment(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Environment provisioning failed: %v\n", err) + os.Exit(1) + } + + // Phase 2b: Install binaries + fmt.Printf("\nPhase 2b: Installing binaries...\n") + if err := setup.Phase2bInstallBinaries(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Binary installation failed: %v\n", err) + os.Exit(1) + } + + // Phase 2c: Initialize services + nodeType := "node" + if isBootstrap { + nodeType = "bootstrap" + } + fmt.Printf("\nPhase 2c: Initializing services...\n") + if err := setup.Phase2cInitializeServices(nodeType); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Service initialization failed: %v\n", err) + os.Exit(1) + } + + // Phase 3: Generate secrets + fmt.Printf("\n๐Ÿ” Phase 3: Generating secrets...\n") + if err := setup.Phase3GenerateSecrets(isBootstrap); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Secret generation failed: %v\n", err) + os.Exit(1) + } + + // Phase 4: Generate configs + fmt.Printf("\nโš™๏ธ Phase 4: Generating configurations...\n") + enableHTTPS := domain != "" + if err := setup.Phase4GenerateConfigs(isBootstrap, bootstrapPeers, vpsIP, enableHTTPS, domain); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Configuration generation failed: %v\n", err) + os.Exit(1) + } + + // Phase 5: Create systemd services + fmt.Printf("\n๐Ÿ”ง Phase 5: Creating systemd services...\n") + if err := setup.Phase5CreateSystemdServices(nodeType); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Service creation failed: %v\n", err) + os.Exit(1) + } + + // Log completion + setup.LogSetupComplete("< peer ID from config >") + fmt.Printf("โœ… Production installation complete!\n\n") +} + +func handleProdUpgrade(args []string) { + // Parse arguments + force := false + for _, arg := range args { + if arg == "--force" { + force = true + } + } + + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "โŒ Production upgrade must be run as root (use sudo)\n") + os.Exit(1) + } + + debrosHome := "/home/debros" + fmt.Printf("๐Ÿ”„ Upgrading production installation...\n") + fmt.Printf(" This will preserve existing configurations and data\n\n") + + // For now, just re-run the install with force flag + setup := production.NewProductionSetup(debrosHome, os.Stdout, force) + + if err := setup.Phase1CheckPrerequisites(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Prerequisites check failed: %v\n", err) + os.Exit(1) + } + + if err := setup.Phase2ProvisionEnvironment(); err != nil { + fmt.Fprintf(os.Stderr, "โŒ Environment provisioning failed: %v\n", err) + os.Exit(1) + } + + fmt.Printf("โœ… Upgrade complete!\n") + fmt.Printf(" Services will use existing configurations\n") + fmt.Printf(" To restart services: sudo systemctl restart debros-*\n\n") +} + +func handleProdStatus() { + fmt.Printf("Production Environment Status\n\n") + + servicesList := []struct { + name string + desc string + }{ + {"debros-ipfs-bootstrap", "IPFS Daemon (Bootstrap)"}, + {"debros-ipfs-cluster-bootstrap", "IPFS Cluster (Bootstrap)"}, + {"debros-rqlite-bootstrap", "RQLite Database (Bootstrap)"}, + {"debros-olric", "Olric Cache Server"}, + {"debros-node-bootstrap", "DeBros Node (Bootstrap)"}, + {"debros-gateway", "DeBros Gateway"}, + } + + fmt.Printf("Services:\n") + for _, svc := range servicesList { + cmd := "systemctl" + err := exec.Command(cmd, "is-active", "--quiet", svc.name).Run() + status := "โŒ Inactive" + if err == nil { + status = "โœ… Active" + } + fmt.Printf(" %s: %s\n", status, svc.desc) + } + + fmt.Printf("\nDirectories:\n") + debrosDir := "/home/debros/.debros" + if _, err := os.Stat(debrosDir); err == nil { + fmt.Printf(" โœ… %s exists\n", debrosDir) + } else { + fmt.Printf(" โŒ %s not found\n", debrosDir) + } + + fmt.Printf("\nView logs with: network-cli prod logs \n") +} + +func handleProdLogs(args []string) { + if len(args) == 0 { + fmt.Fprintf(os.Stderr, "Usage: network-cli prod logs [--follow]\n") + os.Exit(1) + } + + service := args[0] + follow := false + if len(args) > 1 && (args[1] == "--follow" || args[1] == "-f") { + follow = true + } + + if follow { + fmt.Printf("Following logs for %s (press Ctrl+C to stop)...\n\n", service) + cmd := exec.Command("journalctl", "-u", service, "-f") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Stdin = os.Stdin + cmd.Run() + } else { + cmd := exec.Command("journalctl", "-u", service, "-n", "50") + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + cmd.Run() + } +} + +func handleProdUninstall() { + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "โŒ Production uninstall must be run as root (use sudo)\n") + os.Exit(1) + } + + fmt.Printf("โš ๏ธ This will stop and remove all DeBros production services\n") + fmt.Printf("โš ๏ธ Configuration and data will be preserved in /home/debros/.debros\n\n") + fmt.Printf("Continue? (yes/no): ") + + reader := bufio.NewReader(os.Stdin) + response, _ := reader.ReadString('\n') + response = strings.ToLower(strings.TrimSpace(response)) + + if response != "yes" && response != "y" { + fmt.Printf("Uninstall cancelled\n") + return + } + + services := []string{ + "debros-gateway", + "debros-node-node", + "debros-node-bootstrap", + "debros-olric", + "debros-rqlite-bootstrap", + "debros-rqlite-node", + "debros-ipfs-cluster-bootstrap", + "debros-ipfs-cluster-node", + "debros-ipfs-bootstrap", + "debros-ipfs-node", + } + + fmt.Printf("Stopping services...\n") + for _, svc := range services { + exec.Command("systemctl", "stop", svc).Run() + exec.Command("systemctl", "disable", svc).Run() + unitPath := filepath.Join("/etc/systemd/system", svc+".service") + os.Remove(unitPath) + } + + exec.Command("systemctl", "daemon-reload").Run() + fmt.Printf("โœ… Services uninstalled\n") + fmt.Printf(" Configuration and data preserved in /home/debros/.debros\n") + fmt.Printf(" To remove all data: rm -rf /home/debros/.debros\n\n") +} diff --git a/pkg/cli/rqlite_commands.go b/pkg/cli/rqlite_commands.go deleted file mode 100644 index b9961cb..0000000 --- a/pkg/cli/rqlite_commands.go +++ /dev/null @@ -1,327 +0,0 @@ -package cli - -import ( - "fmt" - "net" - "os" - "os/exec" - "path/filepath" - "runtime" - "strings" - - "github.com/DeBrosOfficial/network/pkg/config" - "gopkg.in/yaml.v3" -) - -// HandleRQLiteCommand handles rqlite-related commands -func HandleRQLiteCommand(args []string) { - if len(args) == 0 { - showRQLiteHelp() - return - } - - if runtime.GOOS != "linux" { - fmt.Fprintf(os.Stderr, "โŒ RQLite commands are only supported on Linux\n") - os.Exit(1) - } - - subcommand := args[0] - subargs := args[1:] - - switch subcommand { - case "fix": - handleRQLiteFix(subargs) - case "help": - showRQLiteHelp() - default: - fmt.Fprintf(os.Stderr, "Unknown rqlite subcommand: %s\n", subcommand) - showRQLiteHelp() - os.Exit(1) - } -} - -func showRQLiteHelp() { - fmt.Printf("๐Ÿ—„๏ธ RQLite Commands\n\n") - fmt.Printf("Usage: network-cli rqlite [options]\n\n") - fmt.Printf("Subcommands:\n") - fmt.Printf(" fix - Fix misconfigured join address and clean stale raft state\n\n") - fmt.Printf("Description:\n") - fmt.Printf(" The 'fix' command automatically repairs common rqlite cluster issues:\n") - fmt.Printf(" - Corrects join address from HTTP port (5001) to Raft port (7001) if misconfigured\n") - fmt.Printf(" - Cleans stale raft state that prevents proper cluster formation\n") - fmt.Printf(" - Restarts the node service with corrected configuration\n\n") - fmt.Printf("Requirements:\n") - fmt.Printf(" - Must be run as root (use sudo)\n") - fmt.Printf(" - Only works on non-bootstrap nodes (nodes with join_address configured)\n") - fmt.Printf(" - Stops and restarts the debros-node service\n\n") - fmt.Printf("Examples:\n") - fmt.Printf(" sudo network-cli rqlite fix\n") -} - -func handleRQLiteFix(args []string) { - requireRoot() - - // Parse optional flags - dryRun := false - for _, arg := range args { - if arg == "--dry-run" || arg == "-n" { - dryRun = true - } - } - - if dryRun { - fmt.Printf("๐Ÿ” Dry-run mode - no changes will be made\n\n") - } - - fmt.Printf("๐Ÿ”ง RQLite Cluster Repair\n\n") - - // Load config - configPath, err := config.DefaultPath("node.yaml") - if err != nil { - fmt.Fprintf(os.Stderr, "โŒ Failed to determine config path: %v\n", err) - os.Exit(1) - } - - cfg, err := loadConfigForRepair(configPath) - if err != nil { - fmt.Fprintf(os.Stderr, "โŒ Failed to load config: %v\n", err) - os.Exit(1) - } - - // Check if this is a bootstrap node - if cfg.Node.Type == "bootstrap" || cfg.Database.RQLiteJoinAddress == "" { - fmt.Printf("โ„น๏ธ This is a bootstrap node (no join address configured)\n") - fmt.Printf(" Bootstrap nodes don't need repair - they are the cluster leader\n") - fmt.Printf(" Run this command on follower nodes instead\n") - return - } - - joinAddr := cfg.Database.RQLiteJoinAddress - - // Check if join address needs fixing - needsConfigFix := needsFix(joinAddr, cfg.Database.RQLiteRaftPort, cfg.Database.RQLitePort) - var fixedAddr string - - if needsConfigFix { - fmt.Printf("โš ๏ธ Detected misconfigured join address: %s\n", joinAddr) - fmt.Printf(" Expected Raft port (%d) but found HTTP port (%d)\n", cfg.Database.RQLiteRaftPort, cfg.Database.RQLitePort) - - // Extract host from join address - host, _, err := parseJoinAddress(joinAddr) - if err != nil { - fmt.Fprintf(os.Stderr, "โŒ Failed to parse join address: %v\n", err) - os.Exit(1) - } - - // Fix the join address - rqlite expects Raft port for -join - fixedAddr = fmt.Sprintf("%s:%d", host, cfg.Database.RQLiteRaftPort) - fmt.Printf(" Corrected address: %s\n\n", fixedAddr) - } else { - fmt.Printf("โœ… Join address looks correct: %s\n", joinAddr) - fmt.Printf(" Will clean stale raft state to ensure proper cluster formation\n\n") - fixedAddr = joinAddr // No change needed - } - - if dryRun { - fmt.Printf("๐Ÿ” Dry-run: Would clean raft state") - if needsConfigFix { - fmt.Printf(" and fix config") - } - fmt.Printf("\n") - return - } - - // Stop the service - fmt.Printf("โน๏ธ Stopping debros-node service...\n") - if err := stopService("debros-node"); err != nil { - fmt.Fprintf(os.Stderr, "โŒ Failed to stop service: %v\n", err) - os.Exit(1) - } - fmt.Printf(" โœ“ Service stopped\n\n") - - // Update config file if needed - if needsConfigFix { - fmt.Printf("๐Ÿ“ Updating configuration file...\n") - if err := updateConfigJoinAddress(configPath, fixedAddr); err != nil { - fmt.Fprintf(os.Stderr, "โŒ Failed to update config: %v\n", err) - fmt.Fprintf(os.Stderr, " Service is stopped - please fix manually and restart\n") - os.Exit(1) - } - fmt.Printf(" โœ“ Config updated: %s\n\n", configPath) - } - - // Clean raft state - fmt.Printf("๐Ÿงน Cleaning stale raft state...\n") - dataDir := expandDataDir(cfg.Node.DataDir) - raftDir := filepath.Join(dataDir, "rqlite", "raft") - if err := cleanRaftState(raftDir); err != nil { - fmt.Fprintf(os.Stderr, "โš ๏ธ Failed to clean raft state: %v\n", err) - fmt.Fprintf(os.Stderr, " Continuing anyway - raft state may still exist\n") - } else { - fmt.Printf(" โœ“ Raft state cleaned\n\n") - } - - // Restart the service - fmt.Printf("๐Ÿš€ Restarting debros-node service...\n") - if err := startService("debros-node"); err != nil { - fmt.Fprintf(os.Stderr, "โŒ Failed to start service: %v\n", err) - fmt.Fprintf(os.Stderr, " Config has been fixed - please restart manually:\n") - fmt.Fprintf(os.Stderr, " sudo systemctl start debros-node\n") - os.Exit(1) - } - fmt.Printf(" โœ“ Service started\n\n") - - fmt.Printf("โœ… Repair complete!\n\n") - fmt.Printf("The node should now join the cluster correctly.\n") - fmt.Printf("Monitor logs with: sudo network-cli service logs node --follow\n") -} - -func loadConfigForRepair(path string) (*config.Config, error) { - file, err := os.Open(path) - if err != nil { - return nil, fmt.Errorf("failed to open config file: %w", err) - } - defer file.Close() - - var cfg config.Config - if err := config.DecodeStrict(file, &cfg); err != nil { - return nil, fmt.Errorf("failed to parse config: %w", err) - } - - return &cfg, nil -} - -func needsFix(joinAddr string, raftPort int, httpPort int) bool { - if joinAddr == "" { - return false - } - - // Remove http:// or https:// prefix if present - addr := joinAddr - if strings.HasPrefix(addr, "http://") { - addr = strings.TrimPrefix(addr, "http://") - } else if strings.HasPrefix(addr, "https://") { - addr = strings.TrimPrefix(addr, "https://") - } - - // Parse host:port - _, port, err := net.SplitHostPort(addr) - if err != nil { - return false // Can't parse, assume it's fine - } - - // Check if port matches HTTP port (incorrect - should be Raft port) - if port == fmt.Sprintf("%d", httpPort) { - return true - } - - // If it matches Raft port, it's correct - if port == fmt.Sprintf("%d", raftPort) { - return false - } - - // Unknown port - assume it's fine - return false -} - -func parseJoinAddress(joinAddr string) (host, port string, err error) { - // Remove http:// or https:// prefix if present - addr := joinAddr - if strings.HasPrefix(addr, "http://") { - addr = strings.TrimPrefix(addr, "http://") - } else if strings.HasPrefix(addr, "https://") { - addr = strings.TrimPrefix(addr, "https://") - } - - host, port, err = net.SplitHostPort(addr) - if err != nil { - return "", "", fmt.Errorf("invalid join address format: %w", err) - } - - return host, port, nil -} - -func updateConfigJoinAddress(configPath string, newJoinAddr string) error { - // Read the file - data, err := os.ReadFile(configPath) - if err != nil { - return fmt.Errorf("failed to read config file: %w", err) - } - - // Parse YAML into a generic map to preserve structure - var yamlData map[string]interface{} - if err := yaml.Unmarshal(data, &yamlData); err != nil { - return fmt.Errorf("failed to parse YAML: %w", err) - } - - // Navigate to database.rqlite_join_address - database, ok := yamlData["database"].(map[string]interface{}) - if !ok { - return fmt.Errorf("database section not found in config") - } - - database["rqlite_join_address"] = newJoinAddr - - // Write back to file - updatedData, err := yaml.Marshal(yamlData) - if err != nil { - return fmt.Errorf("failed to marshal YAML: %w", err) - } - - if err := os.WriteFile(configPath, updatedData, 0644); err != nil { - return fmt.Errorf("failed to write config file: %w", err) - } - - return nil -} - -func expandDataDir(dataDir string) string { - expanded := os.ExpandEnv(dataDir) - if strings.HasPrefix(expanded, "~") { - home, err := os.UserHomeDir() - if err != nil { - return expanded // Fallback to original - } - expanded = filepath.Join(home, expanded[1:]) - } - return expanded -} - -func cleanRaftState(raftDir string) error { - if _, err := os.Stat(raftDir); os.IsNotExist(err) { - return nil // Directory doesn't exist, nothing to clean - } - - // Remove raft state files - filesToRemove := []string{ - "peers.json", - "peers.json.backup", - "peers.info", - "raft.db", - } - - for _, file := range filesToRemove { - filePath := filepath.Join(raftDir, file) - if err := os.Remove(filePath); err != nil && !os.IsNotExist(err) { - return fmt.Errorf("failed to remove %s: %w", filePath, err) - } - } - - return nil -} - -func stopService(serviceName string) error { - cmd := exec.Command("systemctl", "stop", serviceName) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl stop failed: %w", err) - } - return nil -} - -func startService(serviceName string) error { - cmd := exec.Command("systemctl", "start", serviceName) - if err := cmd.Run(); err != nil { - return fmt.Errorf("systemctl start failed: %w", err) - } - return nil -} diff --git a/pkg/cli/service.go b/pkg/cli/service.go deleted file mode 100644 index 6379db2..0000000 --- a/pkg/cli/service.go +++ /dev/null @@ -1,243 +0,0 @@ -package cli - -import ( - "fmt" - "os" - "os/exec" - "runtime" - "strings" -) - -// HandleServiceCommand handles systemd service management commands -func HandleServiceCommand(args []string) { - if len(args) == 0 { - showServiceHelp() - return - } - - if runtime.GOOS != "linux" { - fmt.Fprintf(os.Stderr, "โŒ Service commands are only supported on Linux with systemd\n") - os.Exit(1) - } - - subcommand := args[0] - subargs := args[1:] - - switch subcommand { - case "start": - handleServiceStart(subargs) - case "stop": - handleServiceStop(subargs) - case "restart": - handleServiceRestart(subargs) - case "status": - handleServiceStatus(subargs) - case "logs": - handleServiceLogs(subargs) - case "help": - showServiceHelp() - default: - fmt.Fprintf(os.Stderr, "Unknown service subcommand: %s\n", subcommand) - showServiceHelp() - os.Exit(1) - } -} - -func showServiceHelp() { - fmt.Printf("๐Ÿ”ง Service Management Commands\n\n") - fmt.Printf("Usage: network-cli service [options]\n\n") - fmt.Printf("Subcommands:\n") - fmt.Printf(" start - Start services\n") - fmt.Printf(" stop - Stop services\n") - fmt.Printf(" restart - Restart services\n") - fmt.Printf(" status - Show service status\n") - fmt.Printf(" logs - View service logs\n\n") - fmt.Printf("Targets:\n") - fmt.Printf(" node - DeBros node service\n") - fmt.Printf(" gateway - DeBros gateway service\n") - fmt.Printf(" all - All DeBros services\n\n") - fmt.Printf("Logs Options:\n") - fmt.Printf(" --follow - Follow logs in real-time (-f)\n") - fmt.Printf(" --since=