/* * Copyright 2026 Safronov Grigorii * * Licensed under the CDDL, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * https://opensource.org/licenses/CDDL-1.0 */ // Файл: cmd/futriis/main.go // Назначение: Точка входа в приложение СУБД futriix. Инициализирует все компоненты: // конфигурацию, логгер, хранилище, Raft координатор, ACL менеджер, HTTP API, WebUI и REPL. // Управляет жизненным циклом приложения. package main import ( "encoding/json" "fmt" "os" "os/signal" "syscall" "time" "futriis/internal/acl" "futriis/internal/api" "futriis/internal/cluster" "futriis/internal/config" "futriis/internal/log" "futriis/internal/plugin" "futriis/internal/repl" "futriis/internal/storage" "futriis/pkg/utils" ) func main() { utils.SetColorEnabled(true) cfg, err := config.LoadConfig("config.toml") if err != nil { utils.PrintError("Failed to load config: " + err.Error()) os.Exit(1) } logger, err := log.NewLogger(cfg.Log.LogFile, cfg.Log.LogLevel) if err != nil { utils.PrintError("Failed to initialize logger: " + err.Error()) os.Exit(1) } defer logger.Close() logger.Info("futriix database starting...") store := storage.NewStorage(cfg.Storage.PageSizeMB, logger) // Устанавливаем глобальное хранилище для транзакций storage.SetGlobalStorage(store) // Инициализация менеджера транзакций if err := storage.InitTransactionManager("futriix.wal"); err != nil { logger.Warn("Failed to initialize transaction manager: " + err.Error()) } else { // Устанавливаем логгер для транзакций storage.SetTransactionLogger(logger) logger.Info("Transaction manager initialized") } // Инициализация ACL менеджера aclManager := acl.NewACLManager() logger.Info("ACL manager initialized") // Инициализация Raft координатора raftCoordinator, err := cluster.NewRaftCoordinator(cfg, logger) if err != nil { logger.Error("Failed to start Raft coordinator: " + err.Error()) utils.PrintError("Failed to start Raft coordinator: " + err.Error()) os.Exit(1) } if cfg.Cluster.Bootstrap || len(cfg.Cluster.Nodes) <= 1 { maxRetries := 10 for i := 0; i < maxRetries; i++ { if raftCoordinator.IsLeader() { break } time.Sleep(1 * time.Second) } } node := cluster.NewNode(cfg.Cluster.NodeIP, cfg.Cluster.NodePort, store, logger) maxRetries := 5 var registerErr error for i := 0; i < maxRetries; i++ { registerErr = raftCoordinator.RegisterNode(node) if registerErr == nil { break } if i < maxRetries-1 { time.Sleep(2 * time.Second) } } if registerErr != nil { logger.Error("Failed to register node: " + registerErr.Error()) utils.PrintError("Failed to register node: " + registerErr.Error()) os.Exit(1) } // Инициализация менеджера плагинов pluginManager := plugin.NewPluginManager(cfg.Plugins.ScriptDir, logger, store, cfg.Plugins.Enabled) if cfg.Plugins.Enabled { logger.Info(fmt.Sprintf("Plugin manager initialized, directory: %s", cfg.Plugins.ScriptDir)) // Автоматически запускаем все загруженные плагины plugins := pluginManager.ListPlugins() for _, p := range plugins { if err := pluginManager.StartPlugin(p.Name); err != nil { logger.Warn(fmt.Sprintf("Failed to start plugin %s: %v", p.Name, err)) } } } // Запуск HTTP API сервера с портом из конфигурации httpPort := cfg.API.Port httpServer := api.NewHTTPServer(httpPort, store, raftCoordinator, aclManager, logger) go func() { if err := httpServer.Start(); err != nil { logger.Error("HTTP server error: " + err.Error()) utils.PrintError("HTTP server error: " + err.Error()) } }() logger.Info(fmt.Sprintf("HTTP API server started on port %d", httpPort)) // Запуск WebUI сервера (если включён в конфигурации) webUIPort := cfg.WebUI.Port if webUIPort == 0 { webUIPort = 8081 } webUI := api.NewWebUIServer(webUIPort, cfg.WebUI.Enabled, store, raftCoordinator, aclManager, logger) go func() { if err := webUI.Start(); err != nil && cfg.WebUI.Enabled { logger.Error("Web UI error: " + err.Error()) utils.PrintError("Web UI error: " + err.Error()) } }() if cfg.WebUI.Enabled { logger.Info(fmt.Sprintf("Web UI started on port %d", webUIPort)) } displayBanner(cfg.Cluster.Name, cfg.WebUI.Enabled, webUIPort, httpPort) replInstance := repl.NewRepl(store, raftCoordinator, logger, cfg, aclManager, pluginManager) sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) go func() { <-sigChan utils.Println("\nReceived shutdown signal") logger.Info("Received shutdown signal") // Останавливаем все плагины if cfg.Plugins.Enabled && pluginManager != nil { plugins := pluginManager.ListPlugins() for _, p := range plugins { if err := pluginManager.StopPlugin(p.Name); err != nil { logger.Warn(fmt.Sprintf("Failed to stop plugin %s: %v", p.Name, err)) } } } httpServer.Stop() webUI.Stop() raftCoordinator.Stop() node.Stop() replInstance.Close() utils.DisableColorMode() os.Exit(0) }() if err := replInstance.Run(); err != nil { logger.Error("REPL error: " + err.Error()) utils.PrintError("REPL error: " + err.Error()) os.Exit(1) } } func displayBanner(clusterName string, webUIEnabled bool, webUIPort int, httpPort int) { utils.Println("") bannerLines := []string{ " futriix 3i²(by 02.04.2026) ", " Distributed Document-Store in-memory database with support lua plugins ", " Cluster status: enable (Raft consensus)", " Cluster name: " + clusterName, " HTTP API (for curl or wget utils only): http://localhost:" + fmt.Sprintf("%d", httpPort) + "/api/", } if webUIEnabled { bannerLines = append(bannerLines, fmt.Sprintf(" Web UI: http://localhost:%d/", webUIPort)) } bannerLines = append(bannerLines, []string{ " Type 'quit' or 'exit' to quit", " Type 'status' to see cluster status", " Type 'acl login ' to authenticate", " Type 'plugin list' to see loaded plugins", }...) for _, line := range bannerLines { utils.PrintInfo(line) } } // Вспомогательная функция для форматирования JSON func printJSON(data interface{}) { jsonData, err := json.MarshalIndent(data, "", " ") if err != nil { utils.PrintError("Failed to marshal JSON: " + err.Error()) return } fmt.Println(string(jsonData)) }