390 lines
9.5 KiB
Go
390 lines
9.5 KiB
Go
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 != ""
|
|
|
|
color.SetColors(color.DetectShellColors())
|
|
banner(scriptMode)
|
|
|
|
// Load config
|
|
cfg, err := config.Load(*configPath)
|
|
if err != nil {
|
|
logger.Warning("Config load failed: %v, using defaults", color.Red(err.Error()))
|
|
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.Yellow(staticDir))
|
|
} else {
|
|
logger.Warning("Static directory not found: %s", color.Yellow(cfg.Dirs.Static))
|
|
}
|
|
|
|
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.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.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.Yellow(s.Config.Dirs.Routes))
|
|
|
|
return nil
|
|
}
|
|
|
|
func initRunner(s *Moonshark) error {
|
|
if !dirExists(s.Config.Dirs.Override) {
|
|
logger.Warning("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.Warning("Lib directory not found... %s", color.Yellow(dir))
|
|
}
|
|
}
|
|
|
|
// 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.Yellow(strconv.Itoa(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)
|
|
})
|
|
}
|
|
plural := ""
|
|
if len(moduleWatchers) == 1 {
|
|
plural = "directory"
|
|
} else {
|
|
plural = "directories"
|
|
}
|
|
logger.Info("Watching %s module %s.", color.Yellow(strconv.Itoa(len(moduleWatchers))), plural)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
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)))
|
|
}
|