package main import ( "context" "errors" "flag" "fmt" "os" "os/signal" "path/filepath" "strconv" "syscall" "time" "Moonshark/http" "Moonshark/router" "Moonshark/runner" "Moonshark/sessions" "Moonshark/utils/color" "Moonshark/utils/config" "Moonshark/utils/logger" "Moonshark/utils/metadata" "Moonshark/watchers" ) // Moonshark represents the server and all its dependencies type Moonshark struct { Config *config.Config LuaRouter *router.LuaRouter LuaRunner *runner.Runner HTTPServer *http.Server cleanupFuncs []func() error scriptMode bool } func main() { configPath := flag.String("config", "config.lua", "Path to configuration file") debugFlag := flag.Bool("debug", false, "Enable debug mode") scriptPath := flag.String("script", "", "Path to Lua script to execute once") flag.Parse() scriptMode := *scriptPath != "" banner() mode := "" if scriptMode { mode = "[Script Mode]" } else { mode = "[Server Mode]" } fmt.Printf("%s %s\n\n", color.Apply(mode, color.Gray), color.Apply("v"+metadata.Version, color.Blue)) // Initialize logger logger.InitGlobalLogger(true, false) // Load config cfg, err := config.Load(*configPath) if err != nil { logger.Warning("Config load failed: %v, using defaults", color.Apply(err.Error(), color.Red)) cfg = config.New() } // Setup logging with debug mode if *debugFlag || cfg.Server.Debug { logger.EnableDebug() logger.Debug("Debug logging enabled") } var moonshark *Moonshark if scriptMode { moonshark, err = initScriptMode(cfg) } else { moonshark, err = initServerMode(cfg, *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.Info("Shutdown signal received") } // initScriptMode initializes minimal components needed for script execution func initScriptMode(cfg *config.Config) (*Moonshark, error) { moonshark := &Moonshark{ Config: cfg, scriptMode: true, } // 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), } var err error 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(cfg *config.Config, debug bool) (*Moonshark, error) { moonshark := &Moonshark{ Config: cfg, scriptMode: false, } if debug { cfg.Server.Debug = true } if err := initLuaRouter(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) } // Get static directory - empty string if it doesn't exist staticDir := "" if dirExists(cfg.Dirs.Static) { staticDir = cfg.Dirs.Static logger.Info("Static files enabled: %s", color.Apply(staticDir, color.Yellow)) } else { logger.Warning("Static directory not found: %s", color.Apply(cfg.Dirs.Static, color.Yellow)) } moonshark.HTTPServer = http.New( moonshark.LuaRouter, staticDir, moonshark.LuaRunner, cfg.Server.HTTPLogging, cfg.Server.Debug, cfg.Dirs.Override, cfg, ) // For development, disable caching. For production, enable it if cfg.Server.Debug { moonshark.HTTPServer.SetStaticCaching(0) // No caching in debug mode } else { moonshark.HTTPServer.SetStaticCaching(1 * time.Hour) // Cache for 1 hour in production } 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.Info("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.Info("Script result: %v", resp.Body) } else { logger.Info("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.Info("Surf's up on port %s!", color.Apply(strconv.Itoa(s.Config.Server.Port), color.Cyan)) 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.Info("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() } if err := runner.CleanupEnv(); err != nil { logger.Warning("Environment cleanup failed: %v", err) } logger.Info("Shutdown complete") return nil } func dirExists(path string) bool { info, err := os.Stat(path) if err != nil { return false } return info.IsDir() } func initLuaRouter(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 = router.NewLuaRouter(s.Config.Dirs.Routes) if err != nil { if errors.Is(err, router.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("LuaRouter is g2g! %s", color.Set(s.Config.Dirs.Routes, color.Yellow)) return nil } func initRunner(s *Moonshark) error { if !dirExists(s.Config.Dirs.Override) { logger.Warning("Override directory not found... %s", color.Apply(s.Config.Dirs.Override, color.Yellow)) s.Config.Dirs.Override = "" } for _, dir := range s.Config.Dirs.Libs { if !dirExists(dir) { logger.Warning("Lib directory not found... %s", color.Apply(dir, color.Yellow)) } } // Initialize environment manager if err := runner.InitEnv(s.Config.Dirs.Data); err != nil { logger.Warning("Environment initialization failed: %v", err) } 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.Info("LuaRunner is g2g with %s states!", color.Apply(strconv.Itoa(poolSize), color.Yellow)) 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) }) } plural := "" if len(moduleWatchers) == 1 { plural = "directory" } else { plural = "directories" } logger.Info("Watching %s module %s.", color.Apply(strconv.Itoa(len(moduleWatchers)), color.Yellow), plural) } return nil } func banner() { banner := ` _____ _________.__ __ / \ ____ ____ ____ / _____/| |__ _____ _______| | __ / \ / \ / _ \ / _ \ / \ \_____ \ | | \\__ \\_ __ \ |/ / / Y ( <_> | <_> ) | \/ \| Y \/ __ \| | \/ < \____|__ /\____/ \____/|___| /_______ /|___| (____ /__| |__|_ \ \/ \/ \/ \/ \/ \/ ` fmt.Println(color.Apply(banner, color.Blue)) }