Files
futriix/cmd/futriis/main.go

224 lines
7.7 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/*
* 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.
// Управляет жизненным циклом приложения.
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("futriis database starting...")
store := storage.NewStorage(cfg.Storage.PageSizeMB, logger)
// Устанавливаем глобальное хранилище для транзакций
storage.SetGlobalStorage(store)
// Инициализация менеджера транзакций
if err := storage.InitTransactionManager("futriis.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 <user> <pass>' 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))
}