diff --git a/go.mod b/go.mod index 8a873e1..1b813f8 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.24.1 require ( git.sharkk.net/Go/LRU v1.0.0 + git.sharkk.net/Sharkk/Fin v1.2.0 git.sharkk.net/Sky/LuaJIT-to-Go v0.4.1 github.com/VictoriaMetrics/fastcache v1.12.4 github.com/alexedwards/argon2id v1.0.0 @@ -26,7 +27,7 @@ require ( github.com/ncruces/go-strftime v0.1.9 // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect golang.org/x/crypto v0.38.0 // indirect - golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 // indirect + golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b // indirect golang.org/x/sys v0.33.0 // indirect modernc.org/libc v1.65.8 // indirect modernc.org/mathutil v1.7.1 // indirect diff --git a/go.sum b/go.sum index 2cfd634..5b49d29 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ git.sharkk.net/Go/LRU v1.0.0 h1:/KqdRVhHldi23aVfQZ4ss6vhCWZqA3vFiQyf1MJPpQc= git.sharkk.net/Go/LRU v1.0.0/go.mod h1:8tdTyl85mss9a+KKwo+Wj9gKHOizhfLfpJhz1ltYz50= +git.sharkk.net/Sharkk/Fin v1.2.0 h1:axhme8vHRYoaB3us7PNfXzXxKOxhpS5BMuNpN8ESe6U= +git.sharkk.net/Sharkk/Fin v1.2.0/go.mod h1:ca0Ej9yCM/vHh1o3YMvBZspme3EtbwoEL2UXN5UPXMo= git.sharkk.net/Sky/LuaJIT-to-Go v0.4.1 h1:CAYt+C6Vgo4JxK876j0ApQ2GDFFvy9FKO0OoZBVD18k= git.sharkk.net/Sky/LuaJIT-to-Go v0.4.1/go.mod h1:HQz+D7AFxOfNbTIogjxP+shEBtz1KKrLlLucU+w07c8= github.com/VictoriaMetrics/fastcache v1.12.4 h1:2xvmwZBW+9QtHsXggfzAZRs1FZWCsBs8QDg22bMidf0= @@ -52,8 +54,8 @@ golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5y golang.org/x/crypto v0.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4= golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= golang.org/x/crypto v0.38.0/go.mod h1:MvrbAqul58NNYPKnOra203SB9vpuZW0e+RRZV+Ggqjw= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6 h1:y5zboxd6LQAqYIhHnB48p0ByQ/GnQx2BE33L8BOHQkI= -golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= +golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA= +golang.org/x/exp v0.0.0-20250531010427-b6e5de432a8b/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= diff --git a/http/server.go b/http/server.go index 1cd006b..49aa185 100644 --- a/http/server.go +++ b/http/server.go @@ -3,6 +3,7 @@ package http import ( "bytes" "context" + "strings" "sync" "time" @@ -32,14 +33,12 @@ type Server struct { staticFS *fasthttp.FS luaRunner *runner.Runner fasthttpServer *fasthttp.Server - loggingEnabled bool debugMode bool - config *config.Config + cfg *config.Config sessionManager *sessions.SessionManager errorConfig utils.ErrorPageConfig ctxPool sync.Pool paramsPool sync.Pool - staticDir string staticPrefix string staticPrefixBytes []byte @@ -49,34 +48,25 @@ type Server struct { errorCacheMu sync.RWMutex } -func New(luaRouter *router.LuaRouter, staticDir string, - runner *runner.Runner, loggingEnabled bool, debugMode bool, - overrideDir string, config *config.Config) *Server { - - staticPrefix := config.Server.StaticPrefix - if staticPrefix == "" { - staticPrefix = "/static/" - } - - if staticPrefix[0] != '/' { +func New(luaRouter *router.LuaRouter, runner *runner.Runner, cfg *config.Config, debugMode bool) *Server { + staticPrefix := cfg.Server.StaticPrefix + if !strings.HasPrefix(staticPrefix, "/") { staticPrefix = "/" + staticPrefix } - if staticPrefix[len(staticPrefix)-1] != '/' { + if !strings.HasSuffix(staticPrefix, "/") { staticPrefix = staticPrefix + "/" } s := &Server{ luaRouter: luaRouter, luaRunner: runner, - loggingEnabled: loggingEnabled, debugMode: debugMode, - config: config, + cfg: cfg, sessionManager: sessions.GlobalSessionManager, - staticDir: staticDir, staticPrefix: staticPrefix, staticPrefixBytes: []byte(staticPrefix), errorConfig: utils.ErrorPageConfig{ - OverrideDir: overrideDir, + OverrideDir: cfg.Dirs.Override, DebugMode: debugMode, }, ctxPool: sync.Pool{ @@ -96,9 +86,9 @@ func New(luaRouter *router.LuaRouter, staticDir string, s.cached500 = []byte(utils.InternalErrorPage(s.errorConfig, "", "Internal Server Error")) // Setup static file serving - if staticDir != "" { + if cfg.Dirs.Static != "" { s.staticFS = &fasthttp.FS{ - Root: staticDir, + Root: cfg.Dirs.Static, IndexNames: []string{"index.html"}, GenerateIndexPages: false, AcceptByteRange: true, @@ -144,25 +134,22 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { methodBytes := ctx.Method() pathBytes := ctx.Path() - // Fast path for debug stats if s.debugMode && bytes.Equal(pathBytes, debugPath) { s.handleDebugStats(ctx) - if s.loggingEnabled { + if s.cfg.Server.HTTPLogging { logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start)) } return } - // Fast path for static files if s.staticHandler != nil && bytes.HasPrefix(pathBytes, s.staticPrefixBytes) { s.staticHandler(ctx) - if s.loggingEnabled { + if s.cfg.Server.HTTPLogging { logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start)) } return } - // Lua route lookup - only allocate params if found bytecode, scriptPath, routeErr, params, found := s.luaRouter.GetRouteInfo(methodBytes, pathBytes) if found { @@ -175,7 +162,7 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) { s.send404(ctx, pathBytes) } - if s.loggingEnabled { + if s.cfg.Server.HTTPLogging { logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start)) } } @@ -310,7 +297,7 @@ func (s *Server) sendError(ctx *fasthttp.RequestCtx, status int, pathBytes []byt } func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) { - stats := utils.CollectSystemStats(s.config) + stats := utils.CollectSystemStats(s.cfg) routeCount, bytecodeBytes := s.luaRouter.GetRouteStats() stats.Components = utils.ComponentStats{ RouteCount: routeCount, diff --git a/main.go b/main.go index eda8e5c..e92efe9 100644 --- a/main.go +++ b/main.go @@ -21,6 +21,8 @@ import ( "Moonshark/utils/logger" "Moonshark/utils/metadata" "Moonshark/watchers" + + fin "git.sharkk.net/Sharkk/Fin" ) // Moonshark represents the server and all its dependencies @@ -34,7 +36,7 @@ type Moonshark struct { } func main() { - configPath := flag.String("config", "config.lua", "Path to configuration file") + 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() @@ -42,26 +44,20 @@ func main() { color.SetColors(color.DetectShellColors()) banner(scriptMode) + cfg := config.New(readConfig(*configPath)) - // 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 + var err error if scriptMode { moonshark, err = initScriptMode(cfg) } else { - moonshark, err = initServerMode(cfg, *debugFlag) + moonshark, err = initServerMode(cfg, *debugFlag || cfg.Server.Debug) } if err != nil { @@ -156,12 +152,9 @@ func initServerMode(cfg *config.Config, debug bool) (*Moonshark, error) { moonshark.HTTPServer = http.New( moonshark.LuaRouter, - staticDir, moonshark.LuaRunner, - cfg.Server.HTTPLogging, - cfg.Server.Debug, - cfg.Dirs.Override, cfg, + debug, ) // For development, disable caching. For production, enable it @@ -387,3 +380,21 @@ func banner(scriptMode bool) { ` fmt.Println(color.Blue(fmt.Sprintf(banner, metadata.Version))) } + +// readConfig attempts to read config data from a Fin file +func readConfig(path string) *fin.Data { + file, err := os.Open(path) + if err != nil { + logger.Error("Failed to open config file %s", color.Yellow(path)) + cfg, _ := fin.Load(nil) + return cfg + } + defer file.Close() + + cfg, err := fin.Load(file) + if err != nil { + logger.Warning("Config load failed: %v, using defaults", color.Red(err.Error())) + } + + return cfg +} diff --git a/utils/color/color.go b/utils/color/color.go index beacbe4..d0c133c 100644 --- a/utils/color/color.go +++ b/utils/color/color.go @@ -24,8 +24,7 @@ const ( var useColors = true -// Color function. Makes a call to makeColorFunc with the associated -// color code. +// Color function; makes a call to makeColorFunc with the associated color code var ( Reset = makeColorFunc(resetCode) Red = makeColorFunc(redCode) diff --git a/utils/config/config.go b/utils/config/config.go index c11afbf..aa87ee6 100644 --- a/utils/config/config.go +++ b/utils/config/config.go @@ -1,16 +1,11 @@ package config import ( - "errors" - "fmt" "runtime" - "strconv" - "strings" - luajit "git.sharkk.net/Sky/LuaJIT-to-Go" + fin "git.sharkk.net/Sharkk/Fin" ) -// Config represents a configuration loaded from a Lua file type Config struct { // Server settings Server struct { @@ -35,256 +30,29 @@ type Config struct { Libs []string } - // Raw values map for custom values - values map[string]any + // Raw fin data struct for custom data + data *fin.Data } // NewConfig creates a new configuration with default values -func New() *Config { +func New(data *fin.Data) *Config { config := &Config{ - // Initialize values map - values: make(map[string]any), + data: data, } - // Server defaults - config.Server.Port = 3117 - config.Server.Debug = false - config.Server.HTTPLogging = false - config.Server.StaticPrefix = "static/" + config.Server.Port = data.GetOr("server.port", 3117).(int) + config.Server.Debug = data.GetOr("server.debug", false).(bool) + config.Server.HTTPLogging = data.GetOr("server.http_logging", true).(bool) + config.Server.StaticPrefix = data.GetOr("server.static_prefix", "public").(string) - // Runner defaults - config.Runner.PoolSize = runtime.GOMAXPROCS(0) + config.Runner.PoolSize = data.GetOr("runner.pool_size", runtime.GOMAXPROCS(0)).(int) - // Dirs defaults - config.Dirs.Routes = "routes" - config.Dirs.Static = "public" - config.Dirs.FS = "fs" - config.Dirs.Data = "data" - config.Dirs.Override = "override" - config.Dirs.Libs = []string{"libs"} + config.Dirs.Routes = data.GetOr("dirs.routes", "routes").(string) + config.Dirs.Static = data.GetOr("dirs.static", "public").(string) + config.Dirs.FS = data.GetOr("dirs.fs", "fs").(string) + config.Dirs.Data = data.GetOr("dirs.data", "data").(string) + config.Dirs.Override = data.GetOr("dirs.override", "override").(string) + config.Dirs.Libs = data.GetOr("dirs.libs", []string{"libs"}).([]string) return config } - -// Load loads configuration from a Lua file -func Load(filePath string) (*Config, error) { - // Create Lua state - state := luajit.New(true) - if state == nil { - return nil, errors.New("failed to create Lua state") - } - defer state.Close() - defer state.Cleanup() - - // Create config with default values - config := New() - - // Execute the config file - if err := state.DoFile(filePath); err != nil { - return nil, fmt.Errorf("failed to load config file: %w", err) - } - - // Store values directly to the config - config.values = make(map[string]any) - - // Extract top-level tables - tables := []string{"server", "runner", "dirs"} - for _, table := range tables { - state.GetGlobal(table) - if state.IsTable(-1) { - tableMap, err := state.ToTable(-1) - if err == nil { - config.values[table] = tableMap - } - } - state.Pop(1) - } - - // Apply configuration values - applyConfig(config, config.values) - - return config, nil -} - -// applyConfig applies configuration values from the globals map -func applyConfig(config *Config, values map[string]any) { - // Apply server settings - if serverTable, ok := values["server"].(map[string]any); ok { - if v, ok := serverTable["port"].(float64); ok { - config.Server.Port = int(v) - } - if v, ok := serverTable["debug"].(bool); ok { - config.Server.Debug = v - } - if v, ok := serverTable["http_logging"].(bool); ok { - config.Server.HTTPLogging = v - } - if v, ok := serverTable["static_prefix"].(string); ok { - config.Server.StaticPrefix = v - } - } - - // Apply runner settings - if runnerTable, ok := values["runner"].(map[string]any); ok { - if v, ok := runnerTable["pool_size"].(float64); ok && v != 0 { - config.Runner.PoolSize = int(v) - } - } - - // Apply dirs settings - if dirsTable, ok := values["dirs"].(map[string]any); ok { - if v, ok := dirsTable["routes"].(string); ok { - config.Dirs.Routes = v - } - if v, ok := dirsTable["static"].(string); ok { - config.Dirs.Static = v - } - if v, ok := dirsTable["fs"].(string); ok { - config.Dirs.FS = v - } - if v, ok := dirsTable["data"].(string); ok { - config.Dirs.Data = v - } - if v, ok := dirsTable["override"].(string); ok { - config.Dirs.Override = v - } - - // Handle libs array - if libs, ok := dirsTable["libs"]; ok { - if libsArray := extractStringArray(libs); len(libsArray) > 0 { - config.Dirs.Libs = libsArray - } - } - } -} - -// extractStringArray extracts a string array from a Lua table -func extractStringArray(value any) []string { - // Direct array case - if arr, ok := value.([]any); ok { - result := make([]string, 0, len(arr)) - for _, v := range arr { - if str, ok := v.(string); ok { - result = append(result, str) - } - } - return result - } - - // Map with numeric keys case - if tableMap, ok := value.(map[string]any); ok { - result := make([]string, 0, len(tableMap)) - for _, v := range tableMap { - if str, ok := v.(string); ok { - result = append(result, str) - } - } - return result - } - - return nil -} - -// GetCustomValue returns any custom configuration value by key -// Key can be a dotted path like "server.port" -func (c *Config) GetCustomValue(key string) any { - parts := strings.Split(key, ".") - - if len(parts) == 1 { - return c.values[key] - } - - current := c.values - for _, part := range parts[:len(parts)-1] { - next, ok := current[part].(map[string]any) - if !ok { - return nil - } - current = next - } - - return current[parts[len(parts)-1]] -} - -// GetCustomString returns a custom string configuration value -func (c *Config) GetCustomString(key string, defaultValue string) string { - value := c.GetCustomValue(key) - if value == nil { - return defaultValue - } - - // Convert to string - switch v := value.(type) { - case string: - return v - case float64: - return fmt.Sprintf("%g", v) - case int: - return strconv.Itoa(v) - case bool: - if v { - return "true" - } - return "false" - default: - return defaultValue - } -} - -// GetCustomInt returns a custom integer configuration value -func (c *Config) GetCustomInt(key string, defaultValue int) int { - value := c.GetCustomValue(key) - if value == nil { - return defaultValue - } - - // Convert to int - switch v := value.(type) { - case int: - return v - case float64: - return int(v) - case string: - if i, err := strconv.Atoi(v); err == nil { - return i - } - case bool: - if v { - return 1 - } - return 0 - default: - return defaultValue - } - - return defaultValue -} - -// GetCustomBool returns a custom boolean configuration value -func (c *Config) GetCustomBool(key string, defaultValue bool) bool { - value := c.GetCustomValue(key) - if value == nil { - return defaultValue - } - - // Convert to bool - switch v := value.(type) { - case bool: - return v - case string: - switch v { - case "true", "yes", "1": - return true - case "false", "no", "0", "": - return false - } - case int: - return v != 0 - case float64: - return v != 0 - default: - return defaultValue - } - - return defaultValue -}