mirror of
https://github.com/DeBrosOfficial/network.git
synced 2025-12-11 10:18:50 +00:00
feat: add RQLite command support and help documentation
- Introduced a new RQLite command in the CLI to handle RQLite-related operations. - Implemented the 'fix' subcommand to automatically repair common RQLite cluster issues, including correcting misconfigured join addresses and cleaning stale raft state. - Updated help documentation to include RQLite commands and their usage.
This commit is contained in:
parent
ed7f4ae3d9
commit
30d18aca02
@ -1,5 +1,23 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Get the directory where this hook is located
|
||||
HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
REPO_ROOT="$(cd "$HOOK_DIR/.." && pwd)"
|
||||
CHANGELOG_SCRIPT="$REPO_ROOT/scripts/update_changelog.sh"
|
||||
|
||||
# Update changelog before push
|
||||
if [ -f "$CHANGELOG_SCRIPT" ]; then
|
||||
echo -e "\nUpdating changelog..."
|
||||
bash "$CHANGELOG_SCRIPT"
|
||||
changelog_status=$?
|
||||
if [ $changelog_status -ne 0 ]; then
|
||||
echo "Push aborted: changelog update failed."
|
||||
exit 1
|
||||
fi
|
||||
else
|
||||
echo "Warning: changelog update script not found at $CHANGELOG_SCRIPT"
|
||||
fi
|
||||
|
||||
echo -e "\nRunning tests:"
|
||||
go test ./... # Runs all tests in your repo
|
||||
status=$?
|
||||
|
||||
@ -108,6 +108,10 @@ func main() {
|
||||
}
|
||||
cli.HandleConnectCommand(args[0], timeout)
|
||||
|
||||
// RQLite commands
|
||||
case "rqlite":
|
||||
cli.HandleRQLiteCommand(args)
|
||||
|
||||
// Help
|
||||
case "help", "--help", "-h":
|
||||
showHelp()
|
||||
@ -175,6 +179,9 @@ func showHelp() {
|
||||
fmt.Printf("🗄️ Database:\n")
|
||||
fmt.Printf(" query <sql> 🔐 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 <topic> <msg> 🔐 Publish message\n")
|
||||
fmt.Printf(" pubsub subscribe <topic> 🔐 Subscribe to topic\n")
|
||||
|
||||
327
pkg/cli/rqlite_commands.go
Normal file
327
pkg/cli/rqlite_commands.go
Normal file
@ -0,0 +1,327 @@
|
||||
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 <subcommand> [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
|
||||
}
|
||||
@ -328,7 +328,6 @@ func (n *Node) startLibP2P() error {
|
||||
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(),
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user