From aebc3d2e9b2b1a5a7d186ffd00930b1ca397587b Mon Sep 17 00:00:00 2001 From: Alexey Date: Wed, 30 Jul 2025 12:33:51 +0300 Subject: [PATCH] - move run hook to external package - replace own contains function to slices.Contains --- cmd/run.go | 336 +-------------------------------------------------- hooks/run.go | 164 +++++++++++++++++++++++++ 2 files changed, 166 insertions(+), 334 deletions(-) create mode 100644 hooks/run.go diff --git a/cmd/run.go b/cmd/run.go index af2538a..36e2b86 100644 --- a/cmd/run.go +++ b/cmd/run.go @@ -1,349 +1,17 @@ package cmd import ( - "context" - "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/akyaiy/GoSally-mvp/hooks" "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{ Use: "run", Aliases: []string{"r"}, Short: "Run node normally", Long: ` "run" starts the node with settings depending on the configuration file`, - Run: func(cmd *cobra.Command, args []string) { - 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 - }) - }, + Run: hooks.Run, } func init() { diff --git a/hooks/run.go b/hooks/run.go new file mode 100644 index 0000000..db3583b --- /dev/null +++ b/hooks/run.go @@ -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 +}