package main import ( "context" "errors" "flag" "fmt" "os" "os/signal" "path/filepath" "runtime" "strconv" "syscall" "time" "Moonshark/color" "Moonshark/http" "Moonshark/metadata" "Moonshark/router" "Moonshark/runner" "Moonshark/runner/lualibs" "Moonshark/sessions" "Moonshark/utils/config" "Moonshark/utils/logger" "Moonshark/watchers" fin "git.sharkk.net/Sharkk/Fin" ) type Moonshark struct { Config *config.Config LuaRouter *router.Router LuaRunner *runner.Runner HTTPServer *http.Server cleanupFuncs []func() error scriptMode bool } func main() { configPath := flag.String("config", "config", "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 != "" color.SetColors(color.DetectShellColors()) banner(scriptMode) cfg := config.New(readConfig(*configPath)) debug := *debugFlag || cfg.Server.Debug logger.Debug(debug) if debug { logger.Debugf("Debug logging enabled") } moonshark, err := newMoonshark(cfg, debug, scriptMode) if err != nil { logger.Fatalf("Initialization failed: %v", err) } defer func() { if err := moonshark.Shutdown(); err != nil { logger.Errorf("Error during shutdown: %v", err) os.Exit(1) } }() if scriptMode { if err := moonshark.RunScript(*scriptPath); err != nil { logger.Fatalf("Script execution failed: %v", err) } return } if err := moonshark.Start(); err != nil { logger.Fatalf("Failed to start server: %v", err) } stop := make(chan os.Signal, 1) signal.Notify(stop, os.Interrupt, syscall.SIGTERM) <-stop fmt.Print("\n") logger.Infof("Shutdown signal received") } func newMoonshark(cfg *config.Config, debug, scriptMode bool) (*Moonshark, error) { s := &Moonshark{Config: cfg, scriptMode: scriptMode} if debug { cfg.Server.Debug = true } poolSize := cfg.Runner.PoolSize if scriptMode { poolSize = 1 } if poolSize == 0 { poolSize = runtime.GOMAXPROCS(0) } // Initialize runner first (needed for both modes) if err := s.initRunner(poolSize); err != nil { return nil, err } if scriptMode { logger.Debugf("Script mode initialized") return s, nil } // Server mode: initialize router, watchers, and HTTP server if err := s.initRouter(); err != nil { return nil, err } s.setupWatchers() s.HTTPServer = http.New(s.LuaRouter, s.LuaRunner, cfg, debug) // Log static directory status if dirExists(cfg.Dirs.Static) { logger.Infof("Static files enabled: %s", color.Yellow(cfg.Dirs.Static)) } else { logger.Warnf("Static directory not found: %s", color.Yellow(cfg.Dirs.Static)) } return s, nil } func (s *Moonshark) initRunner(poolSize int) error { // Warn about missing directories but continue if !dirExists(s.Config.Dirs.Override) { logger.Warnf("Override directory not found... %s", color.Yellow(s.Config.Dirs.Override)) s.Config.Dirs.Override = "" } for _, dir := range s.Config.Dirs.Libs { if !dirExists(dir) { logger.Warnf("Lib directory not found... %s", color.Yellow(dir)) } } if err := lualibs.InitEnv(s.Config.Dirs.Data); err != nil { logger.Warnf("Environment initialization failed: %v", err) } sessions.GlobalSessionManager.SetCookieOptions("MoonsharkSID", "/", "", false, true, 86400) var err error s.LuaRunner, err = runner.NewRunner(poolSize, s.Config.Dirs.Data, s.Config.Dirs.FS, s.Config.Dirs.Libs) if err != nil { return fmt.Errorf("lua runner init failed: %v", err) } logger.Infof("LuaRunner is g2g with %s states!", color.Yellow(strconv.Itoa(poolSize))) return nil } func (s *Moonshark) initRouter() error { if err := os.MkdirAll(s.Config.Dirs.Routes, 0755); err != nil { return fmt.Errorf("failed to create routes directory: %w", err) } var err error s.LuaRouter, err = router.New(s.Config.Dirs.Routes) if err != nil { return fmt.Errorf("lua router init failed: %v", err) } logger.Infof("LuaRouter is g2g! %s", color.Yellow(s.Config.Dirs.Routes)) return nil } func (s *Moonshark) setupWatchers() { manager := watchers.GetWatcherManager() // Watch routes if routeWatcher, err := watchers.WatchLuaRouter(s.LuaRouter, s.LuaRunner, s.Config.Dirs.Routes); err != nil { logger.Warnf("Routes directory watch failed: %v", err) } else { routesDir := routeWatcher.GetDir() s.cleanupFuncs = append(s.cleanupFuncs, func() error { return manager.UnwatchDirectory(routesDir) }) } // Watch modules if moduleWatchers, err := watchers.WatchLuaModules(s.LuaRunner, s.Config.Dirs.Libs); err != nil { logger.Warnf("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 := "directory" if len(moduleWatchers) != 1 { plural = "directories" } logger.Infof("Watching %s module %s.", color.Yellow(strconv.Itoa(len(moduleWatchers))), plural) } } 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.Infof("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.Infof("Script result: %v", resp.Body) } else { logger.Infof("Script executed successfully (no return value)") } return nil } func (s *Moonshark) Start() error { if s.scriptMode { return errors.New("cannot start server in script mode") } logger.Infof("Surf's up on port %s!", color.Cyan(strconv.Itoa(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.Errorf("Server error: %v", err) } } }() return nil } func (s *Moonshark) Shutdown() error { logger.Infof("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.Errorf("HTTP server shutdown error: %v", err) } } for _, cleanup := range s.cleanupFuncs { if err := cleanup(); err != nil { logger.Warnf("Cleanup error: %v", err) } } if s.LuaRunner != nil { s.LuaRunner.Close() } if err := lualibs.CleanupEnv(); err != nil { logger.Warnf("Environment cleanup failed: %v", err) } logger.Infof("Shutdown complete") return nil } func dirExists(path string) bool { info, err := os.Stat(path) return err == nil && info.IsDir() } func banner(scriptMode bool) { if scriptMode { fmt.Println(color.Blue(fmt.Sprintf("Moonshark %s << Script Mode >>", metadata.Version))) return } banner := ` _____ _________.__ __ / \ ____ ____ ____ / _____/| |__ _____ _______| | __ / \ / \ / _ \ / _ \ / \ \_____ \ | | \\__ \\_ __ \ |/ / / Y ( <_> | <_> ) | \/ \| Y \/ __ \| | \/ < \____|__ /\____/ \____/|___| /_______ /|___| (____ /__| |__|_ \ %s \/ \/ \/ \/ \/ \/ ` fmt.Println(color.Blue(fmt.Sprintf(banner, metadata.Version))) } func readConfig(path string) *fin.Data { file, err := os.Open(path) if err != nil { logger.Errorf("Failed to open config file %s, using defaults", color.Yellow(path)) return fin.NewData() } defer file.Close() cfg, err := fin.Load(file) if err != nil { logger.Errorf("Failed to load config file %s, using defaults", color.Yellow(path)) } return cfg }