package main import ( "context" "errors" "flag" "fmt" "os" "os/signal" "path/filepath" "syscall" "time" "Moonshark/core/http" "Moonshark/core/routers" "Moonshark/core/runner" "Moonshark/core/sessions" "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 cleanupFuncs []func() error scriptMode bool } func main() { configPath := flag.String("config", "config.lua", "Path to configuration file") scriptPath := flag.String("script", "", "Path to Lua script to execute once") debugFlag := flag.Bool("debug", false, "Enable debug mode") flag.Parse() // Initialize logger with basic settings first logger.InitGlobalLogger(logger.LevelInfo, true, false) scriptMode := *scriptPath != "" var moonshark *Moonshark var err error if scriptMode { logger.Server("Moonshark script execution mode 🦈") moonshark, err = initScriptMode(*configPath, *debugFlag) } else { logger.Server("Moonshark server mode 🦈") moonshark, err = initServerMode(*configPath, *debugFlag) } if err != nil { logger.Fatal("Initialization failed: %v", err) } defer func() { if err := moonshark.Shutdown(); err != nil { logger.Error("Error during shutdown: %v", err) os.Exit(1) } }() if scriptMode { // Run the script and exit if err := moonshark.RunScript(*scriptPath); err != nil { logger.Fatal("Script execution failed: %v", err) } return } // Start the server if err := moonshark.Start(); err != nil { logger.Fatal("Failed to start server: %v", err) } // Wait for shutdown signal stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) <-stop fmt.Print("\n") logger.Server("Shutdown signal received") } // initScriptMode initializes minimal components needed for script execution func initScriptMode(configPath string, debug bool) (*Moonshark, error) { moonshark := &Moonshark{scriptMode: true} // Load config (minimal validation for script mode) cfg, err := config.Load(configPath) if err != nil { logger.Warning("Config load failed: %v, using defaults", err) cfg = config.New() } moonshark.Config = cfg // Setup logging setupLogging(cfg, debug) // Only initialize the Lua runner with required paths runnerOpts := []runner.RunnerOption{ runner.WithPoolSize(1), // Only need one state for script mode runner.WithLibDirs(cfg.Dirs.Libs...), runner.WithFsDir(cfg.Dirs.FS), runner.WithDataDir(cfg.Dirs.Data), } moonshark.LuaRunner, err = runner.NewRunner(runnerOpts...) if err != nil { return nil, fmt.Errorf("failed to initialize Lua runner: %v", err) } logger.Debug("Script mode initialized with minimized components") return moonshark, nil } // initServerMode initializes all components needed for server operation func initServerMode(configPath string, debug bool) (*Moonshark, error) { moonshark := &Moonshark{scriptMode: false} cfg, err := config.Load(configPath) if err != nil { logger.Warning("Config load failed: %v, using defaults", err) cfg = config.New() } moonshark.Config = cfg if debug { cfg.Server.Debug = true } setupLogging(cfg, debug) if err := initRouters(moonshark); err != nil { return nil, err } if err := initRunner(moonshark); err != nil { return nil, err } if err := setupWatchers(moonshark); err != nil { logger.Warning("Watcher setup failed: %v", err) } moonshark.HTTPServer = http.New( moonshark.LuaRouter, moonshark.StaticRouter, moonshark.LuaRunner, cfg.Server.HTTPLogging, cfg.Server.Debug, cfg.Dirs.Override, cfg, ) return moonshark, nil } // RunScript executes a Lua script in the sandbox environment func (s *Moonshark) RunScript(scriptPath string) error { scriptPath, err := filepath.Abs(scriptPath) if err != nil { return fmt.Errorf("failed to resolve script path: %v", err) } if _, err := os.Stat(scriptPath); os.IsNotExist(err) { return fmt.Errorf("script file not found: %s", scriptPath) } logger.Server("Executing: %s", scriptPath) resp, err := s.LuaRunner.RunScriptFile(scriptPath) if err != nil { return fmt.Errorf("execution failed: %v", err) } if resp != nil && resp.Body != nil { logger.Server("Script result: %v", resp.Body) } else { logger.Server("Script executed successfully (no return value)") } return nil } // Start starts the HTTP server func (s *Moonshark) Start() error { if s.scriptMode { return errors.New("cannot start server in script mode") } logger.Server("Surf's up on port %d!", s.Config.Server.Port) 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 Moonshark func (s *Moonshark) Shutdown() error { logger.Server("Shutting down...") if !s.scriptMode && s.HTTPServer != nil { ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second) defer cancel() if err := s.HTTPServer.Shutdown(ctx); err != nil { logger.Error("HTTP server shutdown error: %v", err) } } for _, cleanup := range s.cleanupFuncs { if err := cleanup(); err != nil { logger.Warning("Cleanup error: %v", err) } } if s.LuaRunner != nil { s.LuaRunner.Close() } logger.Server("Shutdown complete") return nil } func setupLogging(cfg *config.Config, debug bool) { switch cfg.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) } if debug || cfg.Server.Debug { logger.EnableDebug() logger.Debug("Debug logging enabled") } } func dirExists(path string) bool { info, err := os.Stat(path) if err != nil { return false } return info.IsDir() } func initRouters(s *Moonshark) error { if !dirExists(s.Config.Dirs.Routes) { return fmt.Errorf("routes directory doesn't exist: %s", s.Config.Dirs.Routes) } var err error s.LuaRouter, err = routers.NewLuaRouter(s.Config.Dirs.Routes) if err != nil { if errors.Is(err, routers.ErrRoutesCompilationErrors) { // Non-fatal, some routes failed logger.Warning("Some routes failed to compile") if failedRoutes := s.LuaRouter.ReportFailedRoutes(); len(failedRoutes) > 0 { for _, re := range failedRoutes { logger.Error("Route %s %s: %v", re.Method, re.Path, re.Err) } } } else { return fmt.Errorf("lua router init failed: %v", err) } } logger.Info("Lua router initialized: %s", s.Config.Dirs.Routes) if dirExists(s.Config.Dirs.Static) { s.StaticRouter, err = routers.NewStaticRouter(s.Config.Dirs.Static) if err != nil { return fmt.Errorf("static router init failed: %v", err) } logger.Info("Static router initialized: %s", s.Config.Dirs.Static) } else { logger.Warning("Static directory not found: %s", s.Config.Dirs.Static) } return nil } func initRunner(s *Moonshark) error { if !dirExists(s.Config.Dirs.Override) { logger.Warning("Override directory not found: %s", s.Config.Dirs.Override) s.Config.Dirs.Override = "" } for _, dir := range s.Config.Dirs.Libs { if !dirExists(dir) { logger.Warning("Lib directory not found: %s", dir) } } sessionManager := sessions.GlobalSessionManager sessionManager.SetCookieOptions( "MoonsharkSID", "/", "", false, true, 86400, ) poolSize := s.Config.Runner.PoolSize if s.scriptMode { poolSize = 1 // Only need one state for script mode } runnerOpts := []runner.RunnerOption{ runner.WithPoolSize(poolSize), runner.WithLibDirs(s.Config.Dirs.Libs...), runner.WithFsDir(s.Config.Dirs.FS), runner.WithDataDir(s.Config.Dirs.Data), } var err error s.LuaRunner, err = runner.NewRunner(runnerOpts...) if err != nil { return fmt.Errorf("lua runner init failed: %v", err) } logger.Server("Lua runner initialized with pool size: %d", poolSize) return nil } func setupWatchers(s *Moonshark) error { manager := watchers.GetWatcherManager() // Watch routes directory routeWatcher, err := watchers.WatchLuaRouter(s.LuaRouter, s.LuaRunner, s.Config.Dirs.Routes) if err != nil { logger.Warning("Routes directory watch failed: %v", err) } else { routesDir := routeWatcher.GetDir() s.cleanupFuncs = append(s.cleanupFuncs, func() error { return manager.UnwatchDirectory(routesDir) }) } // Watch module directories moduleWatchers, err := watchers.WatchLuaModules(s.LuaRunner, s.Config.Dirs.Libs) if err != nil { logger.Warning("Module directories watch failed: %v", err) } else { for _, watcher := range moduleWatchers { dirPath := watcher.GetDir() s.cleanupFuncs = append(s.cleanupFuncs, func() error { return manager.UnwatchDirectory(dirPath) }) } logger.Info("Watching %d module directories", len(moduleWatchers)) } return nil }