318 lines
7.7 KiB
Go
318 lines
7.7 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"flag"
|
|
"fmt"
|
|
"os"
|
|
"os/signal"
|
|
"path/filepath"
|
|
"runtime"
|
|
"strconv"
|
|
"syscall"
|
|
"time"
|
|
|
|
"Moonshark/color"
|
|
"Moonshark/config"
|
|
"Moonshark/http"
|
|
"Moonshark/logger"
|
|
"Moonshark/metadata"
|
|
"Moonshark/router"
|
|
"Moonshark/runner"
|
|
"Moonshark/runner/lualibs"
|
|
"Moonshark/sessions"
|
|
"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
|
|
}
|