diff --git a/cmd/futriis/main.go b/cmd/futriis/main.go new file mode 100644 index 0000000..39508e3 --- /dev/null +++ b/cmd/futriis/main.go @@ -0,0 +1,191 @@ +/* + * 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 +// Назначение: Точка входа в приложение СУБД futriis. Инициализирует все компоненты: +// конфигурацию, логгер, хранилище, Raft координатор, ACL менеджер, HTTP API, WebUI и REPL. +// Управляет жизненным циклом приложения. + + +// Файл: cmd/futriis/main.go +// Назначение: Точка входа в приложение СУБД futriis. Инициализирует все компоненты: +// конфигурацию, логгер, хранилище, 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/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("futriis database starting...") + + store := storage.NewStorage(cfg.Storage.PageSizeMB, logger) + + // Инициализация ACL менеджера + aclManager := acl.NewACLManager() + logger.Info("ACL manager initialized") + + 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) + } + + // Запуск HTTP API сервера + httpPort := 8080 + 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)) + // Убрано: вывод строки "Web UI available at http://localhost:9080" в консоль + } + + displayBanner(cfg.Cluster.Name, cfg.WebUI.Enabled, webUIPort) + + replInstance := repl.NewRepl(store, raftCoordinator, logger, cfg) + + 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") + 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) { + 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 (with curl only): http://localhost:8080/api/", + } + + if webUIEnabled { + // Строка с Web UI убрана, оставлена только для внутреннего использования + // 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", + }...) + + 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)) +}