From b0ac58af3e0ab3e324e4dd349a10a7532c3e540b Mon Sep 17 00:00:00 2001 From: anonpenguin23 Date: Tue, 11 Nov 2025 09:00:45 +0200 Subject: [PATCH] feat: add production deployment documentation and service management commands - Introduced a new section in the README for Production Deployment, detailing prerequisites, installation steps, and service management commands. - Added commands for starting, stopping, and restarting production services in the CLI, enhancing user control over service management. - Updated IPFS initialization to configure API, Gateway, and Swarm addresses to avoid port conflicts, improving deployment reliability. - Enhanced error handling and logging for service management operations, ensuring better feedback during execution. --- CHANGELOG.md | 16 ++ Makefile | 2 +- README.md | 281 ++++++++++++++++++++ pkg/cli/prod_commands.go | 119 +++++++++ pkg/environments/production/installers.go | 47 +++- pkg/environments/production/orchestrator.go | 3 +- 6 files changed, 465 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 52b68c7..7525917 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.68.0] - 2025-11-11 + +### Added +- Added comprehensive documentation for production deployment, including installation, upgrade, service management, and troubleshooting. +- Added new CLI commands (`dbn prod start`, `dbn prod stop`, `dbn prod restart`) for convenient management of production systemd services. + +### Changed +- Updated IPFS configuration during production installation to use port 4501 for the API (to avoid conflicts with RQLite on port 5001) and port 8080 for the Gateway. + +### Deprecated + +### Removed + +### Fixed +- Ensured that IPFS configuration automatically disables AutoConf when a private swarm key is present during installation and upgrade, preventing startup errors. + ## [0.67.7] - 2025-11-11 ### Added diff --git a/Makefile b/Makefile index d73584f..d5b63d0 100644 --- a/Makefile +++ b/Makefile @@ -19,7 +19,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.67.7 +VERSION := 0.68.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)' diff --git a/README.md b/README.md index 9738c4f..655da4a 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,7 @@ DeBros Network is a decentralized peer-to-peer data platform built in Go. It com - [At a Glance](#at-a-glance) - [Quick Start](#quick-start) +- [Production Deployment](#production-deployment) - [Components & Ports](#components--ports) - [Configuration Cheatsheet](#configuration-cheatsheet) - [CLI Highlights](#cli-highlights) @@ -54,6 +55,286 @@ DeBros Network is a decentralized peer-to-peer data platform built in Go. It com ./bin/dbn pubsub subscribe notifications 10s ``` +## Production Deployment + +DeBros Network can be deployed as production systemd services on Linux servers. The production installer handles all dependencies, configuration, and service management automatically. + +### Prerequisites + +- **OS**: Ubuntu 20.04+, Debian 11+, or compatible Linux distribution +- **Architecture**: `amd64` (x86_64) or `arm64` (aarch64) +- **Permissions**: Root access (use `sudo`) +- **Resources**: Minimum 2GB RAM, 10GB disk space, 2 CPU cores + +### Installation + +#### Quick Install + +Install the CLI tool first: + +```bash +curl -fsSL https://install.debros.network | sudo bash +``` + +Or download manually from [GitHub Releases](https://github.com/DeBrosOfficial/network/releases). + +#### Bootstrap Node (First Node) + +Install the first node in your cluster: + +```bash +# Main branch (stable releases) +sudo dbn prod install --bootstrap + +# Nightly branch (latest development) +sudo dbn prod install --bootstrap --branch nightly +``` + +The bootstrap node initializes the cluster and serves as the primary peer for other nodes to join. + +#### Secondary Node (Join Existing Cluster) + +Join an existing cluster by providing the bootstrap node's IP and peer multiaddr: + +```bash +sudo dbn prod install \ + --vps-ip \ + --peers /ip4//tcp/4001/p2p/ \ + --branch nightly +``` + +**Required flags for secondary nodes:** + +- `--vps-ip`: Your server's public IP address +- `--peers`: Comma-separated list of bootstrap peer multiaddrs + +**Optional flags:** + +- `--branch`: Git branch to use (`main` or `nightly`, default: `main`) +- `--domain`: Domain name for HTTPS (enables ACME/Let's Encrypt) +- `--bootstrap-join`: Raft join address for secondary bootstrap nodes + +#### Secondary Bootstrap Node + +Create a secondary bootstrap node that joins an existing Raft cluster: + +```bash +sudo dbn prod install \ + --bootstrap \ + --vps-ip \ + --bootstrap-join :7001 \ + --branch nightly +``` + +### Branch Selection + +DeBros Network supports two branches: + +- **`main`**: Stable releases (default). Recommended for production. +- **`nightly`**: Latest development builds. Use for testing new features. + +**Branch preference is saved automatically** during installation. Future upgrades will use the same branch unless you override it with `--branch`. + +**Examples:** + +```bash +# Install with nightly branch +sudo dbn prod install --bootstrap --branch nightly + +# Upgrade using saved branch preference +sudo dbn prod upgrade --restart + +# Upgrade and switch to main branch +sudo dbn prod upgrade --restart --branch main +``` + +### Upgrade + +Upgrade an existing installation to the latest version: + +```bash +# Upgrade using saved branch preference +sudo dbn prod upgrade --restart + +# Upgrade and switch branches +sudo dbn prod upgrade --restart --branch nightly + +# Upgrade without restarting services +sudo dbn prod upgrade +``` + +The upgrade process: + +1. ✅ Checks prerequisites +2. ✅ Updates binaries (fetches latest from selected branch) +3. ✅ Preserves existing configurations and data +4. ✅ Updates configurations to latest format +5. ✅ Updates systemd service files +6. ✅ Optionally restarts services (`--restart` flag) + +**Note**: The upgrade automatically detects your node type (bootstrap vs. regular node) and preserves all secrets, data, and configurations. + +### Service Management + +All services run as systemd units under the `debros` user. + +#### Check Status + +```bash +# View status of all services +dbn prod status + +# Or use systemctl directly +systemctl status debros-node-bootstrap +systemctl status debros-ipfs-bootstrap +systemctl status debros-gateway +``` + +#### View Logs + +```bash +# View recent logs +dbn prod logs node + +# Follow logs in real-time +dbn prod logs node --follow + +# View specific service logs +dbn prod logs ipfs --follow +dbn prod logs gateway --follow +``` + +Available log targets: `node`, `ipfs`, `ipfs-cluster`, `rqlite`, `olric`, `gateway` + +#### Service Control Commands + +Use `dbn prod` commands for convenient service management: + +```bash +# Start all services +sudo dbn prod start + +# Stop all services +sudo dbn prod stop + +# Restart all services +sudo dbn prod restart +``` + +Or use `systemctl` directly for more control: + +```bash +# Restart all services +sudo systemctl restart debros-* + +# Restart specific service +sudo systemctl restart debros-node-bootstrap + +# Stop services +sudo systemctl stop debros-* + +# Start services +sudo systemctl start debros-* + +# Enable services (start on boot) +sudo systemctl enable debros-* +``` + +### Directory Structure + +Production installations use `/home/debros/.debros/`: + +``` +/home/debros/.debros/ +├── configs/ # Configuration files +│ ├── bootstrap.yaml # Bootstrap node config +│ ├── node.yaml # Regular node config +│ ├── gateway.yaml # Gateway config +│ └── olric/ # Olric cache config +├── data/ # Runtime data +│ ├── bootstrap/ # Bootstrap node data +│ │ ├── ipfs/ # IPFS repository +│ │ ├── ipfs-cluster/ # IPFS Cluster data +│ │ └── rqlite/ # RQLite database +│ └── node/ # Regular node data +├── secrets/ # Secrets and keys +│ ├── cluster-secret # IPFS Cluster secret +│ └── swarm.key # IPFS swarm key +├── logs/ # Service logs +│ ├── node-bootstrap.log +│ ├── ipfs-bootstrap.log +│ └── gateway.log +└── .branch # Saved branch preference +``` + +### Uninstall + +Remove all production services (preserves data and configs): + +```bash +sudo dbn prod uninstall +``` + +This stops and removes all systemd services but keeps `/home/debros/.debros/` intact. To completely remove: + +```bash +sudo dbn prod uninstall +sudo rm -rf /home/debros/.debros +``` + +### Production Troubleshooting + +#### Services Not Starting + +```bash +# Check service status +systemctl status debros-node-bootstrap + +# View detailed logs +journalctl -u debros-node-bootstrap -n 100 + +# Check log files +tail -f /home/debros/.debros/logs/node-bootstrap.log +``` + +#### Configuration Issues + +```bash +# Verify configs exist +ls -la /home/debros/.debros/configs/ + +# Regenerate configs (preserves secrets) +sudo dbn prod upgrade --restart +``` + +#### IPFS AutoConf Errors + +If you see "AutoConf.Enabled=false but 'auto' placeholder is used" errors, the upgrade process should fix this automatically. If not: + +```bash +# Re-run upgrade to fix IPFS config +sudo dbn prod upgrade --restart +``` + +#### Port Conflicts + +```bash +# Check what's using ports +sudo lsof -i :4001 # P2P port +sudo lsof -i :5001 # RQLite HTTP +sudo lsof -i :6001 # Gateway +``` + +#### Reset Installation + +To start fresh (⚠️ **destroys all data**): + +```bash +sudo dbn prod uninstall +sudo rm -rf /home/debros/.debros +sudo dbn prod install --bootstrap --branch nightly +``` + ## Components & Ports - **Bootstrap node**: P2P `4001`, RQLite HTTP `5001`, Raft `7001` diff --git a/pkg/cli/prod_commands.go b/pkg/cli/prod_commands.go index ab27e91..f17c991 100644 --- a/pkg/cli/prod_commands.go +++ b/pkg/cli/prod_commands.go @@ -28,6 +28,12 @@ func HandleProdCommand(args []string) { handleProdUpgrade(subargs) case "status": handleProdStatus() + case "start": + handleProdStart() + case "stop": + handleProdStop() + case "restart": + handleProdRestart() case "logs": handleProdLogs(subargs) case "uninstall": @@ -59,6 +65,9 @@ func showProdHelp() { fmt.Printf(" --restart - Automatically restart services after upgrade\n") fmt.Printf(" --branch BRANCH - Git branch to use (main or nightly, uses saved preference if not specified)\n") fmt.Printf(" status - Show status of production services\n") + fmt.Printf(" start - Start all production services (requires root/sudo)\n") + fmt.Printf(" stop - Stop all production services (requires root/sudo)\n") + fmt.Printf(" restart - Restart all production services (requires root/sudo)\n") fmt.Printf(" logs - View production service logs\n") fmt.Printf(" Options:\n") fmt.Printf(" --follow - Follow logs in real-time\n") @@ -76,6 +85,10 @@ func showProdHelp() { fmt.Printf(" sudo dbn prod upgrade --restart\n\n") fmt.Printf(" # Upgrade and switch to nightly branch\n") fmt.Printf(" sudo dbn prod upgrade --restart --branch nightly\n\n") + fmt.Printf(" # Service management\n") + fmt.Printf(" sudo dbn prod start\n") + fmt.Printf(" sudo dbn prod stop\n") + fmt.Printf(" sudo dbn prod restart\n\n") fmt.Printf(" dbn prod status\n") fmt.Printf(" dbn prod logs node --follow\n") } @@ -452,6 +465,112 @@ func handleProdLogs(args []string) { } } +// getProductionServices returns a list of all DeBros production service names that exist +func getProductionServices() []string { + // All possible service names (both bootstrap and node variants) + allServices := []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", + } + + // Filter to only existing services by checking if unit file exists + var existing []string + for _, svc := range allServices { + unitPath := filepath.Join("/etc/systemd/system", svc+".service") + if _, err := os.Stat(unitPath); err == nil { + existing = append(existing, svc) + } + } + + return existing +} + +func handleProdStart() { + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "❌ Production commands must be run as root (use sudo)\n") + os.Exit(1) + } + + fmt.Printf("Starting all DeBros production services...\n") + + services := getProductionServices() + if len(services) == 0 { + fmt.Printf(" ⚠️ No DeBros services found\n") + return + } + + for _, svc := range services { + cmd := exec.Command("systemctl", "start", svc) + if err := cmd.Run(); err != nil { + fmt.Printf(" ⚠️ Failed to start %s: %v\n", svc, err) + } else { + fmt.Printf(" ✓ Started %s\n", svc) + } + } + + fmt.Printf("\n✅ All services started\n") +} + +func handleProdStop() { + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "❌ Production commands must be run as root (use sudo)\n") + os.Exit(1) + } + + fmt.Printf("Stopping all DeBros production services...\n") + + services := getProductionServices() + if len(services) == 0 { + fmt.Printf(" ⚠️ No DeBros services found\n") + return + } + + for _, svc := range services { + cmd := exec.Command("systemctl", "stop", svc) + if err := cmd.Run(); err != nil { + fmt.Printf(" ⚠️ Failed to stop %s: %v\n", svc, err) + } else { + fmt.Printf(" ✓ Stopped %s\n", svc) + } + } + + fmt.Printf("\n✅ All services stopped\n") +} + +func handleProdRestart() { + if os.Geteuid() != 0 { + fmt.Fprintf(os.Stderr, "❌ Production commands must be run as root (use sudo)\n") + os.Exit(1) + } + + fmt.Printf("Restarting all DeBros production services...\n") + + services := getProductionServices() + if len(services) == 0 { + fmt.Printf(" ⚠️ No DeBros services found\n") + return + } + + for _, svc := range services { + cmd := exec.Command("systemctl", "restart", svc) + if err := cmd.Run(); err != nil { + fmt.Printf(" ⚠️ Failed to restart %s: %v\n", svc, err) + } else { + fmt.Printf(" ✓ Restarted %s\n", svc) + } + } + + fmt.Printf("\n✅ All services restarted\n") +} + func handleProdUninstall() { if os.Geteuid() != 0 { fmt.Fprintf(os.Stderr, "❌ Production uninstall must be run as root (use sudo)\n") diff --git a/pkg/environments/production/installers.go b/pkg/environments/production/installers.go index a14f374..a62e63c 100644 --- a/pkg/environments/production/installers.go +++ b/pkg/environments/production/installers.go @@ -355,7 +355,7 @@ func (bi *BinaryInstaller) InstallSystemDependencies() error { } // InitializeIPFSRepo initializes an IPFS repository for a node -func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swarmKeyPath string) error { +func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swarmKeyPath string, apiPort, gatewayPort, swarmPort int) error { configPath := filepath.Join(ipfsRepoPath, "config") repoExists := false if _, err := os.Stat(configPath); err == nil { @@ -393,6 +393,13 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa swarmKeyExists = true } + // Configure IPFS addresses (API, Gateway, Swarm) by modifying the config file directly + // This ensures the ports are set correctly and avoids conflicts with RQLite on port 5001 + fmt.Fprintf(bi.logWriter.(interface{ Write([]byte) (int, error) }), " Configuring IPFS addresses (API: %d, Gateway: %d, Swarm: %d)...\n", apiPort, gatewayPort, swarmPort) + if err := bi.configureIPFSAddresses(ipfsRepoPath, apiPort, gatewayPort, swarmPort); err != nil { + return fmt.Errorf("failed to configure IPFS addresses: %w", err) + } + // Always disable AutoConf for private swarm when swarm.key is present // This is critical - IPFS will fail to start if AutoConf is enabled on a private network // We do this even for existing repos to fix repos initialized before this fix was applied @@ -437,6 +444,44 @@ func (bi *BinaryInstaller) InitializeIPFSRepo(nodeType, ipfsRepoPath string, swa return nil } +// configureIPFSAddresses configures the IPFS API, Gateway, and Swarm addresses in the config file +func (bi *BinaryInstaller) configureIPFSAddresses(ipfsRepoPath string, apiPort, gatewayPort, swarmPort int) error { + configPath := filepath.Join(ipfsRepoPath, "config") + + // Read existing config + data, err := os.ReadFile(configPath) + if err != nil { + return fmt.Errorf("failed to read IPFS config: %w", err) + } + + var config map[string]interface{} + if err := json.Unmarshal(data, &config); err != nil { + return fmt.Errorf("failed to parse IPFS config: %w", err) + } + + // Set Addresses + config["Addresses"] = map[string]interface{}{ + "API": []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", apiPort)}, + "Gateway": []string{fmt.Sprintf("/ip4/127.0.0.1/tcp/%d", gatewayPort)}, + "Swarm": []string{ + fmt.Sprintf("/ip4/0.0.0.0/tcp/%d", swarmPort), + fmt.Sprintf("/ip6/::/tcp/%d", swarmPort), + }, + } + + // Write config back + updatedData, err := json.MarshalIndent(config, "", " ") + if err != nil { + return fmt.Errorf("failed to marshal IPFS config: %w", err) + } + + if err := os.WriteFile(configPath, updatedData, 0600); err != nil { + return fmt.Errorf("failed to write IPFS config: %w", err) + } + + return nil +} + // InitializeIPFSClusterConfig initializes IPFS Cluster configuration // This runs `ipfs-cluster-service init` to create the service.json configuration file. // For existing installations, it ensures the cluster secret is up to date. diff --git a/pkg/environments/production/orchestrator.go b/pkg/environments/production/orchestrator.go index ad30886..4231221 100644 --- a/pkg/environments/production/orchestrator.go +++ b/pkg/environments/production/orchestrator.go @@ -263,8 +263,9 @@ func (ps *ProductionSetup) Phase2cInitializeServices(nodeType string) error { dataDir := filepath.Join(ps.debrosDir, "data", nodeType) // Initialize IPFS repo with correct path structure + // Use port 4501 for API (to avoid conflict with RQLite on 5001), 8080 for gateway (standard), 4001 for swarm ipfsRepoPath := filepath.Join(dataDir, "ipfs", "repo") - if err := ps.binaryInstaller.InitializeIPFSRepo(nodeType, ipfsRepoPath, filepath.Join(ps.debrosDir, "secrets", "swarm.key")); err != nil { + if err := ps.binaryInstaller.InitializeIPFSRepo(nodeType, ipfsRepoPath, filepath.Join(ps.debrosDir, "secrets", "swarm.key"), 4501, 8080, 4001); err != nil { return fmt.Errorf("failed to initialize IPFS repo: %w", err) }