move go files to src/

This commit is contained in:
2025-10-10 22:46:24 +03:00
parent f0c591f325
commit 57f35e8f33
45 changed files with 0 additions and 0 deletions

View File

@@ -0,0 +1,95 @@
package app
import (
"context"
"log"
"log/slog"
"os"
"os/signal"
"sync"
"syscall"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
)
type AppContract interface {
InitialHooks(fn ...func(ctx context.Context, cs *corestate.CoreState, x *AppX))
Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error)
Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX))
CallFallback(ctx context.Context)
}
type App struct {
initHooks []func(ctx context.Context, cs *corestate.CoreState, x *AppX)
runHook func(ctx context.Context, cs *corestate.CoreState, x *AppX) error
fallback func(ctx context.Context, cs *corestate.CoreState, x *AppX)
Corestate *corestate.CoreState
AppX *AppX
fallbackOnce sync.Once
}
type AppX struct {
Config *config.Compositor
Log *log.Logger
SLog *slog.Logger
}
func New() AppContract {
return &App{
AppX: &AppX{
Log: log.Default(),
},
Corestate: &corestate.CoreState{},
}
}
func (a *App) InitialHooks(fn ...func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
a.initHooks = append(a.initHooks, fn...)
}
func (a *App) Fallback(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX)) {
a.fallback = fn
}
func (a *App) Run(fn func(ctx context.Context, cs *corestate.CoreState, x *AppX) error) {
a.runHook = fn
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
defer stop()
for _, hook := range a.initHooks {
hook(ctx, a.Corestate, a.AppX)
}
defer func() {
if r := recover(); r != nil {
a.AppX.Log.Printf("PANIC recovered: %v", r)
if a.fallback != nil {
a.fallback(ctx, a.Corestate, a.AppX)
}
os.Exit(1)
}
}()
var runErr error
if a.runHook != nil {
runErr = a.runHook(ctx, a.Corestate, a.AppX)
}
if runErr != nil {
a.AppX.Log.Fatalf("fatal in Run: %v", runErr)
}
}
func (a *App) CallFallback(ctx context.Context) {
a.fallbackOnce.Do(func() {
if a.fallback != nil {
a.fallback(ctx, a.Corestate, a.AppX)
}
os.Exit(0)
})
}

View File

