Project structure refactor:

- Change package name general_server to gateway
- Changing the structure of directories and packages
- Adding vendor to the project
This commit is contained in:
2025-07-28 20:16:40 +03:00
parent 19b699d92b
commit ec94df5f4a
786 changed files with 357010 additions and 357 deletions

View File

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

View File

@@ -0,0 +1,182 @@
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("mode", "dev")
v.SetDefault("com_dir", "./com/")
v.SetDefault("http_server.address", "0.0.0.0")
v.SetDefault("http_server.port", "8080")
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.level", "info")
v.SetDefault("log.out_path", "")
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,80 @@
// 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 {
Mode string `mapstructure:"mode"`
ComDir string `mapstructure:"com_dir"`
HTTPServer HTTPServer `mapstructure:"http_server"`
TLS TLS `mapstructure:"tls"`
Updates Updates `mapstructure:"updates"`
Log Log `mapstructure:"log"`
DisableWarnings []string `mapstructure:"disable_warnings"`
}
type HTTPServer struct {
Address string `mapstructure:"address"`
Port string `mapstructure:"port"`
Timeout time.Duration `mapstructure:"timeout"`
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
HTTPServer_Api HTTPServer_Api `mapstructure:"api"`
}
type HTTPServer_Api struct {
LatestVer string `mapstructure:"latest-version"`
Layers []string `mapstructure:"layers"`
}
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 {
Level string `mapstructure:"level"`
OutPath string `mapstructure:"out_path"`
}
// 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,34 @@
package config
import "os"
// 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,24 @@
package logs
import "fmt"
func SetBlack(s string) string { return fmt.Sprintf("\033[30m%s\033[0m", s) }
func SetRed(s string) string { return fmt.Sprintf("\033[31m%s\033[0m", s) }
func SetGreen(s string) string { return fmt.Sprintf("\033[32m%s\033[0m", s) }
func SetYellow(s string) string { return fmt.Sprintf("\033[33m%s\033[0m", s) }
func SetBlue(s string) string { return fmt.Sprintf("\033[34m%s\033[0m", s) }
func SetMagenta(s string) string { return fmt.Sprintf("\033[35m%s\033[0m", s) }
func SetCyan(s string) string { return fmt.Sprintf("\033[36m%s\033[0m", s) }
func SetWhite(s string) string { return fmt.Sprintf("\033[37m%s\033[0m", s) }
func SetBrightBlack(s string) string { return fmt.Sprintf("\033[90m%s\033[0m", s) }
func SetBrightRed(s string) string { return fmt.Sprintf("\033[91m%s\033[0m", s) }
func SetBrightGreen(s string) string { return fmt.Sprintf("\033[92m%s\033[0m", s) }
func SetBrightYellow(s string) string { return fmt.Sprintf("\033[93m%s\033[0m", s) }
func SetBrightBlue(s string) string { return fmt.Sprintf("\033[94m%s\033[0m", s) }
func SetBrightMagenta(s string) string { return fmt.Sprintf("\033[95m%s\033[0m", s) }
func SetBrightCyan(s string) string { return fmt.Sprintf("\033[96m%s\033[0m", s) }
func SetBrightWhite(s string) string { return fmt.Sprintf("\033[97m%s\033[0m", s) }
func PrintError() string { return SetRed("Error") }
func PrintWarn() string { return SetYellow("Warning") }

View File

@@ -0,0 +1,87 @@
// 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"
"regexp"
"strings"
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
"gopkg.in/natefinch/lumberjack.v2"
)
var GlobalLevel slog.Level
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
}
if o.OutPath != "" {
repl := map[string]string{
"tmp": filepath.Clean(run_manager.RuntimeDir()),
}
re := regexp.MustCompile(`%(\w+)%`)
result := re.ReplaceAllStringFunc(o.OutPath, func(match string) string {
sub := re.FindStringSubmatch(match)
if len(sub) < 2 {
return match
}
key := sub[1]
if val, ok := repl[key]; ok {
return val
}
return match
})
if strings.Contains(o.OutPath, "%tmp%") {
relPath := strings.TrimPrefix(result, filepath.Clean(run_manager.RuntimeDir()))
if err := run_manager.SetDir(relPath); err != nil {
return nil, err
}
}
logFile := &lumberjack.Logger{
Filename: filepath.Join(result, "event.log"),
MaxSize: 10,
MaxBackups: 5,
MaxAge: 28,
Compress: true,
}
writer = logFile
}
log := slog.New(slog.NewJSONHandler(writer, &handlerOpts))
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
}