package main import ( "bufio" "crypto/rand" "encoding/base64" "encoding/json" "fmt" "log" "os" "strconv" "strings" "sync" "time" "github.com/fsnotify/fsnotify" ) type Config struct { Port int AuthCode string Peers []string PeerID string OpenSearch OpenSearchConfig } type OpenSearchConfig struct { Domain string } var defaultConfig = Config{ Port: 5000, OpenSearch: OpenSearchConfig{ Domain: "localhost", }, } const configFilePath = "config.json" var config Config var configLock sync.RWMutex func main() { err := initConfig() if err != nil { fmt.Println("Error during initialization:", err) return } loadNodeConfig() go startFileWatcher() go checkMasterHeartbeat() if config.AuthCode == "" { config.AuthCode = generateStrongRandomString(64) fmt.Printf("Generated connection code: %s\n", config.AuthCode) saveConfig(config) } // Generate Host ID hostID, nodeErr := generateHostID() if nodeErr != nil { log.Fatalf("Failed to generate host ID: %v", nodeErr) } config.PeerID = hostID if len(config.Peers) > 0 { time.Sleep(2 * time.Second) // Give some time for connections to establish startElection() } go startNodeClient() runServer() } func initConfig() error { if _, err := os.Stat(configFilePath); os.IsNotExist(err) { return createConfig() } fmt.Println("Configuration file already exists.") config = loadConfig() return nil } func createConfig() error { reader := bufio.NewReader(os.Stdin) fmt.Println("Configuration file not found.") fmt.Print("Do you want to use default values? (yes/no): ") useDefaults, _ := reader.ReadString('\n') config := defaultConfig if useDefaults != "yes\n" { fmt.Print("Enter port (default 5000): ") portStr, _ := reader.ReadString('\n') if portStr != "\n" { port, err := strconv.Atoi(portStr[:len(portStr)-1]) if err != nil { return err } config.Port = port } fmt.Print("Enter your domain address (e.g., domain.com): ") domain, _ := reader.ReadString('\n') if domain != "\n" { config.OpenSearch.Domain = domain[:len(domain)-1] } fmt.Print("Do you want to connect to other nodes? (yes/no): ") connectNodes, _ := reader.ReadString('\n') if strings.TrimSpace(connectNodes) == "yes" { fmt.Println("Enter peer addresses (comma separated, e.g., /ip4/127.0.0.1/tcp/5000,/ip4/127.0.0.1/tcp/5001): ") peersStr, _ := reader.ReadString('\n') if peersStr != "\n" { config.Peers = strings.Split(strings.TrimSpace(peersStr), ",") } } } if config.AuthCode == "" { config.AuthCode = generateStrongRandomString(64) fmt.Printf("Generated connection code: %s\n", config.AuthCode) } saveConfig(config) return nil } func saveConfig(config Config) { file, err := os.Create(configFilePath) if err != nil { fmt.Println("Error creating config file:", err) return } defer file.Close() configData, err := json.MarshalIndent(config, "", " ") if err != nil { fmt.Println("Error marshalling config data:", err) return } _, err = file.Write(configData) if err != nil { fmt.Println("Error writing to config file:", err) } } func loadConfig() Config { configFile, err := os.Open(configFilePath) if err != nil { log.Fatalf("Error opening config file: %v", err) } defer configFile.Close() var config Config if err := json.NewDecoder(configFile).Decode(&config); err != nil { log.Fatalf("Error decoding config file: %v", err) } return config } func generateStrongRandomString(length int) string { bytes := make([]byte, length) _, err := rand.Read(bytes) if err != nil { log.Fatalf("Error generating random string: %v", err) } return base64.URLEncoding.EncodeToString(bytes)[:length] } func startFileWatcher() { watcher, err := fsnotify.NewWatcher() if err != nil { log.Fatal(err) } go func() { defer watcher.Close() for { select { case event, ok := <-watcher.Events: if !ok { return } if event.Op&fsnotify.Write == fsnotify.Write { log.Println("Modified file:", event.Name) configLock.Lock() config = loadConfig() configLock.Unlock() // Perform your logic here to handle the changes in the config file } case err, ok := <-watcher.Errors: if !ok { return } log.Println("Error:", err) } } }() err = watcher.Add(configFilePath) if err != nil { log.Fatal(err) } }