@@ -0,0 +1,186 @@
package config
import (
"fmt"
"reflect"
"strconv"
"strings"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func NewCompositor() *Compositor {
return &Compositor{}
}
func (c *Compositor) LoadEnv() error {
v := viper.New()
// defaults
v.SetDefault("config_path", "./cfg/config.yaml")
v.SetDefault("node_path", "./")
v.SetDefault("parent_pid", -1)
// GS_*
v.SetEnvPrefix("GS")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
var env Env
if err := v.Unmarshal(&env); err != nil {
return fmt.Errorf("error unmarshaling env: %w", err)
}
c.Env = &env
return nil
}
func (c *Compositor) LoadConf(path string) error {
v := viper.New()
v.SetConfigFile(path)
v.SetConfigType("yaml")
// defaults
v.SetDefault("node.name", "noname")
v.SetDefault("node.mode", "dev")
v.SetDefault("node.show_config", "false")
v.SetDefault("node.com_dir", "./com/")
v.SetDefault("http_server.address", "0.0.0.0")
v.SetDefault("http_server.port", "8080")
v.SetDefault("http_server.session_ttl", "30m")
v.SetDefault("http_server.timeout", "5s")
v.SetDefault("http_server.idle_timeout", "60s")
v.SetDefault("tls.enabled", false)
v.SetDefault("tls.cert_file", "./cert/server.crt")
v.SetDefault("tls.key_file", "./cert/server.key")
v.SetDefault("updates.enabled", false)
v.SetDefault("updates.check_interval", "2h")
v.SetDefault("updates.wanted_version", "latest-stable")
v.SetDefault("log.json_format", "false")
v.SetDefault("log.level", "info")
v.SetDefault("log.output", "%2%")
if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("error reading config: %w", err)
}
var cfg Conf
if err := v.Unmarshal(&cfg); err != nil {
return fmt.Errorf("error unmarshaling config: %w", err)
}
c.Conf = &Conf{}
c.Conf = &cfg
return nil
}
func (c *Compositor) LoadCMDLine(root *cobra.Command) {
cmdLine := &CMDLine{}
c.CMDLine = cmdLine
t := reflect.TypeOf(cmdLine).Elem()
v := reflect.ValueOf(cmdLine).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldVal := v.Field(i)
ptr := fieldVal.Addr().Interface()
use := strings.ToLower(field.Name)
var cmd *cobra.Command
for _, sub := range root.Commands() {
if sub.Use == use {
cmd = sub
break
}
}
if use == root.Use {
cmd = root
}
if cmd == nil {
continue
}
Unmarshal(cmd, ptr)
}
}
func Unmarshal(cmd *cobra.Command, target any) {
t := reflect.TypeOf(target).Elem()
v := reflect.ValueOf(target).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
valPtr := v.Field(i).Addr().Interface()
full := field.Tag.Get("full")
short := field.Tag.Get("short")
def := field.Tag.Get("def")
desc := field.Tag.Get("desc")
isPersistent := field.Tag.Get("persistent") == "true"
flagSet := cmd.Flags()
if isPersistent {
flagSet = cmd.PersistentFlags()
}
switch field.Type.Kind() {
case reflect.String:
flagSet.StringVarP(valPtr.(*string), full, short, def, desc)
case reflect.Bool:
defVal, err := strconv.ParseBool(def)
if err != nil && def != "" {
fmt.Printf("warning: cannot parse default bool: %q\n", def)
}
flagSet.BoolVarP(valPtr.(*bool), full, short, defVal, desc)
case reflect.Int:
defVal, err := strconv.Atoi(def)
if err != nil && def != "" {
fmt.Printf("warning: cannot parse default int: %q\n", def)
}
flagSet.IntVarP(valPtr.(*int), full, short, defVal, desc)
case reflect.Slice:
elemKind := field.Type.Elem().Kind()
switch elemKind {
case reflect.String:
defVals := []string{}
if def != "" {
defVals = strings.Split(def, ",")
}
flagSet.StringSliceVarP(valPtr.(*[]string), full, short, defVals, desc)
case reflect.Int:
var intVals []int
if def != "" {
for _, s := range strings.Split(def, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
n, err := strconv.Atoi(s)
if err != nil {
fmt.Printf("warning: cannot parse int in slice: %q\n", s)
continue
}
intVals = append(intVals, n)
}
}
flagSet.IntSliceVarP(valPtr.(*[]int), full, short, intVals, desc)
default:
fmt.Printf("unsupported slice element type: %s\n", elemKind)
}
default:
fmt.Printf("unsupported field type: %s\n", field.Type.Kind())
}
}
}

View File

@@ -0,0 +1,82 @@
// Package config provides configuration management for the application.
// config is built on top of the third-party module cleanenv
package config
import (
"time"
)
type CompositorContract interface {
LoadEnv() error
LoadConf(path string) error
}
type Compositor struct {
CMDLine *CMDLine
Conf *Conf
Env *Env
}
type Conf struct {
Node *Node `mapstructure:"node"`
HTTPServer *HTTPServer `mapstructure:"http_server"`
TLS *TLS `mapstructure:"tls"`
Updates *Updates `mapstructure:"updates"`
Log *Log `mapstructure:"log"`
DisableWarnings *[]string `mapstructure:"disable_warnings"`
}
type Node struct {
Mode *string `mapstructure:"mode"`
Name *string `mapstructure:"name"`
ShowConfig *bool `mapstructure:"show_config"`
ComDir *string `mapstructure:"com_dir"`
}
type HTTPServer struct {
Address *string `mapstructure:"address"`
Port *string `mapstructure:"port"`
SessionTTL *time.Duration `mapstructure:"session_ttl"`
Timeout *time.Duration `mapstructure:"timeout"`
IdleTimeout *time.Duration `mapstructure:"idle_timeout"`
}
type TLS struct {
TlsEnabled *bool `mapstructure:"enabled"`
CertFile *string `mapstructure:"cert_file"`
KeyFile *string `mapstructure:"key_file"`
}
type Updates struct {
UpdatesEnabled *bool `mapstructure:"enabled"`
CheckInterval *time.Duration `mapstructure:"check_interval"`
RepositoryURL *string `mapstructure:"repository_url"`
WantedVersion *string `mapstructure:"wanted_version"`
}
type Log struct {
JSON *bool `mapstructure:"json_format"`
Level *string `mapstructure:"level"`
OutPath *string `mapstructure:"output"`
}
// ConfigEnv structure for environment variables
type Env struct {
ConfigPath *string `mapstructure:"config_path"`
NodePath *string `mapstructure:"node_path"`
ParentStagePID *int `mapstructure:"parent_pid"`
}
type CMDLine struct {
Run Run
Node Root
}
type Root struct {
Debug bool `persistent:"true" full:"debug" short:"d" def:"false" desc:"Set debug mode"`
}
type Run struct {
ConfigPath string `persistent:"true" full:"config" short:"c" def:"./config.yaml" desc:"Path to configuration file"`
Test []int `persistent:"true" full:"test" short:"t" def:"" desc:"js test"`
}

