diff --git a/core/moonshark.go b/core/moonshark.go deleted file mode 100644 index 2c36f18..0000000 --- a/core/moonshark.go +++ /dev/null @@ -1,262 +0,0 @@ -package core - -import ( - "context" - "errors" - "fmt" - "os" - "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 - - // Clean-up functions for watchers - cleanupFuncs []func() error -} - -// dirExists checks if a directory exists without creating it -func dirExists(path string) bool { - info, err := os.Stat(path) - if err != nil { - return false - } - return info.IsDir() -} - -// NewMoonshark creates a new Moonshark server instance -func NewMoonshark(configPath string) (*Moonshark, error) { - server := &Moonshark{} - - if err := server.loadConfig(configPath); err != nil { - return nil, err - } - - server.setupLogging() - - if err := server.initRouters(); err != nil { - return nil, err - } - - if err := server.initRunner(); err != nil { - return nil, err - } - - if err := server.setupWatchers(); err != nil { - logger.Warning("Error setting up watchers: %v", err) - } - - 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 - - s.Config, err = config.Load(configPath) - if err != nil { - logger.Warning("Wipeout! Couldn't load config file: %v", err) - logger.Warning("Rolling with the default setup") - s.Config = config.New() - } - - return nil -} - -// setupLogging configures the logger based on config settings -func (s *Moonshark) setupLogging() { - 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) - } - - 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 { - if !dirExists(s.Config.Dirs.Routes) { - logger.Fatal("Routes directory doesn't exist: %s", s.Config.Dirs.Routes) - return fmt.Errorf("routes directory doesn't exist: %s", s.Config.Dirs.Routes) - } - - // 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) - - if dirExists(s.Config.Dirs.Static) { - 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() - } else { - logger.Warning("Static directory doesn't exist: %s - static file serving disabled", s.Config.Dirs.Static) - } - - return nil -} - -// initRunner initializes the Lua runner -func (s *Moonshark) initRunner() error { - if !dirExists(s.Config.Dirs.Override) { - logger.Warning("Override directory doesn't exist: %s", s.Config.Dirs.Override) - s.Config.Dirs.Override = "" - } - - for _, dir := range s.Config.Dirs.Libs { - if !dirExists(dir) { - logger.Warning("Lib directory doesn't exist: %s", dir) - } - } - - sessionManager := sessions.GlobalSessionManager - sessionManager.SetCookieOptions( - "MoonsharkSID", // name - "/", // path - "", // domain - false, // secure - true, // httpOnly - 86400, // maxAge (1 day) - ) - - runnerOpts := []runner.RunnerOption{ - runner.WithPoolSize(s.Config.Runner.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("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 { - manager := watchers.GetWatcherManager() - - // Set up watcher for Lua routes - routeWatcher, err := watchers.WatchLuaRouter(s.LuaRouter, s.LuaRunner, s.Config.Dirs.Routes) - if err != nil { - logger.Warning("Failed to watch routes directory: %v", err) - } else { - routesDir := routeWatcher.GetDir() - s.cleanupFuncs = append(s.cleanupFuncs, func() error { - return manager.UnwatchDirectory(routesDir) - }) - } - - // 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 { - dirPath := watcher.GetDir() - s.cleanupFuncs = append(s.cleanupFuncs, func() error { - return manager.UnwatchDirectory(dirPath) - }) - } - 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) - - 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.Server("Server stopped") - return nil -} diff --git a/core/runner/runner.go b/core/runner/runner.go index 69135bf..997a54a 100644 --- a/core/runner/runner.go +++ b/core/runner/runner.go @@ -4,6 +4,8 @@ import ( "Moonshark/core/utils/logger" "context" "errors" + "fmt" + "os" "path/filepath" "runtime" "sync" @@ -412,3 +414,84 @@ func (r *Runner) GetActiveStateCount() int { return count } + +// RunScriptFile loads, compiles and executes a Lua script file +func (r *Runner) RunScriptFile(filePath string) (*Response, error) { + if !r.isRunning.Load() { + return nil, ErrRunnerClosed + } + + if _, err := os.Stat(filePath); os.IsNotExist(err) { + return nil, fmt.Errorf("script file not found: %s", filePath) + } + + content, err := os.ReadFile(filePath) + if err != nil { + return nil, fmt.Errorf("failed to read file: %w", err) + } + + absPath, err := filepath.Abs(filePath) + if err != nil { + return nil, fmt.Errorf("failed to get absolute path: %w", err) + } + scriptDir := filepath.Dir(absPath) + + r.mu.Lock() + prevScriptDir := r.scriptDir + r.scriptDir = scriptDir + r.moduleLoader.SetScriptDir(scriptDir) + r.mu.Unlock() + + defer func() { + r.mu.Lock() + r.scriptDir = prevScriptDir + r.moduleLoader.SetScriptDir(prevScriptDir) + r.mu.Unlock() + }() + + var stateIndex int + select { + case stateIndex = <-r.statePool: + // Got a state + case <-time.After(5 * time.Second): + return nil, ErrTimeout + } + + state := r.states[stateIndex] + if state == nil { + r.statePool <- stateIndex + return nil, ErrStateNotReady + } + + state.inUse = true + + defer func() { + state.inUse = false + if r.isRunning.Load() { + select { + case r.statePool <- stateIndex: + // State returned to pool + default: + // Pool is full or closed + } + } + }() + + bytecode, err := state.L.CompileBytecode(string(content), filepath.Base(absPath)) + if err != nil { + return nil, fmt.Errorf("compilation error: %w", err) + } + + ctx := NewContext() + defer ctx.Release() + + ctx.Set("_script_path", absPath) + ctx.Set("_script_dir", scriptDir) + + response, err := state.sandbox.Execute(state.L, bytecode, ctx) + if err != nil { + return nil, fmt.Errorf("execution error: %w", err) + } + + return response, nil +} diff --git a/core/runner/sqlite.go b/core/runner/sqlite.go index 20af1ff..0f7fc6d 100644 --- a/core/runner/sqlite.go +++ b/core/runner/sqlite.go @@ -12,6 +12,8 @@ import ( "Moonshark/core/utils/logger" + "maps" + luajit "git.sharkk.net/Sky/LuaJIT-to-Go" ) @@ -256,7 +258,7 @@ func luaSQLQuery(state *luajit.State) int { row := make(map[string]any) columnCount := stmt.ColumnCount() - for i := 0; i < columnCount; i++ { + for i := range columnCount { columnName := stmt.ColumnName(i) columnType := stmt.ColumnType(i) @@ -279,9 +281,7 @@ func luaSQLQuery(state *luajit.State) int { // Add row copy to results rowCopy := make(map[string]any, len(row)) - for k, v := range row { - rowCopy[k] = v - } + maps.Copy(rowCopy, row) rows = append(rows, rowCopy) return nil }, diff --git a/main.go b/main.go index 774e8d9..6bd1612 100644 --- a/main.go +++ b/main.go @@ -1,41 +1,372 @@ package main import ( + "context" + "errors" "flag" "fmt" "os" "os/signal" + "path/filepath" "syscall" + "time" - "Moonshark/core" + "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() - logger.InitGlobalLogger(logger.LevelDebug, true, false) - logger.Server("Moonshark is starting to prowl 🦈") + // Initialize logger with basic settings first + logger.InitGlobalLogger(logger.LevelInfo, true, false) - server, err := core.NewMoonshark(*configPath) - if err != nil { - logger.Fatal("Failed to initialize server: %v", err) + 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 := server.Start(); err != nil { + 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") +} - if err := server.Shutdown(); err != nil { - logger.Error("Error during shutdown: %v", err) - os.Exit(1) +// 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 +}