mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 21:52:24 +00:00
Compare commits
5 Commits
22ff90ca56
...
b97febc16e
| Author | SHA1 | Date | |
|---|---|---|---|
| b97febc16e | |||
| 149cfc0a17 | |||
| 00276dc817 | |||
| ec2ef34f23 | |||
| aebc3d2e9b |
2
Makefile
2
Makefile
@@ -50,8 +50,10 @@ test:
|
|||||||
fmt:
|
fmt:
|
||||||
@go fmt ./internal/./...
|
@go fmt ./internal/./...
|
||||||
@go fmt ./cmd/./...
|
@go fmt ./cmd/./...
|
||||||
|
@go fmt ./hooks/./...
|
||||||
@$(GOPATH)/bin/goimports -w ./internal/
|
@$(GOPATH)/bin/goimports -w ./internal/
|
||||||
@$(GOPATH)/bin/goimports -w ./cmd/
|
@$(GOPATH)/bin/goimports -w ./cmd/
|
||||||
|
@$(GOPATH)/bin/goimports -w ./hooks/
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
@go vet ./...
|
@go vet ./...
|
||||||
|
|||||||
@@ -5,14 +5,12 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
)
|
)
|
||||||
|
|
||||||
var compositor *config.Compositor = config.NewCompositor()
|
|
||||||
|
|
||||||
var rootCmd = &cobra.Command{
|
var rootCmd = &cobra.Command{
|
||||||
Use: "node",
|
Use: "node",
|
||||||
Short: "Go Sally node",
|
Short: "Go Sally node",
|
||||||
@@ -28,7 +26,7 @@ func Execute() {
|
|||||||
log.SetOutput(os.Stdout)
|
log.SetOutput(os.Stdout)
|
||||||
log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
|
log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
|
||||||
log.SetFlags(log.Ldate | log.Ltime)
|
log.SetFlags(log.Ldate | log.Ltime)
|
||||||
compositor.LoadCMDLine(rootCmd)
|
hooks.Compositor.LoadCMDLine(rootCmd)
|
||||||
_ = rootCmd.Execute()
|
_ = rootCmd.Execute()
|
||||||
// if err := rootCmd.Execute(); err != nil {
|
// if err := rootCmd.Execute(); err != nil {
|
||||||
// log.Fatalf("Unexpected error: %s", err.Error())
|
// log.Fatalf("Unexpected error: %s", err.Error())
|
||||||
|
|||||||
336
cmd/run.go
336
cmd/run.go
@@ -1,349 +1,17 @@
|
|||||||
package cmd
|
package cmd
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"github.com/akyaiy/GoSally-mvp/hooks"
|
||||||
"errors"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"io/fs"
|
|
||||||
"log"
|
|
||||||
"log/slog"
|
|
||||||
"net"
|
|
||||||
"net/http"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"regexp"
|
|
||||||
"syscall"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
|
||||||
"github.com/go-chi/chi/v5"
|
|
||||||
"github.com/go-chi/cors"
|
|
||||||
"github.com/spf13/cobra"
|
"github.com/spf13/cobra"
|
||||||
"golang.org/x/net/netutil"
|
|
||||||
"gopkg.in/ini.v1"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func contains(slice []string, item string) bool {
|
|
||||||
for _, v := range slice {
|
|
||||||
if v == item {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var runCmd = &cobra.Command{
|
var runCmd = &cobra.Command{
|
||||||
Use: "run",
|
Use: "run",
|
||||||
Aliases: []string{"r"},
|
Aliases: []string{"r"},
|
||||||
Short: "Run node normally",
|
Short: "Run node normally",
|
||||||
Long: `
|
Long: `
|
||||||
"run" starts the node with settings depending on the configuration file`,
|
"run" starts the node with settings depending on the configuration file`,
|
||||||
Run: func(cmd *cobra.Command, args []string) {
|
Run: hooks.Run,
|
||||||
nodeApp := app.New()
|
|
||||||
|
|
||||||
nodeApp.InitialHooks(
|
|
||||||
func(cs *corestate.CoreState, x *app.AppX) {
|
|
||||||
x.Config = compositor
|
|
||||||
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.NodeVersion,
|
|
||||||
MetaDir: "./.meta",
|
|
||||||
Stage: corestate.StagePreInit,
|
|
||||||
StartTimestampUnix: time.Now().Unix(),
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
func(cs *corestate.CoreState, x *app.AppX) {
|
|
||||||
x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
|
||||||
|
|
||||||
if err := x.Config.LoadEnv(); err != nil {
|
|
||||||
x.Log.Fatalf("env load error: %s", err)
|
|
||||||
}
|
|
||||||
cs.NodePath = x.Config.Env.NodePath
|
|
||||||
|
|
||||||
if cfgPath := x.Config.CMDLine.Run.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() {
|
|
||||||
if !contains(x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
|
||||||
x.Log.Printf("%s: %s", logs.PrintWarn(), "Non-standard value specified for temporary directory")
|
|
||||||
}
|
|
||||||
// still pre-init stage
|
|
||||||
runDir, err := run_manager.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 {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
if err := run_manager.Set(cs.NodeBinName); err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
fmgr := run_manager.File(cs.NodeBinName)
|
|
||||||
output, err := fmgr.Open()
|
|
||||||
if err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
if _, err := io.Copy(output, input); err != nil {
|
|
||||||
fmgr.Close()
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil {
|
|
||||||
fmgr.Close()
|
|
||||||
_ = run_manager.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 := utils.SetEviron(os.Environ(), fmt.Sprintf("GS_PARENT_PID=%d", os.Getpid()))
|
|
||||||
|
|
||||||
if err := syscall.Exec(runArgs[0], runArgs, env); err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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 = run_manager.Toggle()
|
|
||||||
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
|
||||||
if err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
if exist {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unable to continue node operation: A node with the same identifier was found in the runtime environment")
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := run_manager.Set("run.lock"); err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
lockPath, err := run_manager.Get("run.lock")
|
|
||||||
if err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
lockFile := ini.Empty()
|
|
||||||
secRun, err := lockFile.NewSection("runtime")
|
|
||||||
if err != nil {
|
|
||||||
_ = run_manager.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 {
|
|
||||||
_ = run_manager.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)
|
|
||||||
newSlog, err := logs.SetupLogger(x.Config.Conf.Log)
|
|
||||||
if err != nil {
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
|
||||||
}
|
|
||||||
*x.SLog = *newSlog
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
nodeApp.Run(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
|
||||||
ctxMain, cancelMain := context.WithCancel(ctx)
|
|
||||||
runLockFile := run_manager.File("run.lock")
|
|
||||||
_, err := runLockFile.Open()
|
|
||||||
if err != nil {
|
|
||||||
x.Log.Fatalf("cannot open run.lock: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
_, err = runLockFile.Watch(ctxMain, func() {
|
|
||||||
x.Log.Printf("run.lock was touched")
|
|
||||||
_ = run_manager.Clean()
|
|
||||||
cancelMain()
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
x.Log.Printf("watch error: %s", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
|
||||||
X: x,
|
|
||||||
CS: cs,
|
|
||||||
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
|
|
||||||
Ver: "v1",
|
|
||||||
})
|
|
||||||
|
|
||||||
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
|
||||||
CS: cs,
|
|
||||||
X: x,
|
|
||||||
}, 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.HandleFunc(config.ComDirRoute, 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,
|
|
||||||
ErrorLog: log.New(&logs.SlogWriter{
|
|
||||||
Logger: x.SLog,
|
|
||||||
Level: logs.GlobalLevel,
|
|
||||||
}, "", 0),
|
|
||||||
}
|
|
||||||
|
|
||||||
nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
|
||||||
if err := srv.Shutdown(ctxMain); err != nil {
|
|
||||||
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
|
|
||||||
} else {
|
|
||||||
x.Log.Printf("Server stopped gracefully")
|
|
||||||
}
|
|
||||||
|
|
||||||
x.Log.Println("Cleaning up...")
|
|
||||||
|
|
||||||
if err := run_manager.Clean(); err != nil {
|
|
||||||
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
|
|
||||||
}
|
|
||||||
x.Log.Println("bye!")
|
|
||||||
})
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
defer utils.CatchPanicWithCancel(cancelMain)
|
|
||||||
if x.Config.Conf.TLS.TlsEnabled {
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
|
||||||
if err != nil {
|
|
||||||
x.Log.Printf("%s: Failed to start TLS listener: %s", logs.PrintError(), err.Error())
|
|
||||||
cancelMain()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
|
||||||
limitedListener := netutil.LimitListener(listener, 100)
|
|
||||||
if err := srv.ServeTLS(limitedListener, x.Config.Conf.TLS.CertFile, x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
x.Log.Printf("%s: Failed to start HTTPS server: %s", logs.PrintError(), err.Error())
|
|
||||||
cancelMain()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
x.Log.Printf("Serving on %s port %s... (http://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
|
||||||
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
|
||||||
if err != nil {
|
|
||||||
x.Log.Printf("%s: Failed to start listener: %s", logs.PrintError(), err.Error())
|
|
||||||
cancelMain()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
limitedListener := netutil.LimitListener(listener, 100)
|
|
||||||
if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
|
||||||
x.Log.Printf("%s: Failed to start HTTP server: %s", logs.PrintError(), err.Error())
|
|
||||||
cancelMain()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
if x.Config.Conf.Updates.UpdatesEnabled {
|
|
||||||
go func() {
|
|
||||||
defer utils.CatchPanicWithCancel(cancelMain)
|
|
||||||
updated := update.NewUpdater(&update.UpdaterInit{
|
|
||||||
X: x,
|
|
||||||
Ctx: ctxMain,
|
|
||||||
Cancel: cancelMain,
|
|
||||||
})
|
|
||||||
updated.Shutdownfunc(cancelMain)
|
|
||||||
for {
|
|
||||||
isNewUpdate, err := updated.CkeckUpdates()
|
|
||||||
if err != nil {
|
|
||||||
x.Log.Printf("Failed to check for updates: %s", err.Error())
|
|
||||||
}
|
|
||||||
if isNewUpdate {
|
|
||||||
if err := updated.Update(); err != nil {
|
|
||||||
x.Log.Printf("Failed to update: %s", err.Error())
|
|
||||||
} else {
|
|
||||||
x.Log.Printf("Update completed successfully")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
time.Sleep(x.Config.Conf.Updates.CheckInterval)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
}
|
|
||||||
|
|
||||||
<-ctxMain.Done()
|
|
||||||
nodeApp.CallFallback(ctx)
|
|
||||||
return nil
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
|||||||
187
hooks/initial.go
Normal file
187
hooks/initial.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package hooks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/fs"
|
||||||
|
"log"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"syscall"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||||
|
"gopkg.in/ini.v1"
|
||||||
|
)
|
||||||
|
|
||||||
|
var Compositor *config.Compositor = config.NewCompositor()
|
||||||
|
|
||||||
|
func Init0Hook(cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
x.Config = Compositor
|
||||||
|
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 Init1Hook(cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
*cs = *corestate.NewCorestate(&corestate.CoreState{
|
||||||
|
UUID32DirName: "uuid",
|
||||||
|
NodeBinName: filepath.Base(os.Args[0]),
|
||||||
|
NodeVersion: config.NodeVersion,
|
||||||
|
MetaDir: "./.meta",
|
||||||
|
Stage: corestate.StagePreInit,
|
||||||
|
StartTimestampUnix: time.Now().Unix(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init2Hook(cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||||
|
|
||||||
|
if err := x.Config.LoadEnv(); err != nil {
|
||||||
|
x.Log.Fatalf("env load error: %s", err)
|
||||||
|
}
|
||||||
|
cs.NodePath = x.Config.Env.NodePath
|
||||||
|
|
||||||
|
if cfgPath := x.Config.CMDLine.Run.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 Init3Hook(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 Init4Hook(cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
if x.Config.Env.ParentStagePID != os.Getpid() {
|
||||||
|
if !slices.Contains(x.Config.Conf.DisableWarnings, "--WNonStdTmpDir") && os.TempDir() != "/tmp" {
|
||||||
|
x.Log.Printf("%s: %s", logs.PrintWarn(), "Non-standard value specified for temporary directory")
|
||||||
|
}
|
||||||
|
// still pre-init stage
|
||||||
|
runDir, err := run_manager.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 {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
if err := run_manager.Set(cs.NodeBinName); err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
fmgr := run_manager.File(cs.NodeBinName)
|
||||||
|
output, err := fmgr.Open()
|
||||||
|
if err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, err := io.Copy(output, input); err != nil {
|
||||||
|
fmgr.Close()
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil {
|
||||||
|
fmgr.Close()
|
||||||
|
_ = run_manager.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 := utils.SetEviron(os.Environ(), fmt.Sprintf("GS_PARENT_PID=%d", os.Getpid()))
|
||||||
|
|
||||||
|
if err := syscall.Exec(runArgs[0], runArgs, env); err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
x.Log.Printf("Node uuid is %s", cs.UUID32)
|
||||||
|
}
|
||||||
|
|
||||||
|
// post-init stage
|
||||||
|
func Init5Hook(cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
cs.Stage = corestate.StagePostInit
|
||||||
|
x.Log.SetPrefix(logs.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
|
||||||
|
|
||||||
|
cs.RunDir = run_manager.Toggle()
|
||||||
|
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
|
||||||
|
if err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
if exist {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unable to continue node operation: A node with the same identifier was found in the runtime environment")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := run_manager.Set("run.lock"); err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
lockPath, err := run_manager.Get("run.lock")
|
||||||
|
if err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
lockFile := ini.Empty()
|
||||||
|
secRun, err := lockFile.NewSection("runtime")
|
||||||
|
if err != nil {
|
||||||
|
_ = run_manager.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 {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Init6Hook(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)
|
||||||
|
newSlog, err := logs.SetupLogger(x.Config.Conf.Log)
|
||||||
|
if err != nil {
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
x.Log.Fatalf("Unexpected failure: %s", err.Error())
|
||||||
|
}
|
||||||
|
*x.SLog = *newSlog
|
||||||
|
}
|
||||||
164
hooks/run.go
Normal file
164
hooks/run.go
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
package hooks
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/corestate"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/run_manager"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/update"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/app"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/config"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/server/gateway"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/server/sv1"
|
||||||
|
"github.com/go-chi/chi/v5"
|
||||||
|
"github.com/go-chi/cors"
|
||||||
|
"github.com/spf13/cobra"
|
||||||
|
"golang.org/x/net/netutil"
|
||||||
|
)
|
||||||
|
|
||||||
|
var nodeApp = app.New()
|
||||||
|
|
||||||
|
func Run(cmd *cobra.Command, args []string) {
|
||||||
|
nodeApp.InitialHooks()
|
||||||
|
|
||||||
|
nodeApp.Run(RunHook)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RunHook(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
|
||||||
|
ctxMain, cancelMain := context.WithCancel(ctx)
|
||||||
|
runLockFile := run_manager.File("run.lock")
|
||||||
|
_, err := runLockFile.Open()
|
||||||
|
if err != nil {
|
||||||
|
x.Log.Fatalf("cannot open run.lock: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = runLockFile.Watch(ctxMain, func() {
|
||||||
|
x.Log.Printf("run.lock was touched")
|
||||||
|
_ = run_manager.Clean()
|
||||||
|
cancelMain()
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
x.Log.Printf("watch error: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||||
|
X: x,
|
||||||
|
CS: cs,
|
||||||
|
AllowedCmd: regexp.MustCompile(`^[a-zA-Z0-9]+(>[a-zA-Z0-9]+)*$`),
|
||||||
|
Ver: "v1",
|
||||||
|
})
|
||||||
|
|
||||||
|
s := gateway.InitGateway(&gateway.GatewayServerInit{
|
||||||
|
CS: cs,
|
||||||
|
X: x,
|
||||||
|
}, 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.HandleFunc(config.ComDirRoute, 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,
|
||||||
|
ErrorLog: log.New(&logs.SlogWriter{
|
||||||
|
Logger: x.SLog,
|
||||||
|
Level: logs.GlobalLevel,
|
||||||
|
}, "", 0),
|
||||||
|
}
|
||||||
|
|
||||||
|
nodeApp.Fallback(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) {
|
||||||
|
if err := srv.Shutdown(ctxMain); err != nil {
|
||||||
|
x.Log.Printf("%s: Failed to stop the server gracefully: %s", logs.PrintError(), err.Error())
|
||||||
|
} else {
|
||||||
|
x.Log.Printf("Server stopped gracefully")
|
||||||
|
}
|
||||||
|
|
||||||
|
x.Log.Println("Cleaning up...")
|
||||||
|
|
||||||
|
if err := run_manager.Clean(); err != nil {
|
||||||
|
x.Log.Printf("%s: Cleanup error: %s", logs.PrintError(), err.Error())
|
||||||
|
}
|
||||||
|
x.Log.Println("bye!")
|
||||||
|
})
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer utils.CatchPanicWithCancel(cancelMain)
|
||||||
|
if x.Config.Conf.TLS.TlsEnabled {
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
||||||
|
if err != nil {
|
||||||
|
x.Log.Printf("%s: Failed to start TLS listener: %s", logs.PrintError(), err.Error())
|
||||||
|
cancelMain()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
x.Log.Printf("Serving on %s port %s with TLS... (https://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||||
|
limitedListener := netutil.LimitListener(listener, 100)
|
||||||
|
if err := srv.ServeTLS(limitedListener, x.Config.Conf.TLS.CertFile, x.Config.Conf.TLS.KeyFile); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
x.Log.Printf("%s: Failed to start HTTPS server: %s", logs.PrintError(), err.Error())
|
||||||
|
cancelMain()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
x.Log.Printf("Serving on %s port %s... (http://%s%s)", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port, fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port), config.ComDirRoute)
|
||||||
|
listener, err := net.Listen("tcp", fmt.Sprintf("%s:%s", x.Config.Conf.HTTPServer.Address, x.Config.Conf.HTTPServer.Port))
|
||||||
|
if err != nil {
|
||||||
|
x.Log.Printf("%s: Failed to start listener: %s", logs.PrintError(), err.Error())
|
||||||
|
cancelMain()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
limitedListener := netutil.LimitListener(listener, 100)
|
||||||
|
if err := srv.Serve(limitedListener); err != nil && !errors.Is(err, http.ErrServerClosed) {
|
||||||
|
x.Log.Printf("%s: Failed to start HTTP server: %s", logs.PrintError(), err.Error())
|
||||||
|
cancelMain()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
if x.Config.Conf.Updates.UpdatesEnabled {
|
||||||
|
go func() {
|
||||||
|
defer utils.CatchPanicWithCancel(cancelMain)
|
||||||
|
updated := update.NewUpdater(&update.UpdaterInit{
|
||||||
|
X: x,
|
||||||
|
Ctx: ctxMain,
|
||||||
|
Cancel: cancelMain,
|
||||||
|
})
|
||||||
|
updated.Shutdownfunc(cancelMain)
|
||||||
|
for {
|
||||||
|
isNewUpdate, err := updated.CkeckUpdates()
|
||||||
|
if err != nil {
|
||||||
|
x.Log.Printf("Failed to check for updates: %s", err.Error())
|
||||||
|
}
|
||||||
|
if isNewUpdate {
|
||||||
|
if err := updated.Update(); err != nil {
|
||||||
|
x.Log.Printf("Failed to update: %s", err.Error())
|
||||||
|
} else {
|
||||||
|
x.Log.Printf("Update completed successfully")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
time.Sleep(x.Config.Conf.Updates.CheckInterval)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
<-ctxMain.Done()
|
||||||
|
nodeApp.CallFallback(ctx)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,17 +1,10 @@
|
|||||||
package sv1
|
package sv1
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
|
||||||
"log/slog"
|
"log/slog"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"strconv"
|
|
||||||
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
|
||||||
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
lua "github.com/yuin/gopher-lua"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
@@ -33,117 +26,3 @@ func (h *HandlerV1) Handle(r *http.Request, req *rpc.RPCRequest) *rpc.RPCRespons
|
|||||||
|
|
||||||
return h.HandleLUA(method, req)
|
return h.HandleLUA(method, req)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *HandlerV1) HandleLUA(path string, req *rpc.RPCRequest) *rpc.RPCResponse {
|
|
||||||
L := lua.NewState()
|
|
||||||
defer L.Close()
|
|
||||||
|
|
||||||
inTable := L.NewTable()
|
|
||||||
paramsTable := L.NewTable()
|
|
||||||
if fetchedParams, ok := req.Params.(map[string]any); ok {
|
|
||||||
for k, v := range fetchedParams {
|
|
||||||
L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
L.SetField(inTable, "Params", paramsTable)
|
|
||||||
L.SetGlobal("In", inTable)
|
|
||||||
|
|
||||||
outTable := L.NewTable()
|
|
||||||
resultTable := L.NewTable()
|
|
||||||
L.SetField(outTable, "Result", resultTable)
|
|
||||||
L.SetGlobal("Out", outTable)
|
|
||||||
|
|
||||||
logTable := L.NewTable()
|
|
||||||
|
|
||||||
L.SetField(logTable, "Info", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.SLog.Info(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "Debug", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.SLog.Debug(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "Error", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.SLog.Error(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "Warn", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.SLog.Warn(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "Event", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.Log.Printf("%s: %s", path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "EventError", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.Log.Printf("%s: %s: %s", logs.PrintError(), path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetField(logTable, "EventWarn", L.NewFunction(func(L *lua.LState) int {
|
|
||||||
msg := L.ToString(1)
|
|
||||||
h.x.Log.Printf("%s: %s: %s", logs.PrintWarn(), path, msg)
|
|
||||||
return 0
|
|
||||||
}))
|
|
||||||
|
|
||||||
L.SetGlobal("Log", logTable)
|
|
||||||
|
|
||||||
prep := filepath.Join(h.x.Config.Conf.ComDir, "_prepare.lua")
|
|
||||||
if _, err := os.Stat(prep); err == nil {
|
|
||||||
if err := L.DoFile(prep); err != nil {
|
|
||||||
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err := L.DoFile(path); err != nil {
|
|
||||||
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
lv := L.GetGlobal("Out")
|
|
||||||
outTbl, ok := lv.(*lua.LTable)
|
|
||||||
if !ok {
|
|
||||||
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if Out.Error exists
|
|
||||||
if errVal := outTbl.RawGetString("Error"); errVal != lua.LNil {
|
|
||||||
if errTbl, ok := errVal.(*lua.LTable); ok {
|
|
||||||
code := rpc.ErrInternalError
|
|
||||||
message := "Internal script error"
|
|
||||||
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
|
||||||
code = int(c.(lua.LNumber))
|
|
||||||
}
|
|
||||||
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
|
||||||
message = msg.String()
|
|
||||||
}
|
|
||||||
h.x.SLog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
|
||||||
return rpc.NewError(code, message, req.ID)
|
|
||||||
}
|
|
||||||
return rpc.NewError(rpc.ErrInternalError, "Out.Error is not a table", req.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Otherwise, parse Out.Result
|
|
||||||
resultVal := outTbl.RawGetString("Result")
|
|
||||||
resultTbl, ok := resultVal.(*lua.LTable)
|
|
||||||
if !ok {
|
|
||||||
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
out := make(map[string]any)
|
|
||||||
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
|
||||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
|
||||||
})
|
|
||||||
|
|
||||||
out["responsible-node"] = h.cs.UUID32
|
|
||||||
return rpc.NewResponse(out, req.ID)
|
|
||||||
}
|
|
||||||
|
|||||||
128
internal/server/sv1/lua_handler.go
Normal file
128
internal/server/sv1/lua_handler.go
Normal file
@@ -0,0 +1,128 @@
|
|||||||
|
package sv1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log/slog"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/core/utils"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/engine/logs"
|
||||||
|
"github.com/akyaiy/GoSally-mvp/internal/server/rpc"
|
||||||
|
lua "github.com/yuin/gopher-lua"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (h *HandlerV1) HandleLUA(path string, req *rpc.RPCRequest) *rpc.RPCResponse {
|
||||||
|
L := lua.NewState()
|
||||||
|
defer L.Close()
|
||||||
|
|
||||||
|
inTable := L.NewTable()
|
||||||
|
paramsTable := L.NewTable()
|
||||||
|
if fetchedParams, ok := req.Params.(map[string]any); ok {
|
||||||
|
for k, v := range fetchedParams {
|
||||||
|
L.SetField(paramsTable, k, utils.ConvertGolangTypesToLua(L, v))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
L.SetField(inTable, "Params", paramsTable)
|
||||||
|
L.SetGlobal("In", inTable)
|
||||||
|
|
||||||
|
outTable := L.NewTable()
|
||||||
|
resultTable := L.NewTable()
|
||||||
|
L.SetField(outTable, "Result", resultTable)
|
||||||
|
L.SetGlobal("Out", outTable)
|
||||||
|
|
||||||
|
logTable := L.NewTable()
|
||||||
|
|
||||||
|
L.SetField(logTable, "Info", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.SLog.Info(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(logTable, "Debug", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.SLog.Debug(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(logTable, "Error", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.SLog.Error(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(logTable, "Warn", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.SLog.Warn(fmt.Sprintf("the script says: %s", msg), slog.String("script", path))
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(logTable, "Event", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.Log.Printf("%s: %s", path, msg)
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(logTable, "EventError", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.Log.Printf("%s: %s: %s", logs.PrintError(), path, msg)
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetField(logTable, "EventWarn", L.NewFunction(func(L *lua.LState) int {
|
||||||
|
msg := L.ToString(1)
|
||||||
|
h.x.Log.Printf("%s: %s: %s", logs.PrintWarn(), path, msg)
|
||||||
|
return 0
|
||||||
|
}))
|
||||||
|
|
||||||
|
L.SetGlobal("Log", logTable)
|
||||||
|
|
||||||
|
prep := filepath.Join(h.x.Config.Conf.ComDir, "_prepare.lua")
|
||||||
|
if _, err := os.Stat(prep); err == nil {
|
||||||
|
if err := L.DoFile(prep); err != nil {
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := L.DoFile(path); err != nil {
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, err.Error(), req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
lv := L.GetGlobal("Out")
|
||||||
|
outTbl, ok := lv.(*lua.LTable)
|
||||||
|
if !ok {
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, "Out is not a table", req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if Out.Error exists
|
||||||
|
if errVal := outTbl.RawGetString("Error"); errVal != lua.LNil {
|
||||||
|
if errTbl, ok := errVal.(*lua.LTable); ok {
|
||||||
|
code := rpc.ErrInternalError
|
||||||
|
message := "Internal script error"
|
||||||
|
if c := errTbl.RawGetString("code"); c.Type() == lua.LTNumber {
|
||||||
|
code = int(c.(lua.LNumber))
|
||||||
|
}
|
||||||
|
if msg := errTbl.RawGetString("message"); msg.Type() == lua.LTString {
|
||||||
|
message = msg.String()
|
||||||
|
}
|
||||||
|
h.x.SLog.Error("the script terminated with an error", slog.String("code", strconv.Itoa(code)), slog.String("message", message))
|
||||||
|
return rpc.NewError(code, message, req.ID)
|
||||||
|
}
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, "Out.Error is not a table", req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Otherwise, parse Out.Result
|
||||||
|
resultVal := outTbl.RawGetString("Result")
|
||||||
|
resultTbl, ok := resultVal.(*lua.LTable)
|
||||||
|
if !ok {
|
||||||
|
return rpc.NewError(rpc.ErrInternalError, "Out.Result is not a table", req.ID)
|
||||||
|
}
|
||||||
|
|
||||||
|
out := make(map[string]any)
|
||||||
|
resultTbl.ForEach(func(key lua.LValue, value lua.LValue) {
|
||||||
|
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||||
|
})
|
||||||
|
|
||||||
|
out["responsible-node"] = h.cs.UUID32
|
||||||
|
return rpc.NewResponse(out, req.ID)
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user