Moonshark/moonshark.go

283 lines
6.7 KiB
Go

package main
import (
"context"
"errors"
"flag"
"fmt"
"os"
"os/signal"
"path/filepath"
"runtime"
"strconv"
"syscall"
"time"
"Moonshark/config"
"Moonshark/http"
"Moonshark/logger"
"Moonshark/router"
"Moonshark/runner"
"Moonshark/runner/lualibs"
"Moonshark/sessions"
"Moonshark/watchers"
"git.sharkk.net/Go/Color"
)
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()
}