package core import ( "context" "errors" "fmt" "time" "Moonshark/core/http" "Moonshark/core/routers" "Moonshark/core/runner" "Moonshark/core/sessions" "Moonshark/core/utils" "Moonshark/core/utils/config" "Moonshark/core/utils/logger" "Moonshark/core/watchers" ) // Moonshark represents the server and all its dependencies type Moonshark struct { Config *config.Config LuaRouter *routers.LuaRouter StaticRouter *routers.StaticRouter LuaRunner *runner.Runner HTTPServer *http.Server // Clean-up functions for watchers cleanupFuncs []func() error } // NewMoonshark creates a new Moonshark server instance func NewMoonshark(configPath string) (*Moonshark, error) { // Initialize server struct server := &Moonshark{} // Load configuration if err := server.loadConfig(configPath); err != nil { return nil, err } // Configure logging server.setupLogging() // Initialize routers if err := server.initRouters(); err != nil { return nil, err } // Initialize Lua runner if err := server.initRunner(); err != nil { return nil, err } // Set up file watchers if err := server.setupWatchers(); err != nil { logger.Warning("Error setting up watchers: %v", err) } // Create HTTP server server.HTTPServer = http.New( server.LuaRouter, server.StaticRouter, server.LuaRunner, server.Config.Server.HTTPLogging, server.Config.Server.Debug, server.Config.Dirs.Override, server.Config, ) return server, nil } // loadConfig loads the configuration file func (s *Moonshark) loadConfig(configPath string) error { var err error // Load configuration from file s.Config, err = config.Load(configPath) if err != nil { logger.Warning("Wipeout! Couldn't load config file: %v", err) logger.WarningCont("Rolling with the default setup") s.Config = config.New() } return nil } // setupLogging configures the logger based on config settings func (s *Moonshark) setupLogging() { // Set log level from config switch s.Config.Server.LogLevel { case "warn": logger.SetLevel(logger.LevelWarning) case "error": logger.SetLevel(logger.LevelError) case "server": logger.SetLevel(logger.LevelServer) case "fatal": logger.SetLevel(logger.LevelFatal) default: logger.SetLevel(logger.LevelInfo) } // Set debug mode if configured if s.Config.Server.Debug { logger.EnableDebug() // Force debug logs regardless of level logger.Debug("Debug mode is ready to party, bro") } } // initRouters initializes the Lua and static routers func (s *Moonshark) initRouters() error { // Ensure directories exist if err := utils.EnsureDir(s.Config.Dirs.Routes); err != nil { return fmt.Errorf("routes directory doesn't exist and could not be created: %v", err) } if err := utils.EnsureDir(s.Config.Dirs.Static); err != nil { return fmt.Errorf("static directory doesn't exist and could not be created: %v", err) } // Initialize Lua router for dynamic routes var err error s.LuaRouter, err = routers.NewLuaRouter(s.Config.Dirs.Routes) if err != nil { // Check if this is a compilation warning or a more serious error if errors.Is(err, routers.ErrRoutesCompilationErrors) { // Some routes failed to compile, but router is still usable logger.Warning("Some Lua routes failed to compile. Check logs for details.") // Log details about each failed route if failedRoutes := s.LuaRouter.ReportFailedRoutes(); len(failedRoutes) > 0 { for _, re := range failedRoutes { logger.Error("Route %s %s failed to compile: %v", re.Method, re.Path, re.Err) } } } else { // More serious error that prevents router initialization return fmt.Errorf("failed to initialize Lua router: %v", err) } } logger.Info("Lua router is stoked and riding routes from %s", s.Config.Dirs.Routes) // Initialize static file router s.StaticRouter, err = routers.NewStaticRouter(s.Config.Dirs.Static) if err != nil { return fmt.Errorf("failed to initialize static router: %v", err) } logger.Info("Static router catching waves with files from %s", s.Config.Dirs.Static) s.StaticRouter.EnableDebugLog() return nil } // initRunner initializes the Lua runner func (s *Moonshark) initRunner() error { // Ensure override directory exists if err := utils.EnsureDir(s.Config.Dirs.Override); err != nil { logger.Warning("Override directory doesn't exist and could not be created: %v", err) s.Config.Dirs.Override = "" // Disable overrides if directory can't be created } // Ensure lib directories exist for _, dir := range s.Config.Dirs.Libs { if err := utils.EnsureDir(dir); err != nil { logger.Warning("Lib directory doesn't exist and could not be created: %v", err) } } // Initialize session manager sessionManager := sessions.GlobalSessionManager // Configure session cookies sessionManager.SetCookieOptions( "MoonsharkSID", // name "/", // path "", // domain false, // secure true, // httpOnly 86400, // maxAge (1 day) ) // Set up runner options runnerOpts := []runner.RunnerOption{ runner.WithPoolSize(s.Config.Runner.PoolSize), runner.WithLibDirs(s.Config.Dirs.Libs...), runner.WithSessionManager(sessionManager), http.WithCSRFProtection(), } // Add debug option conditionally if s.Config.Server.Debug { runnerOpts = append(runnerOpts, runner.WithDebugEnabled()) logger.Debug("Debug logging enabled for Lua runner") } // Initialize the runner var err error s.LuaRunner, err = runner.NewRunner(runnerOpts...) if err != nil { return fmt.Errorf("failed to initialize Lua runner: %v", err) } logger.Server("Lua runner waxing up with a pool size of %d", s.Config.Runner.PoolSize) return nil } // setupWatchers initializes and starts all configured file watchers func (s *Moonshark) setupWatchers() error { // Set up watcher for Lua routes luaRouterWatcher, err := watchers.WatchLuaRouter(s.LuaRouter, s.LuaRunner, s.Config.Dirs.Routes) if err != nil { logger.Warning("Failed to watch routes directory: %v", err) } else { s.cleanupFuncs = append(s.cleanupFuncs, luaRouterWatcher.Close) } // Set up watchers for Lua modules libraries moduleWatchers, err := watchers.WatchLuaModules(s.LuaRunner, s.Config.Dirs.Libs) if err != nil { logger.Warning("Failed to watch Lua module directories: %v", err) } else { for _, watcher := range moduleWatchers { w := watcher // Capture variable for closure s.cleanupFuncs = append(s.cleanupFuncs, w.Close) } logger.Info("File watchers active for %d Lua module directories", len(moduleWatchers)) } return nil } // Start starts the HTTP server func (s *Moonshark) Start() error { logger.Server("Surf's up on port %d!", s.Config.Server.Port) // Log HTTP logging status if s.Config.Server.HTTPLogging { logger.ServerCont("HTTP logging is turned on - watch those waves") } else { logger.ServerCont("HTTP logging is turned off - waves are flat bro") } // Start the server in a non-blocking way go func() { if err := s.HTTPServer.ListenAndServe(fmt.Sprintf(":%d", s.Config.Server.Port)); err != nil { if err.Error() != "http: Server closed" { logger.Error("Server error: %v", err) } } }() return nil } // Shutdown gracefully shuts down the server func (s *Moonshark) Shutdown() error { logger.Server("Shutting down server...") ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := s.HTTPServer.Shutdown(ctx); err != nil { logger.Error("Server shutdown error: %v", err) return err } for _, cleanup := range s.cleanupFuncs { if err := cleanup(); err != nil { logger.Warning("Cleanup error: %v", err) } } s.LuaRunner.Close() logger.ServerCont("Server stopped") return nil }