diff --git a/cmd/run.go b/cmd/run.go index f2fb24e..b9d65c4 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -32,6 +32,15 @@ import ( "gopkg.in/ini.v1" ) +func contains(slice []string, item string) bool { + for _, v := range slice { + if v == item { + return true + } + } + return false +} + var runCmd = &cobra.Command{ Use: "run", Short: "Run node normally", @@ -93,7 +102,7 @@ var runCmd = &cobra.Command{ func(cs *corestate.CoreState, x *app.AppX) { if x.Config.Env.ParentStagePID != os.Getpid() { - if os.TempDir() != "/tmp" { + if !contains(x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" { x.Log.Printf("%s: %s", logs.PrintWarn(), "Non-standard value specified for temporary directory") } // still pre-init stage @@ -260,23 +269,27 @@ var runCmd = &cobra.Command{ listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port)) if err != nil { x.Log.Printf("%s: Failed to start TLS listener: %s", logs.PrintError(), err.Error()) + cancelMain() return } x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute) limitedListener := netutil.LimitListener(listener, 100) if err := srv.ServeTLS(limitedListener, x.Config.Conf.TLS.CertFile, x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) { x.Log.Printf("%s: Failed to start HTTPS server: %s", logs.PrintError(), err.Error()) + cancelMain() } } else { x.Log.Printf("Serving on %s port %s... (http://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute) listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port)) if err != nil { x.Log.Printf("%s: Failed to start listener: %s", logs.PrintError(), err.Error()) + cancelMain() return } limitedListener := netutil.LimitListener(listener, 100) if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) { x.Log.Printf("%s: Failed to start HTTP server: %s", logs.PrintError(), err.Error()) + cancelMain() } } }() diff --git a/core/config/config.go b/core/config/config.go index acfe448..30ea313 100644 --- a/core/config/config.go +++ b/core/config/config.go @@ -24,6 +24,7 @@ type Conf struct { TLS TLS `mapstructure:"tls"` Updates Updates `mapstructure:"updates"` Log Log `mapstructure:"log"` + DisableWarnings []string `mapstructure:"disable_warnings"` } type HTTPServer struct { diff --git a/core/general_server/handle_multi.go b/core/general_server/handle_multi.go index 16b805b..8c94a6f 100644 --- a/core/general_server/handle_multi.go +++ b/core/general_server/handle_multi.go @@ -14,20 +14,19 @@ package general_server import ( + "encoding/json" "errors" + "io" "log/slog" "net/http" - "slices" "github.com/akyaiy/GoSally-mvp/core/config" - "github.com/akyaiy/GoSally-mvp/core/utils" - - "github.com/go-chi/chi/v5" ) // serversApiVer is a type alias for string, used to represent API version strings in the GeneralServer. type serversApiVer string +/* // GeneralServerApiContract defines the interface for servers that can be registered type GeneralServerApiContract interface { // GetVersion returns the API version of the server. @@ -37,9 +36,13 @@ type GeneralServerApiContract interface { Handle(w http.ResponseWriter, r *http.Request) HandleList(w http.ResponseWriter, r *http.Request) } +*/ + +type GeneralServerApiContract interface { + GetVersion() string + Handle(w http.ResponseWriter, r *http.Request) +} -// GeneralServerContarct extends the GeneralServerApiContract with a method to append new servers. -// This interface is only for general server initialization and does not need to be implemented by individual servers. type GeneralServerContarct interface { GeneralServerApiContract // AppendToArray adds a new server to the GeneralServer's internal map. @@ -61,7 +64,7 @@ type GeneralServer struct { // GeneralServerInit structure only for initialization general server. type GeneralServerInit struct { - Log slog.Logger + Log *slog.Logger Config *config.Conf } @@ -70,7 +73,7 @@ func InitGeneral(o *GeneralServerInit, servers ...GeneralServerApiContract) *Gen general := &GeneralServer{ servers: make(map[serversApiVer]GeneralServerApiContract), cfg: o.Config, - log: o.Log, + log: *o.Log, } // register the provided servers @@ -95,6 +98,62 @@ func (s *GeneralServer) AppendToArray(server GeneralServerApiContract) error { return errors.New("server with this version is already exist") } +func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) { + if r.Method != http.MethodPost { + WriteRouterError(w, &RouterError{ + Status: "error", + StatusCode: http.StatusBadRequest, + Payload: map[string]any{ + "Message": IssueMethod, + }, + }) + s.log.Info("invalid request received", slog.String("issue", IssueMethod), slog.String("requested-method", r.Method)) + return + } + var payload RawPettiEnvelope + + body, err := io.ReadAll(r.Body) + if err != nil { + WriteRouterError(w, &RouterError{ + Status: "error", + StatusCode: http.StatusBadRequest, + Payload: map[string]any{ + "Message": IssueToReadBody, + }, + }) + s.log.Info("invalid request received", slog.String("issue", IssueToReadBody)) + return + } + + if err := json.Unmarshal(body, &payload); err != nil { + WriteRouterError(w, &RouterError{ + Status: "error", + StatusCode: http.StatusBadRequest, + Payload: map[string]any{ + "Message": InvalidProtocol, + }, + }) + s.log.Info("invalid request received", slog.String("issue", InvalidProtocol)) + return + } + + server, ok := s.servers[serversApiVer(payload.PettiVer)] + if !ok { + WriteRouterError(w, &RouterError{ + Status: "error", + StatusCode: http.StatusBadRequest, + Payload: map[string]any{ + "Message": InvalidProtovolVersion, + }, + }) + s.log.Info("invalid request received", slog.String("issue", InvalidProtovolVersion), slog.String("requested-version", payload.PettiVer)) + return + } + + server.Handle(w, r) +} + +/* // Handle processes incoming HTTP requests, routing them to the appropriate server based on the API version. // It checks if the requested version is registered and handles the request accordingly. func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) { @@ -188,3 +247,4 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) { s.log.Error("Failed to write JSON", slog.String("err", err.Error())) } } +*/ diff --git a/core/general_server/issue.go b/core/general_server/issue.go new file mode 100644 index 0000000..2807da1 --- /dev/null +++ b/core/general_server/issue.go @@ -0,0 +1,8 @@ +package general_server + +const ( + IssueMethod = "invalid request method" + IssueToReadBody = "unable to read body" + InvalidProtocol = "unknown protocol or missing PettiVer field" + InvalidProtovolVersion = "unsupported PETTI version" +) diff --git a/core/general_server/protocol.go b/core/general_server/protocol.go new file mode 100644 index 0000000..b2af1da --- /dev/null +++ b/core/general_server/protocol.go @@ -0,0 +1,49 @@ +package general_server + +import ( + "encoding/json" + "net/http" +) + +type RawPettiEnvelope struct { + PettiVer string `json:"PettiVer"` +} + +type RouterError struct { + Status string `json:"Status"` + StatusCode int `json:"StatusCode"` + Payload map[string]any `json:"Payload"` +} + +func WriteRouterError(w http.ResponseWriter, e *RouterError) error { + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(e.StatusCode) + + data, err := json.Marshal(e) + if err != nil { + return err + } + + _, err = w.Write(data) + return err +} + +// func WriteRouterError(w http.ResponseWriter, e *RouterError) error { +// resp := RouterError{ +// Status: e.Status, +// StatusCode: e.StatusCode, +// Payload: e.Payload, +// } +// b, err := json.Marshal(resp) +// if err != nil { +// return err +// } +// var formatted bytes.Buffer +// if err := json.Indent(&formatted, b, "", " "); err != nil { +// return err +// } +// w.Header().Set("Content-Type", "application/json") +// w.WriteHeader(e.StatusCode) +// formatted.WriteTo(w) +// return nil +// } diff --git a/core/sv1/handle_com.go b/core/sv1/handle.go similarity index 50% rename from core/sv1/handle_com.go rename to core/sv1/handle.go index 492175a..86fad7a 100644 --- a/core/sv1/handle_com.go +++ b/core/sv1/handle.go @@ -1,5 +1,168 @@ package sv1 +import ( + "encoding/json" + "log/slog" + "net/http" + "os" + "path/filepath" + + "github.com/akyaiy/GoSally-mvp/core/config" + "github.com/akyaiy/GoSally-mvp/core/corestate" + "github.com/akyaiy/GoSally-mvp/core/utils" + lua "github.com/yuin/gopher-lua" +) + + + +// func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) { +// var req PettiRequest +// // server, ok := s.servers[serversApiVer(payload.PettiVer)] +// // if !ok { +// // WriteRouterError(w, &RouterError{ +// // Status: "error", +// // StatusCode: http.StatusBadRequest, +// // Payload: map[string]any{ +// // "Message": InvalidProtovolVersion, +// // }, +// // }) +// // s.log.Info("invalid request received", slog.String("issue", InvalidProtovolVersion), slog.String("requested-version", payload.PettiVer)) +// // return +// // } +// if err := json.NewDecoder(r.Body).Decode(&req); err != nil { +// utils.WriteJSONError(w, http.StatusBadRequest, "невалидный JSON: "+err.Error()) +// return +// } + +// if req.PettiVer == "" { +// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PettiVer") +// return +// } +// if req.PettiVer != h.GetVersion() { +// utils.WriteJSONError(w, http.StatusBadRequest, "неподдерживаемая версия PettiVer") +// return +// } +// if req.PackageType.Request == "" { +// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует PackageType.Request") +// return +// } +// if req.Payload == nil { +// utils.WriteJSONError(w, http.StatusBadRequest, "отсутствует Payload") +// return +// } +// cmdRaw, ok := req.Payload["Exec"].(string) +// if !ok || cmdRaw == "" { +// utils.WriteJSONError(w, http.StatusBadRequest, "Payload.Exec отсутствует или некорректен") +// return +// } +// cmd := cmdRaw + +// if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) { +// utils.WriteJSONError(w, http.StatusBadRequest, "команда запрещена") +// return +// } + +// // ===== Проверка скрипта +// scriptPath := h.comMatch(h.GetVersion(), cmd) +// if scriptPath == "" { +// utils.WriteJSONError(w, http.StatusNotFound, "команда не найдена") +// return +// } +// fullPath := filepath.Join(h.cfg.ComDir, scriptPath) +// if _, err := os.Stat(fullPath); err != nil { +// utils.WriteJSONError(w, http.StatusNotFound, "файл команды не найден") +// return +// } + +// // ===== Запуск Lua +// L := lua.NewState() +// defer L.Close() + +// inTable := L.NewTable() +// paramsTable := L.NewTable() +// if params, ok := req.Payload["PassedParameters"].(map[string]interface{}); ok { +// for k, v := range params { +// L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v)) +// } +// } +// L.SetField(inTable, "Params", paramsTable) +// L.SetGlobal("In", inTable) + +// resultTable := L.NewTable() +// outTable := L.NewTable() +// L.SetField(outTable, "Result", resultTable) +// L.SetGlobal("Out", outTable) + +// prepareLua := filepath.Join(h.cfg.ComDir, "_prepare.lua") +// if _, err := os.Stat(prepareLua); err == nil { +// if err := L.DoFile(prepareLua); err != nil { +// utils.WriteJSONError(w, http.StatusInternalServerError, "lua _prepare ошибка: "+err.Error()) +// return +// } +// } +// if err := L.DoFile(fullPath); err != nil { +// utils.WriteJSONError(w, http.StatusInternalServerError, "lua exec ошибка: "+err.Error()) +// return +// } + +// lv := L.GetGlobal("Out") +// tbl, ok := lv.(*lua.LTable) +// if !ok { +// utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' не таблица") +// return +// } +// resultVal := tbl.RawGetString("Result") +// resultTbl, ok := resultVal.(*lua.LTable) +// if !ok { +// utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' не таблица") +// return +// } + +// out := make(map[string]any) +// resultTbl.ForEach(func(key lua.LValue, value lua.LValue) { +// out[key.String()] = utils.ConvertLuaTypesToGolang(value) +// }) + +// uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid")) + +// resp := PettiResponse{ +// PettiVer: req.PettiVer, +// ResponsibleAgentUUID: uuid32, +// PackageType: struct { +// AnswerOf string `json:"AnswerOf"` +// }{AnswerOf: req.PackageType.Request}, +// Payload: map[string]any{ +// "RequestedCommand": cmd, +// "Response": out, +// }, +// } + +// // ===== Финальная проверка на сериализацию (валидность сборки) +// respData, err := json.Marshal(resp) +// if err != nil { +// utils.WriteJSONError(w, http.StatusInternalServerError, "внутренняя ошибка: пакет невалиден") +// return +// } + +// w.Header().Set("Content-Type", "application/json") +// w.WriteHeader(http.StatusOK) +// if _, err := w.Write(respData); err != nil { +// h.log.Error("Ошибка при отправке JSON", slog.String("err", err.Error())) +// } + +// // ===== Логгирование статуса +// status, _ := out["status"].(string) +// switch status { +// case "ok": +// h.log.Info("Успешно", slog.String("cmd", cmd), slog.Any("out", out)) +// case "error": +// h.log.Warn("Ошибка в команде", slog.String("cmd", cmd), slog.Any("out", out)) +// default: +// h.log.Info("Неизвестный статус", slog.String("cmd", cmd), slog.Any("out", out)) +// } +// } + +/* import ( "encoding/json" "log/slog" @@ -181,3 +344,4 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) { log.Info("Session completed") } +*/ diff --git a/core/sv1/handle_list.go b/core/sv1/handle_list.go index d4a908b..5287008 100644 --- a/core/sv1/handle_list.go +++ b/core/sv1/handle_list.go @@ -1,5 +1,6 @@ package sv1 +/* import ( "encoding/json" "log/slog" @@ -129,3 +130,4 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) { slog.String("error", err.Error())) } } +*/ diff --git a/core/sv1/petti.go b/core/sv1/petti.go new file mode 100644 index 0000000..5b79709 --- /dev/null +++ b/core/sv1/petti.go @@ -0,0 +1,20 @@ +package sv1 + +// PETTI - Go Sally Protocol for Exchanging Technical Tasks and Information + +type PettiRequest struct { + PettiVer string `json:"PettiVer"` + PackageType struct { + Request string `json:"Request"` + } `json:"PackageType"` + Payload map[string]any `json:"Payload"` +} + +type PettiResponse struct { + PettiVer string `json:"PettiVer"` + PackageType struct { + AnswerOf string `json:"AnswerOf"` + } `json:"PackageType"` + ResponsibleAgentUUID string `json:"ResponsibleAgentUUID"` + Payload map[string]any `json:"Payload"` +} diff --git a/core/sv1/proto.go b/core/sv1/proto.go deleted file mode 100644 index 23b238f..0000000 --- a/core/sv1/proto.go +++ /dev/null @@ -1,7 +0,0 @@ -package sv1 - -type ResponseFormat struct { - ResponsibleAgentUUID string - RequestedCommand string - Response any -} diff --git a/core/sv1/utils.go b/core/sv1/utils.go index 877f7e6..40b82d6 100644 --- a/core/sv1/utils.go +++ b/core/sv1/utils.go @@ -3,7 +3,6 @@ package sv1 import ( "log/slog" "os" - "regexp" ) // func (h *HandlerV1) errNotFound(w http.ResponseWriter, r *http.Request) { @@ -15,19 +14,19 @@ import ( // slog.Int("status", http.StatusBadRequest)) // } -func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) { - data, err := os.ReadFile(path) - if err != nil { - return "", err - } +// func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) { +// data, err := os.ReadFile(path) +// if err != nil { +// return "", err +// } - re := regexp.MustCompile(`---\s*#description\s*=\s*"([^"]+)"`) - m := re.FindStringSubmatch(string(data)) - if len(m) <= 0 { - return "", nil - } - return m[1], nil -} +// re := regexp.MustCompile(`---\s*#description\s*=\s*"([^"]+)"`) +// m := re.FindStringSubmatch(string(data)) +// if len(m) <= 0 { +// return "", nil +// } +// return m[1], nil +// } func (h *HandlerV1) comMatch(ver string, comName string) string { files, err := os.ReadDir(h.cfg.ComDir) diff --git a/core/utils/internal_lua.go b/core/utils/internal_lua.go index 16b0889..91b4282 100644 --- a/core/utils/internal_lua.go +++ b/core/utils/internal_lua.go @@ -1,6 +1,10 @@ package utils -import lua "github.com/yuin/gopher-lua" +import ( + "fmt" + + lua "github.com/yuin/gopher-lua" +) func ConvertLuaTypesToGolang(value lua.LValue) any { switch value.Type() { @@ -11,16 +15,64 @@ func ConvertLuaTypesToGolang(value lua.LValue) any { case lua.LTBool: return bool(value.(lua.LBool)) case lua.LTTable: - result := make(map[string]interface{}) - if tbl, ok := value.(*lua.LTable); ok { - tbl.ForEach(func(key lua.LValue, value lua.LValue) { - result[key.String()] = ConvertLuaTypesToGolang(value) - }) + tbl := value.(*lua.LTable) + + // Попробуем как массив + var arr []any + isArray := true + tbl.ForEach(func(key, val lua.LValue) { + if key.Type() != lua.LTNumber { + isArray = false + } + arr = append(arr, ConvertLuaTypesToGolang(val)) + }) + + if isArray { + return arr } + + result := make(map[string]any) + tbl.ForEach(func(key, val lua.LValue) { + result[key.String()] = ConvertLuaTypesToGolang(val) + }) return result + case lua.LTNil: return nil default: return value.String() } } + +func ConvertGolangTypesToLua(L *lua.LState, val any) lua.LValue { + switch v := val.(type) { + case string: + return lua.LString(v) + case bool: + return lua.LBool(v) + case int: + return lua.LNumber(float64(v)) + case int64: + return lua.LNumber(float64(v)) + case float32: + return lua.LNumber(float64(v)) + case float64: + return lua.LNumber(v) + case []any: + tbl := L.NewTable() + for i, item := range v { + tbl.RawSetInt(i+1, ConvertGolangTypesToLua(L, item)) + } + return tbl + case map[string]any: + tbl := L.NewTable() + for key, value := range v { + tbl.RawSetString(key, ConvertGolangTypesToLua(L, value)) + } + return tbl + case nil: + return lua.LNil + default: + return lua.LString(fmt.Sprintf("%v", v)) + } +} diff --git a/go.mod b/go.mod index 867d3f7..c3a8294 100644 --- a/go.mod +++ b/go.mod @@ -4,35 +4,31 @@ go 1.24.4 require ( github.com/go-chi/chi/v5 v5.2.2 - github.com/ilyakaznacheev/cleanenv v1.5.0 + github.com/spf13/cobra v1.9.1 + github.com/spf13/viper v1.20.1 github.com/yuin/gopher-lua v1.1.1 - golang.org/x/net v0.41.0 + golang.org/x/net v0.42.0 + gopkg.in/ini.v1 v1.67.0 + gopkg.in/natefinch/lumberjack.v2 v2.2.1 ) require ( - github.com/fsnotify/fsnotify v1.8.0 // indirect - github.com/go-viper/mapstructure/v2 v2.2.1 // indirect + github.com/fsnotify/fsnotify v1.9.0 // indirect + github.com/go-viper/mapstructure/v2 v2.3.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/pelletier/go-toml/v2 v2.2.3 // indirect - github.com/sagikazarmark/locafero v0.7.0 // indirect + github.com/pelletier/go-toml/v2 v2.2.4 // indirect + github.com/sagikazarmark/locafero v0.9.0 // indirect github.com/sourcegraph/conc v0.3.0 // indirect - github.com/spf13/afero v1.12.0 // indirect - github.com/spf13/cast v1.7.1 // indirect - github.com/spf13/cobra v1.9.1 // indirect + github.com/spf13/afero v1.14.0 // indirect + github.com/spf13/cast v1.9.2 // indirect github.com/spf13/pflag v1.0.6 // indirect - github.com/spf13/viper v1.20.1 // indirect github.com/subosito/gotenv v1.6.0 // indirect - go.uber.org/atomic v1.9.0 // indirect - go.uber.org/multierr v1.9.0 // indirect - golang.org/x/sys v0.33.0 // indirect - golang.org/x/text v0.26.0 // indirect - gopkg.in/ini.v1 v1.67.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + golang.org/x/sys v0.34.0 // indirect + golang.org/x/text v0.27.0 // indirect ) require ( - github.com/BurntSushi/toml v1.5.0 // indirect github.com/go-chi/cors v1.2.2 - github.com/joho/godotenv v1.5.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect ) diff --git a/go.sum b/go.sum index ed13a07..bbd28c1 100644 --- a/go.sum +++ b/go.sum @@ -1,62 +1,65 @@ -github.com/BurntSushi/toml v1.2.1/go.mod h1:CxXYINrC8qIiEnFrOxCa7Jy5BFHlXnUU2pbicEuybxQ= -github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg= -github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= -github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/fsnotify/fsnotify v1.8.0 h1:dAwr6QBTBZIkG8roQaJjGof0pp0EeF+tNV7YBP3F/8M= -github.com/fsnotify/fsnotify v1.8.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= +github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= +github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= +github.com/fsnotify/fsnotify v1.9.0 h1:2Ml+OJNzbYCTzsxtv8vKSFD9PbJjmhYF14k/jKC7S9k= +github.com/fsnotify/fsnotify v1.9.0/go.mod h1:8jBTzvmWwFyi3Pb8djgCCO5IBqzKJ/Jwo8TRcHyHii0= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE= github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58= -github.com/go-viper/mapstructure/v2 v2.2.1 h1:ZAaOCxANMuZx5RCeg0mBdEZk7DZasvvZIxtHqx8aGss= -github.com/go-viper/mapstructure/v2 v2.2.1/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= -github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= -github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= +github.com/go-viper/mapstructure/v2 v2.3.0 h1:27XbWsHIqhbdR5TIC911OfYvgSaW93HM+dX7970Q7jk= +github.com/go-viper/mapstructure/v2 v2.3.0/go.mod h1:oJDH3BJKyqBA2TXFhDsKDGDTlndYOZ6rGS0BRZIxGhM= +github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= -github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= -github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M= -github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc= +github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= +github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= +github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= +github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= -github.com/sagikazarmark/locafero v0.7.0 h1:5MqpDsTGNDhY8sGp0Aowyf0qKsPrhewaLSsFaodPcyo= -github.com/sagikazarmark/locafero v0.7.0/go.mod h1:2za3Cg5rMaTMoG/2Ulr9AwtFaIppKXTRYnozin4aB5k= +github.com/sagikazarmark/locafero v0.9.0 h1:GbgQGNtTrEmddYDSAH9QLRyfAHY12md+8YFTqyMTC9k= +github.com/sagikazarmark/locafero v0.9.0/go.mod h1:UBUyz37V+EdMS3hDF3QWIiVr/2dPrx49OMO0Bn0hJqk= github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo= github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0= -github.com/spf13/afero v1.12.0 h1:UcOPyRBYczmFn6yvphxkn9ZEOY65cpwGKb5mL36mrqs= -github.com/spf13/afero v1.12.0/go.mod h1:ZTlWwG4/ahT8W7T0WQ5uYmjI9duaLQGy3Q2OAl4sk/4= -github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= -github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= +github.com/spf13/afero v1.14.0 h1:9tH6MapGnn/j0eb0yIXiLjERO8RB6xIVZRDCX7PtqWA= +github.com/spf13/afero v1.14.0/go.mod h1:acJQ8t0ohCGuMN3O+Pv0V0hgMxNYDlvdk+VTfyZmbYo= +github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE= +github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cobra v1.9.1 h1:CXSaggrXdbHK9CF+8ywj8Amf7PBRmPCOJugH954Nnlo= github.com/spf13/cobra v1.9.1/go.mod h1:nDyEzZ8ogv936Cinf6g1RU9MRY64Ir93oCnqb9wxYW0= github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o= github.com/spf13/pflag v1.0.6/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= github.com/spf13/viper v1.20.1 h1:ZMi+z/lvLyPSCoNtFCpqjy0S4kPbirhpTMwl8BkW9X4= github.com/spf13/viper v1.20.1/go.mod h1:P9Mdzt1zoHIG8m2eZQinpiBjo6kCmZSKBClNNqjJvu4= -github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= -github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= +github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8= github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU= github.com/yuin/gopher-lua v1.1.1 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M= github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw= -go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= -go.uber.org/atomic v1.9.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= -go.uber.org/multierr v1.9.0 h1:7fIwc/ZtS0q++VgcfqFDxSBZVv/Xo49/SYnDFupUwlI= -go.uber.org/multierr v1.9.0/go.mod h1:X2jQV1h+kxSjClGpnseKVIxpmcjrj7MNnI0bnlfKTVQ= -golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= -golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= -golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= -golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= -golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= -golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= +go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= +go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= +golang.org/x/net v0.42.0 h1:jzkYrhi3YQWD6MLBJcsklgQsoAcw89EcZbJw8Z614hs= +golang.org/x/net v0.42.0/go.mod h1:FF1RA5d3u7nAYA4z2TkclSCKh68eSXtiFwcWQpPXdt8= +golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA= +golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/text v0.27.0 h1:4fGWRpyh641NLlecmyl4LOe6yDdfaYNrGb2zdfo4JV4= +golang.org/x/text v0.27.0/go.mod h1:1D28KMCvyooCX9hBiosv5Tz/+YLxj0j7XhWjpSUF7CU= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA= gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k= +gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc= +gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ= -olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3/go.mod h1:oVgVk4OWVDi43qWBEyGhXgYxt7+ED4iYNpTngSLX2Iw=