mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 08:32:24 +00:00
Refactor core configuration and UUID handling
- Changed UUIDLength type from byte to int in core/config/consts.go - Introduced MetaDir constant in core/config/consts.go - Added corestate package with initial state management and UUID handling - Implemented GetNodeUUID and SetNodeUUID functions for UUID file management - Created RunManager and RunFileManager for runtime directory management - Updated GeneralServer to use new configuration structure - Removed deprecated init package and replaced with main entry point - Added color utility functions for logging - Enhanced UUID generation functions in utils package - Updated update logic to handle new configuration structure - Added routines for cleaning temporary runtime directories - Introduced response formatting for API responses
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,5 +1,6 @@
|
||||
bak/
|
||||
bin/
|
||||
cert/
|
||||
tmp/
|
||||
|
||||
config.yaml
|
||||
5
.meta/uuid/README.txt
Normal file
5
.meta/uuid/README.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
- - - - ! STRICTLY FORBIDDEN TO MODIFY THIS DIRECTORY ! - - - -
|
||||
This directory contains the unique node identifier stored in the file named data.
|
||||
This identifier is critical for correct node recognition both locally and across the network.
|
||||
Any modification, deletion, or tampering with this directory may lead to permanent loss of identity, data corruption, or network conflicts.
|
||||
Proceed at your own risk. You have been warned.
|
||||
BIN
.meta/uuid/data
Normal file
BIN
.meta/uuid/data
Normal file
Binary file not shown.
8
Makefile
8
Makefile
@@ -30,19 +30,19 @@ build:
|
||||
@# @echo "CGO_CFLAGS is: '$(CGO_CFLAGS)'"
|
||||
@# @echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
|
||||
@# CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(CGO_LDFLAGS)"
|
||||
go build -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./cmd/$(APP_NAME)
|
||||
go build -ldflags "$(LDFLAGS)" -o $(BIN_DIR)/$(APP_NAME) ./
|
||||
|
||||
run: build
|
||||
@echo "Running!"
|
||||
./$(BIN_DIR)/$(APP_NAME)
|
||||
exec ./$(BIN_DIR)/$(APP_NAME)
|
||||
|
||||
runq: build
|
||||
@echo "Running!"
|
||||
./$(BIN_DIR)/$(APP_NAME) | jq
|
||||
exec ./$(BIN_DIR)/$(APP_NAME) | jq
|
||||
|
||||
pure-run:
|
||||
@echo "Running!"
|
||||
./$(BIN_DIR)/$(APP_NAME) | jq
|
||||
exec ./$(BIN_DIR)/$(APP_NAME)
|
||||
|
||||
test:
|
||||
@go test ./... | grep -v '^?' || true
|
||||
|
||||
142
cmd/node/node.go
142
cmd/node/node.go
@@ -1,142 +0,0 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/netutil"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
gs "github.com/akyaiy/GoSally-mvp/core/general_server"
|
||||
_ "github.com/akyaiy/GoSally-mvp/core/init"
|
||||
"github.com/akyaiy/GoSally-mvp/core/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/core/sv1"
|
||||
"github.com/akyaiy/GoSally-mvp/core/update"
|
||||
"github.com/go-chi/cors"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
var log *slog.Logger
|
||||
var cfg *config.ConfigConf
|
||||
|
||||
func init() {
|
||||
cfg = config.MustLoadConfig()
|
||||
|
||||
log = logs.SetupLogger(cfg.Mode)
|
||||
log = log.With("mode", cfg.Mode)
|
||||
|
||||
currentV, currentB, _ := update.NewUpdater(*log, cfg).GetCurrentVersion()
|
||||
|
||||
log.Info("Initializing GoSally server", slog.String("address", cfg.HTTPServer.Address), slog.String("version", string(currentV)+"-"+string(currentB)))
|
||||
log.Debug("Server running in debug mode")
|
||||
}
|
||||
|
||||
func UpdateDaemon(u *update.Updater, cfg config.ConfigConf, srv *http.Server) {
|
||||
//time.Sleep(5 * time.Second)
|
||||
log.Info("New update available, starting update process...")
|
||||
|
||||
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
|
||||
defer cancel()
|
||||
|
||||
log.Info("Trying to down server gracefully before update")
|
||||
if err := srv.Shutdown(ctx); err != nil {
|
||||
log.Error("Failed to shutdown server gracefully", slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
err := u.Update()
|
||||
if err != nil {
|
||||
log.Error("Failed to update", slog.String("error", err.Error()))
|
||||
} else {
|
||||
log.Info("Update completed successfully")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func main() {
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
Log: *log,
|
||||
Config: cfg,
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+$`),
|
||||
ListAllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
|
||||
Ver: "v1",
|
||||
})
|
||||
s := gs.InitGeneral(&gs.GeneralServerInit{
|
||||
Log: *log,
|
||||
Config: cfg,
|
||||
}, serverv1)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
r.Route(config.GetServerConsts().GetApiRoute()+config.GetServerConsts().GetComDirRoute(), func(r chi.Router) {
|
||||
r.Get("/", s.HandleList)
|
||||
r.Get("/{cmd}", s.Handle)
|
||||
})
|
||||
r.Route("/favicon.ico", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: cfg.Address,
|
||||
Handler: r,
|
||||
}
|
||||
go func() {
|
||||
if cfg.TlsEnabled {
|
||||
log.Info("HTTPS server started with TLS", slog.String("address", cfg.Address))
|
||||
listener, err := net.Listen("tcp", cfg.Address)
|
||||
if err != nil {
|
||||
log.Error("Failed to start TLS listener", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
err = http.ServeTLS(limitedListener, r, cfg.CertFile, cfg.KeyFile)
|
||||
if err != nil {
|
||||
log.Error("Failed to start HTTPS server", slog.String("error", err.Error()))
|
||||
}
|
||||
} else {
|
||||
log.Info("HTTP server started", slog.String("address", cfg.Address))
|
||||
listener, err := net.Listen("tcp", cfg.Address)
|
||||
if err != nil {
|
||||
log.Error("Failed to start listener", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
err = http.Serve(limitedListener, r)
|
||||
if err != nil {
|
||||
log.Error("Failed to start HTTP server", slog.String("error", err.Error()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
time.Sleep(5*time.Second)
|
||||
updater := update.NewUpdater(*log, cfg)
|
||||
go func() {
|
||||
time.Sleep(6*time.Second)
|
||||
for {
|
||||
isNewUpdate, err := updater.CkeckUpdates()
|
||||
if err != nil {
|
||||
log.Error("Failed to check for updates", slog.String("error", err.Error()))
|
||||
}
|
||||
if isNewUpdate {
|
||||
UpdateDaemon(updater, *cfg, srv)
|
||||
} else {
|
||||
log.Info("No new updates available")
|
||||
}
|
||||
time.Sleep(cfg.CheckInterval)
|
||||
}
|
||||
}()
|
||||
|
||||
select {}
|
||||
}
|
||||
26
cmd/root.go
Normal file
26
cmd/root.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: "node",
|
||||
Short: "Go Sally node",
|
||||
Long: "Main node runner for Go Sally",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
cmd.Help()
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
log.SetOutput(os.Stdout)
|
||||
log.SetPrefix("\033[34m[INIT]\033[0m ")
|
||||
log.SetFlags(log.Ldate | log.Ltime)
|
||||
if err := rootCmd.Execute(); err != nil {
|
||||
log.Fatalf("Unexpected error: %s", err.Error())
|
||||
}
|
||||
}
|
||||
311
cmd/run.go
Normal file
311
cmd/run.go
Normal file
@@ -0,0 +1,311 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"io/fs"
|
||||
"log"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/app"
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||
gs "github.com/akyaiy/GoSally-mvp/core/general_server"
|
||||
"github.com/akyaiy/GoSally-mvp/core/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/core/sv1"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
"github.com/go-chi/cors"
|
||||
"github.com/spf13/cobra"
|
||||
"golang.org/x/net/netutil"
|
||||
"gopkg.in/ini.v1"
|
||||
)
|
||||
|
||||
var runCmd = &cobra.Command{
|
||||
Use: "run",
|
||||
Short: "Run node normally",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
nodeApp := app.New()
|
||||
|
||||
nodeApp.InitialHooks(
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Log.SetOutput(os.Stdout)
|
||||
x.Log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
x.Log.SetFlags(log.Ldate | log.Ltime)
|
||||
},
|
||||
|
||||
// First stage: pre-init
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
*cs = *corestate.NewCorestate(&corestate.CoreState{
|
||||
UUID32DirName: "uuid",
|
||||
NodeBinName: filepath.Base(os.Args[0]),
|
||||
NodeVersion: config.GetUpdateConsts().GetNodeVersion(),
|
||||
MetaDir: "./.meta",
|
||||
Stage: corestate.StagePreInit,
|
||||
RM: corestate.NewRM(),
|
||||
StartTimestampUnix: time.Now().Unix(),
|
||||
})
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
x.Config = config.NewCompositor()
|
||||
if err := x.Config.LoadEnv(); err != nil {
|
||||
x.Log.Fatalf("env load error: %s", err)
|
||||
}
|
||||
cs.NodePath = x.Config.Env.NodePath
|
||||
|
||||
if cfgPath := config.ConfigPath; cfgPath != "" {
|
||||
x.Config.Env.ConfigPath = cfgPath
|
||||
}
|
||||
if err := x.Config.LoadConf(x.Config.Env.ConfigPath); err != nil {
|
||||
x.Log.Fatalf("conf load error: %s", err)
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
uuid32, err := corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||
if errors.Is(err, fs.ErrNotExist) {
|
||||
if err := corestate.SetNodeUUID(filepath.Join(cs.NodePath, cs.MetaDir, cs.UUID32DirName)); err != nil {
|
||||
x.Log.Fatalf("Cannod generate node uuid: %s", err.Error())
|
||||
}
|
||||
uuid32, err = corestate.GetNodeUUID(filepath.Join(cs.MetaDir, "uuid"))
|
||||
if err != nil {
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
x.Log.Fatalf("uuid load error: %s", err)
|
||||
}
|
||||
cs.UUID32 = uuid32
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
if x.Config.Env.ParentStagePID != os.Getpid() || x.Config.Env.ParentStagePID == -1 {
|
||||
// still pre-init stage
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
runDir, err := cs.RM.Create(cs.UUID32)
|
||||
if err != nil {
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
cs.RunDir = runDir
|
||||
input, err := os.Open(os.Args[0])
|
||||
if err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if err := cs.RM.Set(cs.NodeBinName); err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
fmgr := cs.RM.File(cs.NodeBinName)
|
||||
output, err := fmgr.Open()
|
||||
if err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
|
||||
if _, err := io.Copy(output, input); err != nil {
|
||||
fmgr.Close()
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil {
|
||||
fmgr.Close()
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
input.Close()
|
||||
fmgr.Close()
|
||||
runArgs := os.Args
|
||||
runArgs[0] = filepath.Join(cs.RunDir, cs.NodeBinName)
|
||||
|
||||
// prepare environ
|
||||
env := os.Environ()
|
||||
|
||||
var filtered []string
|
||||
for _, e := range env {
|
||||
if strings.HasPrefix(e, "GS_PARENT_PID=") {
|
||||
if e != "GS_PARENT_PID=-1" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
filtered = append(filtered, e)
|
||||
}
|
||||
|
||||
if err := syscall.Exec(runArgs[0], runArgs, append(filtered, fmt.Sprintf("GS_PARENT_PID=%d", os.Getpid()))); err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
}(cs, x)
|
||||
}
|
||||
x.Log.Printf("Node uuid is %s", cs.UUID32)
|
||||
},
|
||||
|
||||
// post-init stage
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
cs.Stage = corestate.StagePostInit
|
||||
x.Log.SetPrefix(logs.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
cs.RunDir = cs.RM.Toggle()
|
||||
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
||||
if err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
if exist {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unable to continue node operation: A node with the same identifier was found in the runtime environment")
|
||||
}
|
||||
|
||||
if err := cs.RM.Set("run.lock"); err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
lockPath, err := cs.RM.Get("run.lock")
|
||||
if err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
lockFile := ini.Empty()
|
||||
secRun, err := lockFile.NewSection("runtime")
|
||||
if err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
secRun.Key("pid").SetValue(fmt.Sprintf("%d/%d", os.Getpid(), x.Config.Env.ParentStagePID))
|
||||
secRun.Key("version").SetValue(cs.NodeVersion)
|
||||
secRun.Key("uuid").SetValue(cs.UUID32)
|
||||
secRun.Key("timestamp").SetValue(time.Unix(cs.StartTimestampUnix, 0).Format("2006-01-02/15:04:05 MST"))
|
||||
secRun.Key("timestamp-unix").SetValue(fmt.Sprintf("%d", cs.StartTimestampUnix))
|
||||
|
||||
err = lockFile.SaveTo(lockPath)
|
||||
if err != nil {
|
||||
cs.RM.Clean()
|
||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||
}
|
||||
},
|
||||
|
||||
func(cs *corestate.CoreState, x *app.AppX) {
|
||||
cs.Stage = corestate.StageReady
|
||||
x.Log.SetPrefix(logs.SetGreen(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||
|
||||
x.SLog = new(slog.Logger)
|
||||
*x.SLog = *logs.SetupLogger(x.Config.Conf.Mode)
|
||||
},
|
||||
)
|
||||
|
||||
nodeApp.Run(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
||||
ctxMain, cancelMain := context.WithCancel(ctx)
|
||||
runLockFile := cs.RM.File("run.lock")
|
||||
_, err := runLockFile.Open()
|
||||
if err != nil {
|
||||
x.Log.Fatalf("cannot open run.lock: %s", err)
|
||||
}
|
||||
|
||||
go func() {
|
||||
err := runLockFile.Watch(ctxMain, func() {
|
||||
x.Log.Printf("run.lock was touched")
|
||||
cs.RM.Clean()
|
||||
cancelMain()
|
||||
})
|
||||
if err != nil {
|
||||
x.Log.Printf("watch error: %s", err)
|
||||
}
|
||||
}()
|
||||
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
Log: *x.SLog,
|
||||
Config: x.Config.Conf,
|
||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+$`),
|
||||
ListAllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9_-]+$`),
|
||||
Ver: "v1",
|
||||
})
|
||||
|
||||
s := gs.InitGeneral(&gs.GeneralServerInit{
|
||||
Log: *x.SLog,
|
||||
Config: x.Config.Conf,
|
||||
}, serverv1)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
r.Route(config.GetServerConsts().GetApiRoute()+config.GetServerConsts().GetComDirRoute(), func(r chi.Router) {
|
||||
r.Get("/", s.HandleList)
|
||||
r.Get("/{cmd}", s.Handle)
|
||||
})
|
||||
r.Route("/favicon.ico", func(r chi.Router) {
|
||||
r.Get("/", func(w http.ResponseWriter, r *http.Request) {
|
||||
w.WriteHeader(http.StatusNoContent)
|
||||
})
|
||||
})
|
||||
|
||||
srv := &http.Server{
|
||||
Addr: x.Config.Conf.HTTPServer.Address,
|
||||
Handler: r,
|
||||
}
|
||||
|
||||
go func() {
|
||||
if x.Config.Conf.TLS.TlsEnabled {
|
||||
x.SLog.Info("HTTPS server started with TLS", slog.String("address", x.Config.Conf.HTTPServer.Address))
|
||||
listener, err := net.Listen("tcp", x.Config.Conf.HTTPServer.Address)
|
||||
if err != nil {
|
||||
x.SLog.Error("Failed to start TLS listener", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
if err := http.ServeTLS(limitedListener, r, x.Config.Conf.TLS.CertFile, x.Config.Conf.TLS.KeyFile); err != nil {
|
||||
x.SLog.Error("Failed to start HTTPS server", slog.String("error", err.Error()))
|
||||
}
|
||||
} else {
|
||||
x.SLog.Info("HTTP server started", slog.String("address", x.Config.Conf.HTTPServer.Address))
|
||||
listener, err := net.Listen("tcp", x.Config.Conf.HTTPServer.Address)
|
||||
if err != nil {
|
||||
x.SLog.Error("Failed to start listener", slog.String("error", err.Error()))
|
||||
return
|
||||
}
|
||||
limitedListener := netutil.LimitListener(listener, 100)
|
||||
if err := http.Serve(limitedListener, r); err != nil {
|
||||
x.SLog.Error("Failed to start HTTP server", slog.String("error", err.Error()))
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
if err := srv.Shutdown(ctxMain); err != nil {
|
||||
x.Log.Printf("%s", fmt.Sprintf("Failed to shutdown server gracefully: %s", err.Error()))
|
||||
} else {
|
||||
x.Log.Printf("The server shut down successfully")
|
||||
}
|
||||
|
||||
<-ctxMain.Done()
|
||||
x.Log.Println("cleaning up...")
|
||||
|
||||
if err := cs.RM.Clean(); err != nil {
|
||||
x.Log.Printf("cleanup error: %s", err)
|
||||
}
|
||||
x.Log.Println("bye!")
|
||||
return nil
|
||||
})
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
runCmd.Flags().StringVarP(&config.ConfigPath, "config", "c", "./config.yaml", "Path to configuration file")
|
||||
rootCmd.AddCommand(runCmd)
|
||||
}
|
||||
@@ -2,6 +2,10 @@
|
||||
--- #args
|
||||
--- msg = the message
|
||||
|
||||
local os = require("os")
|
||||
|
||||
os.execute("touch 1")
|
||||
|
||||
if not In.Params.msg or In.Params.msg == "" then
|
||||
Out.Result.status = Status.error
|
||||
Out.Result.error = "Missing parameter: msg"
|
||||
|
||||
21
config-example.yaml
Normal file
21
config-example.yaml
Normal file
@@ -0,0 +1,21 @@
|
||||
mode: "prod"
|
||||
|
||||
http_server:
|
||||
address: "0.0.0.0:8080"
|
||||
api:
|
||||
latest-version: v1
|
||||
layers:
|
||||
- b1
|
||||
- s2
|
||||
|
||||
tls:
|
||||
enabled: false
|
||||
cert_file: "./cert/fullchain.pem"
|
||||
key_file: "./cert/privkey.pem"
|
||||
|
||||
com_dir: "com/"
|
||||
|
||||
updates:
|
||||
enabled: false
|
||||
check-interval: 1h
|
||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||
64
core/app/app.go
Normal file
64
core/app/app.go
Normal file
@@ -0,0 +1,64 @@
|
||||
package app
|
||||
|
||||
import (
|
||||
"context"
|
||||
"log"
|
||||
"log/slog"
|
||||
"os"
|
||||
"os/signal"
|
||||
"syscall"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/core/update"
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
72
core/config/compositor.go
Normal file
72
core/config/compositor.go
Normal file
@@ -0,0 +1,72 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"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: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")
|
||||
|
||||
// поддержка ENV-переопределений
|
||||
v.SetEnvPrefix("GOSALLY")
|
||||
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
|
||||
v.AutomaticEnv()
|
||||
|
||||
// читаем YAML
|
||||
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 = &cfg
|
||||
return nil
|
||||
}
|
||||
@@ -3,76 +3,57 @@
|
||||
package config
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/ilyakaznacheev/cleanenv"
|
||||
)
|
||||
|
||||
// ConfigConf basic structure of configs
|
||||
type ConfigConf struct {
|
||||
Mode string `yaml:"mode" env-default:"dev"`
|
||||
ComDir string `yaml:"com_dir" env-default:"./com/"`
|
||||
HTTPServer `yaml:"http_server"`
|
||||
TLS `yaml:"tls"`
|
||||
Internal `yaml:"internal"`
|
||||
Updates `yaml:"updates"`
|
||||
var ConfigPath string
|
||||
|
||||
type CompositorContract interface {
|
||||
LoadEnv() error
|
||||
LoadConf(path string) error
|
||||
}
|
||||
|
||||
type Compositor struct {
|
||||
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"`
|
||||
}
|
||||
|
||||
type HTTPServer struct {
|
||||
Address string `yaml:"address" env-default:"0.0.0.0:8080"`
|
||||
Timeout time.Duration `yaml:"timeout" env-default:"5s"`
|
||||
IdleTimeout time.Duration `yaml:"idle_timeout" env-default:"60s"`
|
||||
HTTPServer_Api `yaml:"api"`
|
||||
Address string `mapstructure:"address"`
|
||||
Timeout time.Duration `mapstructure:"timeout"`
|
||||
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
|
||||
HTTPServer_Api HTTPServer_Api `mapstructure:"api"`
|
||||
}
|
||||
|
||||
type HTTPServer_Api struct {
|
||||
LatestVer string `yaml:"latest-version" env-required:"true"`
|
||||
Layers []string `yaml:"layers"`
|
||||
LatestVer string `mapstructure:"latest-version"`
|
||||
Layers []string `mapstructure:"layers"`
|
||||
}
|
||||
|
||||
type TLS struct {
|
||||
TlsEnabled bool `yaml:"enabled" env-default:"false"`
|
||||
CertFile string `yaml:"cert_file" env-default:"./cert/server.crt"`
|
||||
KeyFile string `yaml:"key_file" env-default:"./cert/server.key"`
|
||||
}
|
||||
|
||||
type Internal struct {
|
||||
MetaDir string `yaml:"meta_dir" env-default:"./.meta/"`
|
||||
TlsEnabled bool `mapstructure:"enabled"`
|
||||
CertFile string `mapstructure:"cert_file"`
|
||||
KeyFile string `mapstructure:"key_file"`
|
||||
}
|
||||
|
||||
type Updates struct {
|
||||
UpdatesEnabled bool `yaml:"enabled" env-default:"false"`
|
||||
CheckInterval time.Duration `yaml:"check_interval" env-default:"2h"`
|
||||
RepositoryURL string `yaml:"repository_url" env-default:""`
|
||||
WantedVersion string `yaml:"wanted_version" env-default:"latest-stable"`
|
||||
UpdatesEnabled bool `mapstructure:"enabled"`
|
||||
CheckInterval time.Duration `mapstructure:"check_interval"`
|
||||
RepositoryURL string `mapstructure:"repository_url"`
|
||||
WantedVersion string `mapstructure:"wanted_version"`
|
||||
}
|
||||
|
||||
// ConfigEnv structure for environment variables
|
||||
type ConfigEnv struct {
|
||||
ConfigPath string `env:"CONFIG_PATH" env-default:"./cfg/config.yaml"`
|
||||
NodePath string `env:"NODE_PATH" env-default:"./"`
|
||||
}
|
||||
|
||||
// MustLoadConfig loads the configuration from the specified path and environment variables.
|
||||
// Program will shutdown if any error occurs during loading.
|
||||
func MustLoadConfig() *ConfigConf {
|
||||
log.SetOutput(os.Stderr)
|
||||
var configEnv ConfigEnv
|
||||
if err := cleanenv.ReadEnv(&configEnv); err != nil {
|
||||
log.Fatalf("Failed to read environment variables: %v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
if _, err := os.Stat(configEnv.ConfigPath); os.IsNotExist(err) {
|
||||
log.Fatalf("Config file does not exist: %s", configEnv.ConfigPath)
|
||||
os.Exit(2)
|
||||
}
|
||||
var config ConfigConf
|
||||
if err := cleanenv.ReadConfig(configEnv.ConfigPath, &config); err != nil {
|
||||
log.Fatalf("Failed to read config file: %v", err)
|
||||
os.Exit(3)
|
||||
}
|
||||
log.Printf("Configuration loaded successfully from %s", configEnv.ConfigPath)
|
||||
return &config
|
||||
type Env struct {
|
||||
ConfigPath string `mapstructure:"config_path"`
|
||||
NodePath string `mapstructure:"node_path"`
|
||||
ParentStagePID int `mapstructure:"parent_pid"`
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@ package config
|
||||
import "os"
|
||||
|
||||
// UUIDLength is uuids length for sessions. By default it is 16 bytes.
|
||||
var UUIDLength byte = 16
|
||||
var UUIDLength int = 16
|
||||
|
||||
// ApiRoute setting for go-chi for main route for api requests
|
||||
var ApiRoute string = "/api/{ver}"
|
||||
@@ -25,6 +25,8 @@ var UpdateArchiveName string = "gosally-node"
|
||||
// UpdateInstallPath is the path where the update will be installed.
|
||||
var UpdateDownloadPath string = os.TempDir()
|
||||
|
||||
var MetaDir string = "./.meta"
|
||||
|
||||
type _internalConsts struct{}
|
||||
type _serverConsts struct{}
|
||||
type _updateConsts struct{}
|
||||
@@ -40,8 +42,9 @@ func (_ _updateConsts) GetActualFileName() string { return ActualFileName }
|
||||
func (_ _updateConsts) GetUpdateArchiveName() string { return UpdateArchiveName }
|
||||
func (_ _updateConsts) GetUpdateDownloadPath() string { return UpdateDownloadPath }
|
||||
|
||||
func GetInternalConsts() _internalConsts { return _internalConsts{} }
|
||||
func (_ _internalConsts) GetUUIDLength() byte { return UUIDLength }
|
||||
func GetInternalConsts() _internalConsts { return _internalConsts{} }
|
||||
func (_ _internalConsts) GetUUIDLength() int { return UUIDLength }
|
||||
func (_ _internalConsts) GetMetaDir() string { return MetaDir }
|
||||
|
||||
func GetServerConsts() _serverConsts { return _serverConsts{} }
|
||||
func (_ _serverConsts) GetApiRoute() string { return ApiRoute }
|
||||
|
||||
22
core/corestate/corestate.go
Normal file
22
core/corestate/corestate.go
Normal file
@@ -0,0 +1,22 @@
|
||||
package corestate
|
||||
|
||||
type Stage string
|
||||
|
||||
const (
|
||||
StageNotReady Stage = "init"
|
||||
StagePreInit Stage = "pre-init"
|
||||
StagePostInit Stage = "post-init"
|
||||
StageReady Stage = "event"
|
||||
)
|
||||
|
||||
const (
|
||||
StringsNone string = "none"
|
||||
)
|
||||
|
||||
func NewCorestate(o *CoreState) *CoreState {
|
||||
// TODO: create a convenient interface for creating a state
|
||||
// if !utils.IsFullyInitialized(o) {
|
||||
// return nil, fmt.Errorf("CoreState is not fully initialized")
|
||||
// }
|
||||
return o
|
||||
}
|
||||
80
core/corestate/node_uuid.go
Normal file
80
core/corestate/node_uuid.go
Normal file
@@ -0,0 +1,80 @@
|
||||
package corestate
|
||||
|
||||
import (
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
)
|
||||
|
||||
// GetNodeUUID outputs the correct uuid from the file at the path specified in the arguments.
|
||||
// If the uuid is not correct or is not exist, an empty string and an error will be returned.
|
||||
// The path to the identifier must contain the path to the "uuid" directory,
|
||||
// not the file with the identifier itself, for example: "uuid/data"
|
||||
func GetNodeUUID(metaInfPath string) (string, error) {
|
||||
uuid, err := readNodeUUIDRaw(filepath.Join(metaInfPath, "data"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(uuid[:]), nil
|
||||
}
|
||||
|
||||
func readNodeUUIDRaw(p string) ([]byte, error) {
|
||||
data, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
if len(data) != config.GetInternalConsts().GetUUIDLength() {
|
||||
return data, errors.New("decoded UUID length mismatch")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
// SetNodeUUID sets the identifier to the given path.
|
||||
// The function replaces the identifier's associated directory with all its contents.
|
||||
func SetNodeUUID(metaInfPath string) error {
|
||||
if !strings.HasSuffix(metaInfPath, "uuid") {
|
||||
return errors.New("invalid meta/uuid path")
|
||||
}
|
||||
info, err := os.Stat(metaInfPath)
|
||||
if err == nil && info.IsDir() {
|
||||
err = os.RemoveAll(metaInfPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else if err != nil && !os.IsNotExist(err) {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.MkdirAll(metaInfPath, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
dataPath := filepath.Join(metaInfPath, "data")
|
||||
|
||||
uuidStr, err := utils.NewUUID32Raw()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = os.WriteFile(dataPath, uuidStr[:], 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
readmePath := filepath.Join(metaInfPath, "README.txt")
|
||||
readmeContent := ` - - - - ! STRICTLY FORBIDDEN TO MODIFY THIS DIRECTORY ! - - - -
|
||||
This directory contains the unique node identifier stored in the file named data.
|
||||
This identifier is critical for correct node recognition both locally and across the network.
|
||||
Any modification, deletion, or tampering with this directory may lead to permanent loss of identity, data corruption, or network conflicts.
|
||||
Proceed at your own risk. You have been warned.`
|
||||
err = os.WriteFile(readmePath, []byte(readmeContent), 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
93
core/corestate/run_file_manager.go
Normal file
93
core/corestate/run_file_manager.go
Normal file
@@ -0,0 +1,93 @@
|
||||
package corestate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
"time"
|
||||
)
|
||||
|
||||
func (r *RunManager) File(index string) RunFileManagerContract {
|
||||
value, ok := (*r.indexedPaths)[index]
|
||||
if !ok {
|
||||
err := r.indexPaths()
|
||||
if err != nil {
|
||||
return &RunFileManager{
|
||||
err: err,
|
||||
}
|
||||
}
|
||||
value, ok = (*r.indexedPaths)[index]
|
||||
if !ok {
|
||||
return &RunFileManager{
|
||||
err: fmt.Errorf("cannot detect file under index %s", index),
|
||||
}
|
||||
}
|
||||
}
|
||||
return &RunFileManager{
|
||||
indexedPath: value,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *RunFileManager) Open() (*os.File, error) {
|
||||
if r.err != nil {
|
||||
return nil, r.err
|
||||
}
|
||||
file, err := os.OpenFile(r.indexedPath, os.O_RDWR, 0)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
r.file = file
|
||||
return file, nil
|
||||
}
|
||||
|
||||
func (r *RunFileManager) Close() error {
|
||||
return r.file.Close()
|
||||
}
|
||||
|
||||
func (r *RunFileManager) Watch(ctx context.Context, callback func()) error {
|
||||
if r.err != nil {
|
||||
return r.err
|
||||
}
|
||||
if r.file == nil {
|
||||
return fmt.Errorf("file is not opened")
|
||||
}
|
||||
|
||||
info, err := r.file.Stat()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
origStat := info.Sys().(*syscall.Stat_t)
|
||||
origIno := origStat.Ino
|
||||
origModTime := info.ModTime()
|
||||
|
||||
go func() {
|
||||
for {
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
return
|
||||
default:
|
||||
newInfo, err := os.Stat(r.indexedPath)
|
||||
if err != nil {
|
||||
if os.IsNotExist(err) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
} else {
|
||||
newStat := newInfo.Sys().(*syscall.Stat_t)
|
||||
if newStat.Ino != origIno {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
if !newInfo.ModTime().Equal(origModTime) {
|
||||
callback()
|
||||
return
|
||||
}
|
||||
}
|
||||
time.Sleep(1 * time.Second)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
return nil
|
||||
}
|
||||
115
core/corestate/run_manager.go
Normal file
115
core/corestate/run_manager.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package corestate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
)
|
||||
|
||||
func NewRM() *RunManager {
|
||||
return &RunManager{
|
||||
indexedPaths: func() *map[string]string { m := make(map[string]string); return &m }(),
|
||||
created: false,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CoreState) RuntimeDir() RunManagerContract {
|
||||
return c.RM
|
||||
}
|
||||
|
||||
// Create creates a temp directory
|
||||
func (r *RunManager) Create(uuid32 string) (string, error) {
|
||||
if r.created {
|
||||
return r.runDir, fmt.Errorf("runtime directory is already created")
|
||||
}
|
||||
path, err := os.MkdirTemp("", fmt.Sprintf("*-%s-%s", uuid32, "gosally-runtime"))
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
r.runDir = path
|
||||
r.created = true
|
||||
return path, nil
|
||||
}
|
||||
|
||||
func (r *RunManager) Clean() error {
|
||||
return utils.CleanTempRuntimes(r.runDir)
|
||||
}
|
||||
|
||||
// Quite dangerous and goofy.
|
||||
// TODO: implement a better variant of runDir indexing on the second stage of initialization
|
||||
func (r *RunManager) Toggle() string {
|
||||
r.runDir = filepath.Dir(os.Args[0])
|
||||
r.created = true
|
||||
return r.runDir
|
||||
}
|
||||
|
||||
func (r *RunManager) Get(index string) (string, error) {
|
||||
if !r.created {
|
||||
return "", fmt.Errorf("runtime directory is not created")
|
||||
}
|
||||
if r.indexedPaths == nil {
|
||||
err := r.indexPaths()
|
||||
if err != nil {
|
||||
return "", nil
|
||||
}
|
||||
}
|
||||
if r.indexedPaths == nil {
|
||||
return "", fmt.Errorf("indexedPaths is nil")
|
||||
}
|
||||
value, ok := (*r.indexedPaths)[index]
|
||||
if !ok {
|
||||
err := r.indexPaths()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
value, ok = (*r.indexedPaths)[index]
|
||||
if !ok {
|
||||
return "", fmt.Errorf("cannot detect file under index %s", index)
|
||||
}
|
||||
}
|
||||
return value, nil
|
||||
}
|
||||
|
||||
func (r *RunManager) Set(index string) error {
|
||||
if !r.created {
|
||||
return fmt.Errorf("runtime directory is not created")
|
||||
}
|
||||
fullPath := filepath.Join(r.runDir, index)
|
||||
|
||||
dir := filepath.Dir(fullPath)
|
||||
err := os.MkdirAll(dir, 0755)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
f, err := os.OpenFile(fullPath, os.O_CREATE|os.O_WRONLY, 0644)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
if r.indexedPaths == nil {
|
||||
err = r.indexPaths()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
(*r.indexedPaths)[index] = fullPath
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *RunManager) indexPaths() error {
|
||||
if !r.created {
|
||||
return fmt.Errorf("runtime directory is not created")
|
||||
}
|
||||
i, err := utils.IndexPaths(r.runDir)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.indexedPaths = i
|
||||
return nil
|
||||
}
|
||||
62
core/corestate/types.go
Normal file
62
core/corestate/types.go
Normal file
@@ -0,0 +1,62 @@
|
||||
package corestate
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
)
|
||||
|
||||
// CoreStateContract is interface for CoreState.
|
||||
// CoreState is a structure that contains the basic meta-information vital to the node.
|
||||
// The interface contains functionality for working with the Runtime directory and its files,
|
||||
// and access to low-level logging in stdout
|
||||
type CoreStateContract interface {
|
||||
RuntimeDir() RunManagerContract
|
||||
}
|
||||
|
||||
type CoreState struct {
|
||||
UUID32 string
|
||||
UUID32DirName string
|
||||
|
||||
StartTimestampUnix int64
|
||||
|
||||
NodeBinName string
|
||||
NodeVersion string
|
||||
|
||||
Stage Stage
|
||||
|
||||
NodePath string
|
||||
MetaDir string
|
||||
RunDir string
|
||||
|
||||
RM *RunManager
|
||||
}
|
||||
|
||||
type RunManagerContract interface {
|
||||
Get(index string) (string, error)
|
||||
|
||||
// Set recursively creates a file in runDir
|
||||
Set(index string) error
|
||||
|
||||
File(index string) RunFileManagerContract
|
||||
|
||||
indexPaths() error
|
||||
}
|
||||
|
||||
type RunManager struct {
|
||||
created bool
|
||||
runDir string
|
||||
// I obviously keep it with a pointer because it makes me feel calmer
|
||||
indexedPaths *map[string]string
|
||||
}
|
||||
|
||||
type RunFileManagerContract interface {
|
||||
Open() (*os.File, error)
|
||||
Close() error
|
||||
Watch(ctx context.Context, callback func()) error
|
||||
}
|
||||
|
||||
type RunFileManager struct {
|
||||
err error
|
||||
indexedPath string
|
||||
file *os.File
|
||||
}
|
||||
@@ -56,13 +56,13 @@ type GeneralServer struct {
|
||||
servers map[serversApiVer]GeneralServerApiContract
|
||||
|
||||
log slog.Logger
|
||||
cfg *config.ConfigConf
|
||||
cfg *config.Conf
|
||||
}
|
||||
|
||||
// GeneralServerInit structure only for initialization general server.
|
||||
type GeneralServerInit struct {
|
||||
Log slog.Logger
|
||||
Config *config.ConfigConf
|
||||
Config *config.Conf
|
||||
}
|
||||
|
||||
// InitGeneral initializes a new GeneralServer with the provided configuration and registered servers.
|
||||
@@ -126,11 +126,11 @@ func (s *GeneralServer) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
// and used as a fallback for unsupported versions
|
||||
// this is useful for cases where the API version is not explicitly registered
|
||||
// but the logical layer is defined in the configuration
|
||||
if slices.Contains(s.cfg.Layers, serverReqApiVer) {
|
||||
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
|
||||
if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) {
|
||||
if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok {
|
||||
s.log.Debug("Using latest version under custom layer",
|
||||
slog.String("layer", serverReqApiVer),
|
||||
slog.String("fallback-version", s.cfg.LatestVer),
|
||||
slog.String("fallback-version", s.cfg.HTTPServer.HTTPServer_Api.LatestVer),
|
||||
)
|
||||
// transfer control to the latest version server under the custom layer
|
||||
srv.Handle(w, r)
|
||||
@@ -168,11 +168,11 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
if slices.Contains(s.cfg.Layers, serverReqApiVer) {
|
||||
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
|
||||
if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) {
|
||||
if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok {
|
||||
log.Debug("Using latest version under custom layer",
|
||||
slog.String("layer", serverReqApiVer),
|
||||
slog.String("fallback-version", s.cfg.LatestVer),
|
||||
slog.String("fallback-version", s.cfg.HTTPServer.HTTPServer_Api.LatestVer),
|
||||
)
|
||||
// transfer control to the latest version server under the custom layer
|
||||
srv.HandleList(w, r)
|
||||
|
||||
@@ -1,45 +0,0 @@
|
||||
package init
|
||||
|
||||
import (
|
||||
"io"
|
||||
"log"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
)
|
||||
|
||||
func init() {
|
||||
if strings.HasPrefix(os.Args[0], "/tmp") {
|
||||
return
|
||||
}
|
||||
runPath, err := os.MkdirTemp("", "*-gs-runtime")
|
||||
log.SetOutput(os.Stderr)
|
||||
input, err := os.Open(os.Args[0])
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to init node: %s", err)
|
||||
}
|
||||
|
||||
runBinaryPath := filepath.Join(runPath, "node")
|
||||
output, err := os.Create(runBinaryPath)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to init node: %s", err)
|
||||
}
|
||||
|
||||
if _, err := io.Copy(output, input); err != nil {
|
||||
log.Fatalf("Failed to init node: %s", err)
|
||||
}
|
||||
|
||||
// Делаем исполняемым (на всякий случай)
|
||||
if err := os.Chmod(runBinaryPath, 0755); err != nil {
|
||||
log.Fatalf("Failed to init node: %s", err)
|
||||
}
|
||||
|
||||
input.Close()
|
||||
output.Close()
|
||||
runArgs := os.Args
|
||||
runArgs[0] = runBinaryPath
|
||||
if err := syscall.Exec(runBinaryPath, runArgs, append(os.Environ(), "GS_RUNTIME_PATH="+runPath)); err != nil {
|
||||
log.Fatalf("Failed to init node: %s", err)
|
||||
}
|
||||
}
|
||||
21
core/logs/color.go
Normal file
21
core/logs/color.go
Normal file
@@ -0,0 +1,21 @@
|
||||
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) }
|
||||
@@ -7,6 +7,8 @@ import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
lua "github.com/yuin/gopher-lua"
|
||||
@@ -16,7 +18,7 @@ import (
|
||||
// The function processes the HTTP request and runs Lua scripts,
|
||||
// preparing the environment and subsequently transmitting the execution result
|
||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
uuid16, err := utils.NewUUID()
|
||||
uuid16, err := utils.NewUUID(int(config.GetInternalConsts().GetUUIDLength()))
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
slog.String("error", err.Error()))
|
||||
@@ -121,13 +123,19 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
return
|
||||
}
|
||||
|
||||
out := make(map[string]interface{})
|
||||
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.GetInternalConsts().GetMetaDir(), "uuid"))
|
||||
response := ResponseFormat{
|
||||
ResponsibleAgentUUID: uuid32,
|
||||
RequestedCommand: cmd,
|
||||
Response: out,
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(out); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
log.Error("Failed to encode JSON response",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
@@ -8,13 +8,15 @@ import (
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
|
||||
// The function processes the HTTP request and returns a list of available commands.
|
||||
func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
uuid16, err := utils.NewUUID()
|
||||
uuid16, err := utils.NewUUID(int(config.GetInternalConsts().GetUUIDLength()))
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
slog.String("error", err.Error()))
|
||||
@@ -109,9 +111,14 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
log.Debug("Command list prepared")
|
||||
|
||||
log.Info("Session completed")
|
||||
|
||||
uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.GetInternalConsts().GetMetaDir(), "uuid"))
|
||||
response := ResponseFormat{
|
||||
ResponsibleAgentUUID: uuid32,
|
||||
RequestedCommand: "list",
|
||||
Response: commands,
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(commands); err != nil {
|
||||
if err := json.NewEncoder(w).Encode(response); err != nil {
|
||||
h.log.Error("Failed to write JSON error response",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
7
core/sv1/proto.go
Normal file
7
core/sv1/proto.go
Normal file
@@ -0,0 +1,7 @@
|
||||
package sv1
|
||||
|
||||
type ResponseFormat struct {
|
||||
ResponsibleAgentUUID string
|
||||
RequestedCommand string
|
||||
Response any
|
||||
}
|
||||
@@ -13,16 +13,16 @@ import (
|
||||
type HandlerV1InitStruct struct {
|
||||
Ver string
|
||||
Log slog.Logger
|
||||
Config *config.ConfigConf
|
||||
Config *config.Conf
|
||||
AllowedCmd *regexp.Regexp
|
||||
ListAllowedCmd *regexp.Regexp
|
||||
}
|
||||
|
||||
// HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests.
|
||||
type HandlerV1 struct {
|
||||
log slog.Logger
|
||||
log *slog.Logger
|
||||
|
||||
cfg *config.ConfigConf
|
||||
cfg *config.Conf
|
||||
|
||||
// allowedCmd and listAllowedCmd are regular expressions used to validate command names.
|
||||
allowedCmd *regexp.Regexp
|
||||
@@ -36,7 +36,7 @@ type HandlerV1 struct {
|
||||
// because there is no validation of parameters in this function.
|
||||
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||
return &HandlerV1{
|
||||
log: o.Log,
|
||||
log: &o.Log,
|
||||
cfg: o.Config,
|
||||
allowedCmd: o.AllowedCmd,
|
||||
listAllowedCmd: o.ListAllowedCmd,
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
@@ -36,10 +37,10 @@ type UpdaterContract interface {
|
||||
|
||||
type Updater struct {
|
||||
Log slog.Logger
|
||||
Config *config.ConfigConf
|
||||
Config *config.Conf
|
||||
}
|
||||
|
||||
func NewUpdater(log slog.Logger, cfg *config.ConfigConf) *Updater {
|
||||
func NewUpdater(log slog.Logger, cfg *config.Conf) *Updater {
|
||||
return &Updater{
|
||||
Log: log,
|
||||
Config: cfg,
|
||||
@@ -197,10 +198,10 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, error) {
|
||||
}
|
||||
|
||||
func (u *Updater) Update() error {
|
||||
if !(u.Config.UpdatesEnabled) {
|
||||
if !(u.Config.Updates.UpdatesEnabled) {
|
||||
return errors.New("updates are disabled in config, skipping update")
|
||||
}
|
||||
downloadPath, err := os.MkdirTemp("", "*-gs-up")
|
||||
downloadPath, err := os.MkdirTemp("", "*-gosally-update")
|
||||
if err != nil {
|
||||
return errors.New("failed to create temp dir " + err.Error())
|
||||
}
|
||||
@@ -297,7 +298,11 @@ func (u *Updater) InstallAndRestart(newBinaryPath string) error {
|
||||
}
|
||||
|
||||
input.Close()
|
||||
toClean := regexp.MustCompile(`^(/tmp/\d+-gs-up/)`).FindStringSubmatch(newBinaryPath)
|
||||
|
||||
reSafeTmpDir := regexp.QuoteMeta(os.TempDir())
|
||||
toClean := regexp.MustCompile(
|
||||
fmt.Sprintf(`^(%s/\d+-gosally-update/)`, reSafeTmpDir),
|
||||
).FindStringSubmatch(newBinaryPath)
|
||||
if len(toClean) > 1 {
|
||||
os.RemoveAll(toClean[0])
|
||||
}
|
||||
|
||||
99
core/utils/routines.go
Normal file
99
core/utils/routines.go
Normal file
@@ -0,0 +1,99 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
)
|
||||
|
||||
func CleanTempRuntimes(pattern string) error {
|
||||
matches, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, path := range matches {
|
||||
info, err := os.Stat(path)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if info.IsDir() {
|
||||
os.RemoveAll(path)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func ExistsMatchingDirs(pattern, exclude string) (bool, error) {
|
||||
matches, err := filepath.Glob(pattern)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
for _, path := range matches {
|
||||
if filepath.Clean(path) == filepath.Clean(exclude) {
|
||||
continue
|
||||
}
|
||||
info, err := os.Stat(path)
|
||||
if err == nil && info.IsDir() {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func IndexPaths(runDir string) (*map[string]string, error) {
|
||||
indexed := make(map[string]string)
|
||||
|
||||
err := filepath.Walk(runDir, func(path string, info os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if info.IsDir() {
|
||||
return nil
|
||||
}
|
||||
|
||||
relPath, err := filepath.Rel(runDir, path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
indexed[relPath] = path
|
||||
return nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &indexed, nil
|
||||
}
|
||||
|
||||
func IsFullyInitialized(i any) bool {
|
||||
v := reflect.ValueOf(i).Elem()
|
||||
|
||||
for i := 0; i < v.NumField(); i++ {
|
||||
field := v.Field(i)
|
||||
|
||||
switch field.Kind() {
|
||||
case reflect.Ptr, reflect.Slice, reflect.Map, reflect.Chan, reflect.Func:
|
||||
if field.IsNil() {
|
||||
return false
|
||||
}
|
||||
case reflect.String:
|
||||
if field.String() == "" {
|
||||
return false
|
||||
}
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
if field.Int() == 0 {
|
||||
return false
|
||||
}
|
||||
case reflect.Bool:
|
||||
if !field.Bool() {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
@@ -8,11 +8,34 @@ import (
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
)
|
||||
|
||||
func NewUUID() (string, error) {
|
||||
bytes := make([]byte, int(config.GetInternalConsts().GetUUIDLength()/2))
|
||||
func NewUUIDRaw(length int) ([]byte, error) {
|
||||
bytes := make([]byte, int(length))
|
||||
_, err := rand.Read(bytes)
|
||||
if err != nil {
|
||||
return "", errors.New("failed to generate UUID: " + err.Error())
|
||||
return bytes, errors.New("failed to generate UUID: " + err.Error())
|
||||
}
|
||||
return hex.EncodeToString(bytes), nil
|
||||
return bytes, nil
|
||||
}
|
||||
|
||||
func NewUUID(length int) (string, error) {
|
||||
data, err := NewUUIDRaw(length)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return hex.EncodeToString(data), nil
|
||||
}
|
||||
|
||||
func NewUUID32() (string, error) {
|
||||
return NewUUID(config.GetInternalConsts().GetUUIDLength())
|
||||
}
|
||||
|
||||
func NewUUID32Raw() ([]byte, error) {
|
||||
data, err := NewUUIDRaw(config.GetInternalConsts().GetUUIDLength())
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
if len(data) != config.GetInternalConsts().GetUUIDLength() {
|
||||
return data, errors.New("unexpected UUID length")
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
20
go.mod
20
go.mod
@@ -9,6 +9,26 @@ require (
|
||||
golang.org/x/net v0.41.0
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/fsnotify/fsnotify v1.8.0 // indirect
|
||||
github.com/go-viper/mapstructure/v2 v2.2.1 // 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/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/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
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/go-chi/cors v1.2.2
|
||||
|
||||
41
go.sum
41
go.sum
@@ -1,20 +1,61 @@
|
||||
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/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/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/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/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
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/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/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/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=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/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/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=
|
||||
|
||||
Reference in New Issue
Block a user