config rewrite

This commit is contained in:
Sky Johnson 2025-05-31 13:02:16 -05:00
parent 3f86c31c9f
commit ac646bbad8
6 changed files with 62 additions and 294 deletions

3
go.mod
View File

@ -4,6 +4,7 @@ go 1.24.1
require ( require (
git.sharkk.net/Go/LRU v1.0.0 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 git.sharkk.net/Sky/LuaJIT-to-Go v0.4.1
github.com/VictoriaMetrics/fastcache v1.12.4 github.com/VictoriaMetrics/fastcache v1.12.4
github.com/alexedwards/argon2id v1.0.0 github.com/alexedwards/argon2id v1.0.0
@ -26,7 +27,7 @@ require (
github.com/ncruces/go-strftime v0.1.9 // indirect github.com/ncruces/go-strftime v0.1.9 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
golang.org/x/crypto v0.38.0 // 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 golang.org/x/sys v0.33.0 // indirect
modernc.org/libc v1.65.8 // indirect modernc.org/libc v1.65.8 // indirect
modernc.org/mathutil v1.7.1 // indirect modernc.org/mathutil v1.7.1 // indirect

6
go.sum
View File

@ -1,5 +1,7 @@
git.sharkk.net/Go/LRU v1.0.0 h1:/KqdRVhHldi23aVfQZ4ss6vhCWZqA3vFiQyf1MJPpQc= 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/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 h1:CAYt+C6Vgo4JxK876j0ApQ2GDFFvy9FKO0OoZBVD18k=
git.sharkk.net/Sky/LuaJIT-to-Go v0.4.1/go.mod h1:HQz+D7AFxOfNbTIogjxP+shEBtz1KKrLlLucU+w07c8= 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= 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.14.0/go.mod h1:MVFd36DqK4CsrnJYDkBA3VC4m2GkXAM0PvzMCn4JQf4=
golang.org/x/crypto v0.38.0 h1:jt+WWG8IZlBnVbomuhg2Mdq0+BBQaHbtqHEFEigjUV8= 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/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-20250531010427-b6e5de432a8b h1:QoALfVG9rhQ/M7vYDScfPdWjGL9dlsVVM5VGh7aKoAA=
golang.org/x/exp v0.0.0-20250506013437-ce4c2cf36ca6/go.mod h1:U6Lno4MTRCDY+Ba7aCcauB9T60gsv5s4ralQzP72ZoQ= 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.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.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU= golang.org/x/mod v0.24.0 h1:ZfthKaKaT4NrhGVZHO1/WDTwGES4De8KtWO0SIbNJMU=

View File

@ -3,6 +3,7 @@ package http
import ( import (
"bytes" "bytes"
"context" "context"
"strings"
"sync" "sync"
"time" "time"
@ -32,14 +33,12 @@ type Server struct {
staticFS *fasthttp.FS staticFS *fasthttp.FS
luaRunner *runner.Runner luaRunner *runner.Runner
fasthttpServer *fasthttp.Server fasthttpServer *fasthttp.Server
loggingEnabled bool
debugMode bool debugMode bool
config *config.Config cfg *config.Config
sessionManager *sessions.SessionManager sessionManager *sessions.SessionManager
errorConfig utils.ErrorPageConfig errorConfig utils.ErrorPageConfig
ctxPool sync.Pool ctxPool sync.Pool
paramsPool sync.Pool paramsPool sync.Pool
staticDir string
staticPrefix string staticPrefix string
staticPrefixBytes []byte staticPrefixBytes []byte
@ -49,34 +48,25 @@ type Server struct {
errorCacheMu sync.RWMutex errorCacheMu sync.RWMutex
} }
func New(luaRouter *router.LuaRouter, staticDir string, func New(luaRouter *router.LuaRouter, runner *runner.Runner, cfg *config.Config, debugMode bool) *Server {
runner *runner.Runner, loggingEnabled bool, debugMode bool, staticPrefix := cfg.Server.StaticPrefix
overrideDir string, config *config.Config) *Server { if !strings.HasPrefix(staticPrefix, "/") {
staticPrefix := config.Server.StaticPrefix
if staticPrefix == "" {
staticPrefix = "/static/"
}
if staticPrefix[0] != '/' {
staticPrefix = "/" + staticPrefix staticPrefix = "/" + staticPrefix
} }
if staticPrefix[len(staticPrefix)-1] != '/' { if !strings.HasSuffix(staticPrefix, "/") {
staticPrefix = staticPrefix + "/" staticPrefix = staticPrefix + "/"
} }
s := &Server{ s := &Server{
luaRouter: luaRouter, luaRouter: luaRouter,
luaRunner: runner, luaRunner: runner,
loggingEnabled: loggingEnabled,
debugMode: debugMode, debugMode: debugMode,
config: config, cfg: cfg,
sessionManager: sessions.GlobalSessionManager, sessionManager: sessions.GlobalSessionManager,
staticDir: staticDir,
staticPrefix: staticPrefix, staticPrefix: staticPrefix,
staticPrefixBytes: []byte(staticPrefix), staticPrefixBytes: []byte(staticPrefix),
errorConfig: utils.ErrorPageConfig{ errorConfig: utils.ErrorPageConfig{
OverrideDir: overrideDir, OverrideDir: cfg.Dirs.Override,
DebugMode: debugMode, DebugMode: debugMode,
}, },
ctxPool: sync.Pool{ ctxPool: sync.Pool{
@ -96,9 +86,9 @@ func New(luaRouter *router.LuaRouter, staticDir string,
s.cached500 = []byte(utils.InternalErrorPage(s.errorConfig, "", "Internal Server Error")) s.cached500 = []byte(utils.InternalErrorPage(s.errorConfig, "", "Internal Server Error"))
// Setup static file serving // Setup static file serving
if staticDir != "" { if cfg.Dirs.Static != "" {
s.staticFS = &fasthttp.FS{ s.staticFS = &fasthttp.FS{
Root: staticDir, Root: cfg.Dirs.Static,
IndexNames: []string{"index.html"}, IndexNames: []string{"index.html"},
GenerateIndexPages: false, GenerateIndexPages: false,
AcceptByteRange: true, AcceptByteRange: true,
@ -144,25 +134,22 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) {
methodBytes := ctx.Method() methodBytes := ctx.Method()
pathBytes := ctx.Path() pathBytes := ctx.Path()
// Fast path for debug stats
if s.debugMode && bytes.Equal(pathBytes, debugPath) { if s.debugMode && bytes.Equal(pathBytes, debugPath) {
s.handleDebugStats(ctx) s.handleDebugStats(ctx)
if s.loggingEnabled { if s.cfg.Server.HTTPLogging {
logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start)) logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start))
} }
return return
} }
// Fast path for static files
if s.staticHandler != nil && bytes.HasPrefix(pathBytes, s.staticPrefixBytes) { if s.staticHandler != nil && bytes.HasPrefix(pathBytes, s.staticPrefixBytes) {
s.staticHandler(ctx) s.staticHandler(ctx)
if s.loggingEnabled { if s.cfg.Server.HTTPLogging {
logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start)) logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start))
} }
return return
} }
// Lua route lookup - only allocate params if found
bytecode, scriptPath, routeErr, params, found := s.luaRouter.GetRouteInfo(methodBytes, pathBytes) bytecode, scriptPath, routeErr, params, found := s.luaRouter.GetRouteInfo(methodBytes, pathBytes)
if found { if found {
@ -175,7 +162,7 @@ func (s *Server) handleRequest(ctx *fasthttp.RequestCtx) {
s.send404(ctx, pathBytes) s.send404(ctx, pathBytes)
} }
if s.loggingEnabled { if s.cfg.Server.HTTPLogging {
logger.LogRequest(ctx.Response.StatusCode(), string(methodBytes), string(pathBytes), time.Since(start)) 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) { func (s *Server) handleDebugStats(ctx *fasthttp.RequestCtx) {
stats := utils.CollectSystemStats(s.config) stats := utils.CollectSystemStats(s.cfg)
routeCount, bytecodeBytes := s.luaRouter.GetRouteStats() routeCount, bytecodeBytes := s.luaRouter.GetRouteStats()
stats.Components = utils.ComponentStats{ stats.Components = utils.ComponentStats{
RouteCount: routeCount, RouteCount: routeCount,

39
main.go
View File

@ -21,6 +21,8 @@ import (
"Moonshark/utils/logger" "Moonshark/utils/logger"
"Moonshark/utils/metadata" "Moonshark/utils/metadata"
"Moonshark/watchers" "Moonshark/watchers"
fin "git.sharkk.net/Sharkk/Fin"
) )
// Moonshark represents the server and all its dependencies // Moonshark represents the server and all its dependencies
@ -34,7 +36,7 @@ type Moonshark struct {
} }
func main() { 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") debugFlag := flag.Bool("debug", false, "Enable debug mode")
scriptPath := flag.String("script", "", "Path to Lua script to execute once") scriptPath := flag.String("script", "", "Path to Lua script to execute once")
flag.Parse() flag.Parse()
@ -42,26 +44,20 @@ func main() {
color.SetColors(color.DetectShellColors()) color.SetColors(color.DetectShellColors())
banner(scriptMode) 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 { if *debugFlag || cfg.Server.Debug {
logger.EnableDebug() logger.EnableDebug()
logger.Debug("Debug logging enabled") logger.Debug("Debug logging enabled")
} }
var moonshark *Moonshark var moonshark *Moonshark
var err error
if scriptMode { if scriptMode {
moonshark, err = initScriptMode(cfg) moonshark, err = initScriptMode(cfg)
} else { } else {
moonshark, err = initServerMode(cfg, *debugFlag) moonshark, err = initServerMode(cfg, *debugFlag || cfg.Server.Debug)
} }
if err != nil { if err != nil {
@ -156,12 +152,9 @@ func initServerMode(cfg *config.Config, debug bool) (*Moonshark, error) {
moonshark.HTTPServer = http.New( moonshark.HTTPServer = http.New(
moonshark.LuaRouter, moonshark.LuaRouter,
staticDir,
moonshark.LuaRunner, moonshark.LuaRunner,
cfg.Server.HTTPLogging,
cfg.Server.Debug,
cfg.Dirs.Override,
cfg, cfg,
debug,
) )
// For development, disable caching. For production, enable it // 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))) 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
}

View File

@ -24,8 +24,7 @@ const (
var useColors = true var useColors = true
// Color function. Makes a call to makeColorFunc with the associated // Color function; makes a call to makeColorFunc with the associated color code
// color code.
var ( var (
Reset = makeColorFunc(resetCode) Reset = makeColorFunc(resetCode)
Red = makeColorFunc(redCode) Red = makeColorFunc(redCode)

View File

@ -1,16 +1,11 @@
package config package config
import ( import (
"errors"
"fmt"
"runtime" "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 { type Config struct {
// Server settings // Server settings
Server struct { Server struct {
@ -35,256 +30,29 @@ type Config struct {
Libs []string Libs []string
} }
// Raw values map for custom values // Raw fin data struct for custom data
values map[string]any data *fin.Data
} }
// NewConfig creates a new configuration with default values // NewConfig creates a new configuration with default values
func New() *Config { func New(data *fin.Data) *Config {
config := &Config{ config := &Config{
// Initialize values map data: data,
values: make(map[string]any),
} }
// Server defaults config.Server.Port = data.GetOr("server.port", 3117).(int)
config.Server.Port = 3117 config.Server.Debug = data.GetOr("server.debug", false).(bool)
config.Server.Debug = false config.Server.HTTPLogging = data.GetOr("server.http_logging", true).(bool)
config.Server.HTTPLogging = false config.Server.StaticPrefix = data.GetOr("server.static_prefix", "public").(string)
config.Server.StaticPrefix = "static/"
// Runner defaults config.Runner.PoolSize = data.GetOr("runner.pool_size", runtime.GOMAXPROCS(0)).(int)
config.Runner.PoolSize = runtime.GOMAXPROCS(0)
// Dirs defaults config.Dirs.Routes = data.GetOr("dirs.routes", "routes").(string)
config.Dirs.Routes = "routes" config.Dirs.Static = data.GetOr("dirs.static", "public").(string)
config.Dirs.Static = "public" config.Dirs.FS = data.GetOr("dirs.fs", "fs").(string)
config.Dirs.FS = "fs" config.Dirs.Data = data.GetOr("dirs.data", "data").(string)
config.Dirs.Data = "data" config.Dirs.Override = data.GetOr("dirs.override", "override").(string)
config.Dirs.Override = "override" config.Dirs.Libs = data.GetOr("dirs.libs", []string{"libs"}).([]string)
config.Dirs.Libs = []string{"libs"}
return config 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
}