2026-04-22 19:15:01 +00:00
|
|
|
/*
|
|
|
|
|
* 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/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))
|
2026-04-22 21:29:19 +00:00
|
|
|
// utils.PrintInfo(fmt.Sprintf("Web UI available at http://localhost:%d", webUIPort))
|
2026-04-22 19:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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,
|
2026-04-22 21:29:19 +00:00
|
|
|
" HTTP API: http://localhost:8080/api/",
|
2026-04-22 19:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if webUIEnabled {
|
2026-04-22 21:29:19 +00:00
|
|
|
bannerLines = append(bannerLines, fmt.Sprintf(" Web UI: http://localhost:%d/", webUIPort))
|
2026-04-22 19:15:01 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
bannerLines = append(bannerLines, []string{
|
|
|
|
|
" Type 'quit' or 'exit' to quit",
|
|
|
|
|
" Type 'status' to see cluster status",
|
|
|
|
|
" Type 'acl login <user> <pass>' 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))
|
2026-04-22 21:29:19 +00:00
|
|
|
}
|