View File

@@ -0,0 +1,36 @@
package config
import "os"
// TODO: Need to make a more harmonious and understandable way of storing global variables
// UUIDLength is uuids length for sessions. By default it is 16 bytes.
var UUIDLength int = 16
// ApiRoute setting for go-chi for main route for api requests
var ApiRoute string = "/api/{ver}"
// ComDirRoute setting for go-chi for main route for commands
var ComDirRoute string = "/com"
// NodeVersion is the version of the node. It can be set by the build system or manually.
// If not set, it will return "version0.0.0-none" by default
var NodeVersion string
// ActualFileName is a feature of the GoSally update system.
// In the repository, the file specified in the variable contains the current information about updates
var ActualFileName string = "actual.txt"
// UpdateArchiveName is the name of the archive that will be used for updates.
var UpdateArchiveName string = "gosally-node"
// UpdateInstallPath is the path where the update will be installed.
var UpdateDownloadPath string = os.TempDir()
var MetaDir string = "./.meta"
func init() {
if NodeVersion == "" {
NodeVersion = "v0.0.0-none"
}
}

View File

@@ -0,0 +1,72 @@
package config
import (
"fmt"
"reflect"
"time"
"github.com/akyaiy/GoSally-mvp/internal/colors"
)
func (c *Compositor) Print(v any) {
c.printConfig(v, " ")
}
func (c *Compositor) printConfig(v any, prefix string) {
val := reflect.ValueOf(v)
if val.Kind() == reflect.Ptr {
val = val.Elem()
}
typ := val.Type()
for i := 0; i < val.NumField(); i++ {
field := val.Field(i)
fieldType := typ.Field(i)
fieldName := fieldType.Name
if tag, ok := fieldType.Tag.Lookup("mapstructure"); ok {
if tag != "" {
fieldName = tag
}
}
coloredFieldName := colors.SetBrightCyan(fieldName)
if field.Kind() == reflect.Ptr {
if field.IsNil() {
fmt.Printf("%s%s: %s\n", prefix, coloredFieldName, colors.SetBrightRed("<nil>"))
continue
}
field = field.Elem()
}
if field.Kind() == reflect.Struct {
if field.Type() == reflect.TypeOf(time.Duration(0)) {
duration := field.Interface().(time.Duration)
fmt.Printf("%s%s: %s\n",
prefix,
coloredFieldName,
colors.SetBrightYellow(duration.String()))
} else {
fmt.Printf("%s%s:\n", prefix, coloredFieldName)
c.printConfig(field.Addr().Interface(), prefix+" ")
}
} else if field.Kind() == reflect.Slice {
fmt.Printf("%s%s: %s\n",
prefix,
coloredFieldName,
colors.SetBrightYellow(fmt.Sprintf("%v", field.Interface())))
} else {
value := field.Interface()
valueStr := fmt.Sprintf("%v", value)
if field.Kind() == reflect.String {
valueStr = fmt.Sprintf("\"%s\"", value)
}
fmt.Printf("%s%s: %s\n",
prefix,
coloredFieldName,
colors.SetBrightYellow(valueStr))
}
}
}

