mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 20:12:25 +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/
|
bak/
|
||||||
bin/
|
bin/
|
||||||
cert/
|
cert/
|
||||||
|
tmp/
|
||||||
|
|
||||||
config.yaml
|
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_CFLAGS is: '$(CGO_CFLAGS)'"
|
||||||
@# @echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
|
@# @echo "CGO_LDFLAGS is: '$(CGO_LDFLAGS)'"
|
||||||
@# CGO_CFLAGS="$(CGO_CFLAGS)" CGO_LDFLAGS="$(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
|
run: build
|
||||||
@echo "Running!"
|
@echo "Running!"
|
||||||
./$(BIN_DIR)/$(APP_NAME)
|
exec ./$(BIN_DIR)/$(APP_NAME)
|
||||||
|
|
||||||
runq: build
|
runq: build
|
||||||
@echo "Running!"
|
@echo "Running!"
|
||||||
./$(BIN_DIR)/$(APP_NAME) | jq
|
exec ./$(BIN_DIR)/$(APP_NAME) | jq
|
||||||
|
|
||||||
pure-run:
|
pure-run:
|
||||||
@echo "Running!"
|
@echo "Running!"
|
||||||
./$(BIN_DIR)/$(APP_NAME) | jq
|
exec ./$(BIN_DIR)/$(APP_NAME)
|
||||||
|
|
||||||
test:
|
test:
|
||||||
@go test ./... | grep -v '^?' || true
|
@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
|
--- #args
|
||||||
--- msg = the message
|
--- msg = the message
|
||||||
|
|
||||||
|
local os = require("os")
|
||||||
|
|
||||||
|
os.execute("touch 1")
|
||||||
|
|
||||||
if not In.Params.msg or In.Params.msg == "" then
|
if not In.Params.msg or In.Params.msg == "" then
|
||||||
Out.Result.status = Status.error
|
Out.Result.status = Status.error
|
||||||
Out.Result.error = "Missing parameter: msg"
|
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
|
package config
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
|
||||||
"os"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/ilyakaznacheev/cleanenv"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ConfigConf basic structure of configs
|
var ConfigPath string
|
||||||
type ConfigConf struct {
|
|
||||||
Mode string `yaml:"mode" env-default:"dev"`
|
type CompositorContract interface {
|
||||||
ComDir string `yaml:"com_dir" env-default:"./com/"`
|
LoadEnv() error
|
||||||
HTTPServer `yaml:"http_server"`
|
LoadConf(path string) error
|
||||||
TLS `yaml:"tls"`
|
}
|
||||||
Internal `yaml:"internal"`
|
|
||||||
Updates `yaml:"updates"`
|
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 {
|
type HTTPServer struct {
|
||||||
Address string `yaml:"address" env-default:"0.0.0.0:8080"`
|
Address string `mapstructure:"address"`
|
||||||
Timeout time.Duration `yaml:"timeout" env-default:"5s"`
|
Timeout time.Duration `mapstructure:"timeout"`
|
||||||
IdleTimeout time.Duration `yaml:"idle_timeout" env-default:"60s"`
|
IdleTimeout time.Duration `mapstructure:"idle_timeout"`
|
||||||
HTTPServer_Api `yaml:"api"`
|
HTTPServer_Api HTTPServer_Api `mapstructure:"api"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type HTTPServer_Api struct {
|
type HTTPServer_Api struct {
|
||||||
LatestVer string `yaml:"latest-version" env-required:"true"`
|
LatestVer string `mapstructure:"latest-version"`
|
||||||
Layers []string `yaml:"layers"`
|
Layers []string `mapstructure:"layers"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type TLS struct {
|
type TLS struct {
|
||||||
TlsEnabled bool `yaml:"enabled" env-default:"false"`
|
TlsEnabled bool `mapstructure:"enabled"`
|
||||||
CertFile string `yaml:"cert_file" env-default:"./cert/server.crt"`
|
CertFile string `mapstructure:"cert_file"`
|
||||||
KeyFile string `yaml:"key_file" env-default:"./cert/server.key"`
|
KeyFile string `mapstructure:"key_file"`
|
||||||
}
|
|
||||||
|
|
||||||
type Internal struct {
|
|
||||||
MetaDir string `yaml:"meta_dir" env-default:"./.meta/"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Updates struct {
|
type Updates struct {
|
||||||
UpdatesEnabled bool `yaml:"enabled" env-default:"false"`
|
UpdatesEnabled bool `mapstructure:"enabled"`
|
||||||
CheckInterval time.Duration `yaml:"check_interval" env-default:"2h"`
|
CheckInterval time.Duration `mapstructure:"check_interval"`
|
||||||
RepositoryURL string `yaml:"repository_url" env-default:""`
|
RepositoryURL string `mapstructure:"repository_url"`
|
||||||
WantedVersion string `yaml:"wanted_version" env-default:"latest-stable"`
|
WantedVersion string `mapstructure:"wanted_version"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// ConfigEnv structure for environment variables
|
// ConfigEnv structure for environment variables
|
||||||
type ConfigEnv struct {
|
type Env struct {
|
||||||
ConfigPath string `env:"CONFIG_PATH" env-default:"./cfg/config.yaml"`
|
ConfigPath string `mapstructure:"config_path"`
|
||||||
NodePath string `env:"NODE_PATH" env-default:"./"`
|
NodePath string `mapstructure:"node_path"`
|
||||||
}
|
ParentStagePID int `mapstructure:"parent_pid"`
|
||||||
|
|
||||||
// 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
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ package config
|
|||||||
import "os"
|
import "os"
|
||||||
|
|
||||||
// UUIDLength is uuids length for sessions. By default it is 16 bytes.
|
// 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
|
// ApiRoute setting for go-chi for main route for api requests
|
||||||
var ApiRoute string = "/api/{ver}"
|
var ApiRoute string = "/api/{ver}"
|
||||||
@@ -25,6 +25,8 @@ var UpdateArchiveName string = "gosally-node"
|
|||||||
// UpdateInstallPath is the path where the update will be installed.
|
// UpdateInstallPath is the path where the update will be installed.
|
||||||
var UpdateDownloadPath string = os.TempDir()
|
var UpdateDownloadPath string = os.TempDir()
|
||||||
|
|
||||||
|
var MetaDir string = "./.meta"
|
||||||
|
|
||||||
type _internalConsts struct{}
|
type _internalConsts struct{}
|
||||||
type _serverConsts struct{}
|
type _serverConsts struct{}
|
||||||
type _updateConsts struct{}
|
type _updateConsts struct{}
|
||||||
@@ -41,7 +43,8 @@ func (_ _updateConsts) GetUpdateArchiveName() string { return UpdateArchiveName
|
|||||||
func (_ _updateConsts) GetUpdateDownloadPath() string { return UpdateDownloadPath }
|
func (_ _updateConsts) GetUpdateDownloadPath() string { return UpdateDownloadPath }
|
||||||
|
|
||||||
func GetInternalConsts() _internalConsts { return _internalConsts{} }
|
func GetInternalConsts() _internalConsts { return _internalConsts{} }
|
||||||
func (_ _internalConsts) GetUUIDLength() byte { return UUIDLength }
|
func (_ _internalConsts) GetUUIDLength() int { return UUIDLength }
|
||||||
|
func (_ _internalConsts) GetMetaDir() string { return MetaDir }
|
||||||
|
|
||||||
func GetServerConsts() _serverConsts { return _serverConsts{} }
|
func GetServerConsts() _serverConsts { return _serverConsts{} }
|
||||||
func (_ _serverConsts) GetApiRoute() string { return ApiRoute }
|
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
|
servers map[serversApiVer]GeneralServerApiContract
|
||||||
|
|
||||||
log slog.Logger
|
log slog.Logger
|
||||||
cfg *config.ConfigConf
|
cfg *config.Conf
|
||||||
}
|
}
|
||||||
|
|
||||||
// GeneralServerInit structure only for initialization general server.
|
// GeneralServerInit structure only for initialization general server.
|
||||||
type GeneralServerInit struct {
|
type GeneralServerInit struct {
|
||||||
Log slog.Logger
|
Log slog.Logger
|
||||||
Config *config.ConfigConf
|
Config *config.Conf
|
||||||
}
|
}
|
||||||
|
|
||||||
// InitGeneral initializes a new GeneralServer with the provided configuration and registered servers.
|
// 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
|
// and used as a fallback for unsupported versions
|
||||||
// this is useful for cases where the API version is not explicitly registered
|
// this is useful for cases where the API version is not explicitly registered
|
||||||
// but the logical layer is defined in the configuration
|
// but the logical layer is defined in the configuration
|
||||||
if slices.Contains(s.cfg.Layers, serverReqApiVer) {
|
if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) {
|
||||||
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
|
if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok {
|
||||||
s.log.Debug("Using latest version under custom layer",
|
s.log.Debug("Using latest version under custom layer",
|
||||||
slog.String("layer", serverReqApiVer),
|
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
|
// transfer control to the latest version server under the custom layer
|
||||||
srv.Handle(w, r)
|
srv.Handle(w, r)
|
||||||
@@ -168,11 +168,11 @@ func (s *GeneralServer) HandleList(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if slices.Contains(s.cfg.Layers, serverReqApiVer) {
|
if slices.Contains(s.cfg.HTTPServer.HTTPServer_Api.Layers, serverReqApiVer) {
|
||||||
if srv, ok := s.servers[serversApiVer(s.cfg.LatestVer)]; ok {
|
if srv, ok := s.servers[serversApiVer(s.cfg.HTTPServer.HTTPServer_Api.LatestVer)]; ok {
|
||||||
log.Debug("Using latest version under custom layer",
|
log.Debug("Using latest version under custom layer",
|
||||||
slog.String("layer", serverReqApiVer),
|
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
|
// transfer control to the latest version server under the custom layer
|
||||||
srv.HandleList(w, r)
|
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"
|
"os"
|
||||||
"path/filepath"
|
"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/akyaiy/GoSally-mvp/core/utils"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
lua "github.com/yuin/gopher-lua"
|
lua "github.com/yuin/gopher-lua"
|
||||||
@@ -16,7 +18,7 @@ import (
|
|||||||
// The function processes the HTTP request and runs Lua scripts,
|
// The function processes the HTTP request and runs Lua scripts,
|
||||||
// preparing the environment and subsequently transmitting the execution result
|
// preparing the environment and subsequently transmitting the execution result
|
||||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
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 {
|
if err != nil {
|
||||||
h.log.Error("Failed to generate UUID",
|
h.log.Error("Failed to generate UUID",
|
||||||
slog.String("error", err.Error()))
|
slog.String("error", err.Error()))
|
||||||
@@ -121,13 +123,19 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
out := make(map[string]interface{})
|
out := make(map[string]any)
|
||||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
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")
|
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",
|
log.Error("Failed to encode JSON response",
|
||||||
slog.String("error", err.Error()))
|
slog.String("error", err.Error()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,13 +8,15 @@ import (
|
|||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||||
"github.com/go-chi/chi/v5"
|
"github.com/go-chi/chi/v5"
|
||||||
)
|
)
|
||||||
|
|
||||||
// The function processes the HTTP request and returns a list of available commands.
|
// The function processes the HTTP request and returns a list of available commands.
|
||||||
func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
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 {
|
if err != nil {
|
||||||
h.log.Error("Failed to generate UUID",
|
h.log.Error("Failed to generate UUID",
|
||||||
slog.String("error", err.Error()))
|
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.Debug("Command list prepared")
|
||||||
|
|
||||||
log.Info("Session completed")
|
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")
|
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",
|
h.log.Error("Failed to write JSON error response",
|
||||||
slog.String("error", err.Error()))
|
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 {
|
type HandlerV1InitStruct struct {
|
||||||
Ver string
|
Ver string
|
||||||
Log slog.Logger
|
Log slog.Logger
|
||||||
Config *config.ConfigConf
|
Config *config.Conf
|
||||||
AllowedCmd *regexp.Regexp
|
AllowedCmd *regexp.Regexp
|
||||||
ListAllowedCmd *regexp.Regexp
|
ListAllowedCmd *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
// HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests.
|
// HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests.
|
||||||
type HandlerV1 struct {
|
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 and listAllowedCmd are regular expressions used to validate command names.
|
||||||
allowedCmd *regexp.Regexp
|
allowedCmd *regexp.Regexp
|
||||||
@@ -36,7 +36,7 @@ type HandlerV1 struct {
|
|||||||
// because there is no validation of parameters in this function.
|
// because there is no validation of parameters in this function.
|
||||||
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
func InitV1Server(o *HandlerV1InitStruct) *HandlerV1 {
|
||||||
return &HandlerV1{
|
return &HandlerV1{
|
||||||
log: o.Log,
|
log: &o.Log,
|
||||||
cfg: o.Config,
|
cfg: o.Config,
|
||||||
allowedCmd: o.AllowedCmd,
|
allowedCmd: o.AllowedCmd,
|
||||||
listAllowedCmd: o.ListAllowedCmd,
|
listAllowedCmd: o.ListAllowedCmd,
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"archive/tar"
|
"archive/tar"
|
||||||
"compress/gzip"
|
"compress/gzip"
|
||||||
"errors"
|
"errors"
|
||||||
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
@@ -36,10 +37,10 @@ type UpdaterContract interface {
|
|||||||
|
|
||||||
type Updater struct {
|
type Updater struct {
|
||||||
Log slog.Logger
|
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{
|
return &Updater{
|
||||||
Log: log,
|
Log: log,
|
||||||
Config: cfg,
|
Config: cfg,
|
||||||
@@ -197,10 +198,10 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (u *Updater) Update() 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")
|
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 {
|
if err != nil {
|
||||||
return errors.New("failed to create temp dir " + err.Error())
|
return errors.New("failed to create temp dir " + err.Error())
|
||||||
}
|
}
|
||||||
@@ -297,7 +298,11 @@ func (u *Updater) InstallAndRestart(newBinaryPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
input.Close()
|
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 {
|
if len(toClean) > 1 {
|
||||||
os.RemoveAll(toClean[0])
|
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"
|
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||||
)
|
)
|
||||||
|
|
||||||
func NewUUID() (string, error) {
|
func NewUUIDRaw(length int) ([]byte, error) {
|
||||||
bytes := make([]byte, int(config.GetInternalConsts().GetUUIDLength()/2))
|
bytes := make([]byte, int(length))
|
||||||
_, err := rand.Read(bytes)
|
_, err := rand.Read(bytes)
|
||||||
if err != nil {
|
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
|
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 (
|
require (
|
||||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||||
github.com/go-chi/cors v1.2.2
|
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.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 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg=
|
||||||
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
|
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 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
|
||||||
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
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 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
|
||||||
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
|
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 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
|
||||||
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
|
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 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||||
github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4=
|
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 h1:kYKnWBjvbNP4XLT3+bPEwAXJx262OhaHDWDVOPjL46M=
|
||||||
github.com/yuin/gopher-lua v1.1.1/go.mod h1:GBR0iDaNXjAgGg9zfCvksxSRnQx76gclCIb7kdAd1Pw=
|
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 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw=
|
||||||
golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA=
|
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 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
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 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
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 h1:slmdOY3vp8a7KQbHkL+FLbvbkgMqmXojpFUO/jENuqQ=
|
||||||
|
|||||||
Reference in New Issue
Block a user