View File

@@ -0,0 +1,85 @@
// Package logs provides a logger setup function that configures the logger based on the environment.
// It supports different logging levels for development and production environments.
// It uses the standard library's slog package for structured logging.
package logs
import (
"bytes"
"context"
"io"
"log/slog"
"os"
"path/filepath"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"gopkg.in/natefinch/lumberjack.v2"
)
var GlobalLevel slog.Level
type levelsStruct struct {
Available []string
Fallback string
}
var Levels = levelsStruct{
Available: []string{
"debug", "info",
},
Fallback: "info",
}
type SlogWriter struct {
Logger *slog.Logger
Level slog.Level
}
func (w *SlogWriter) Write(p []byte) (n int, err error) {
msg := string(bytes.TrimSpace(p))
w.Logger.Log(context.TODO(), w.Level, msg)
return len(p), nil
}
// SetupLogger initializes and returns a logger based on the provided environment.
func SetupLogger(o *config.Log) (*slog.Logger, error) {
var handlerOpts = slog.HandlerOptions{}
var writer io.Writer = os.Stdout
switch *o.Level {
case "debug":
GlobalLevel = slog.LevelDebug
handlerOpts.Level = slog.LevelDebug
case "info":
GlobalLevel = slog.LevelInfo
handlerOpts.Level = slog.LevelInfo
default:
GlobalLevel = slog.LevelInfo
handlerOpts.Level = slog.LevelInfo
}
switch *o.OutPath {
case "_1STDout":
writer = os.Stdout
case "_2STDerr":
writer = os.Stderr
default:
logFile := &lumberjack.Logger{
Filename: filepath.Join(*o.OutPath, "event.log"),
MaxSize: 10,
MaxBackups: 5,
MaxAge: 28,
Compress: true,
}
writer = logFile
}
var handler slog.Handler
if *o.JSON {
handler = slog.NewJSONHandler(writer, &handlerOpts)
} else {
handler = slog.NewTextHandler(writer, &handlerOpts)
}
log := slog.New(handler)
return log, nil
}

View File

@@ -0,0 +1,25 @@
package logs
import (
"context"
"log/slog"
"sync"
)
// MockHandler is a mock implementation of slog.Handler for testing purposes.
type MockHandler struct {
mu sync.Mutex
// Logs stores the log records captured by the handler.
Logs []slog.Record
}
func NewMockHandler() *MockHandler { return &MockHandler{} }
func (h *MockHandler) Enabled(_ context.Context, _ slog.Level) bool { return true }
func (h *MockHandler) WithAttrs(_ []slog.Attr) slog.Handler { return h }
func (h *MockHandler) WithGroup(_ string) slog.Handler { return h }
func (h *MockHandler) Handle(_ context.Context, r slog.Record) error {
h.mu.Lock()
defer h.mu.Unlock()
h.Logs = append(h.Logs, r.Clone())
return nil
}

View File

@@ -0,0 +1 @@
package lua

View File

@@ -0,0 +1,35 @@
package lua
import (
"sync"
lua "github.com/yuin/gopher-lua"
)
type LuaPool struct {
pool sync.Pool
}
func NewLuaPool() *LuaPool {
return &LuaPool{
pool: sync.Pool{
New: func() any {
L := lua.NewState()
return L
},
},
}
}
func (lp *LuaPool) Get() *lua.LState {
return lp.pool.Get().(*lua.LState)
}
func (lp *LuaPool) Put(L *lua.LState) {
L.Close()
newL := lua.NewState()
lp.pool.Put(newL)
}

View File

@@ -0,0 +1,26 @@
package lua
import (
"net/http"
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
)
type LuaEngineDeps struct {
HttpRequest *http.Request
JSONRPCRequest *rpc.RPCRequest
SessionUUID string
ScriptPath string
}
type LuaEngineContract interface {
Handle(deps *LuaEngineDeps) *rpc.RPCResponse
}
type LuaEngine struct {
x *app.AppX
cs *corestate.CoreState
}