Fixing updates

This commit is contained in:
alex
2025-07-10 18:03:30 +03:00
parent e71d69f3f1
commit 5bc334fd2c
21 changed files with 617 additions and 418 deletions

17
.vscode/launch.json vendored Normal file
View File

@@ -0,0 +1,17 @@
{
"version": "0.2.0",
"configurations": [
{
"name": "Debug Go program",
"type": "go",
"request": "launch",
"mode": "debug",
"program": "${workspaceFolder}",
"args": [
"run",
"-t", "1,2,3"
],
"env": {},
}
]
}

View File

@@ -16,19 +16,18 @@ The basic directory tree looks something like this
│   ├── echo.lua │   ├── echo.lua
│   ├── _globals.lua Declaring global variables and functions for all internal scripts (also required for luarc to work correctly) │   ├── _globals.lua Declaring global variables and functions for all internal scripts (also required for luarc to work correctly)
│   └── _prepare.lua Script that is executed before each script launch │   └── _prepare.lua Script that is executed before each script launch
── config.yaml ── config.yaml
└── Makefile
3 directories, 6 files 3 directories, 6 files
``` ```
Launch by command Launch by command
```bash ```bash
./node run ./bin/node run
``` ```
or for structured logs or for structured logs
```bash ```bash
./node run | jq ./bin/node run | jq
``` ```
Example of GET request to server Example of GET request to server

View File

@@ -1,12 +1,18 @@
package cmd package cmd
import ( import (
"fmt"
"log" "log"
"os" "os"
"github.com/akyaiy/GoSally-mvp/core/config"
"github.com/akyaiy/GoSally-mvp/core/corestate"
"github.com/akyaiy/GoSally-mvp/core/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",
@@ -18,9 +24,11 @@ var rootCmd = &cobra.Command{
func Execute() { func Execute() {
log.SetOutput(os.Stdout) log.SetOutput(os.Stdout)
log.SetPrefix("\033[34m[INIT]\033[0m ") log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", corestate.StageNotReady)))
log.SetFlags(log.Ldate | log.Ltime) log.SetFlags(log.Ldate | log.Ltime)
if err := rootCmd.Execute(); err != nil { compositor.LoadCMDLine(rootCmd)
log.Fatalf("Unexpected error: %s", err.Error()) rootCmd.Execute()
} // if err := rootCmd.Execute(); err != nil {
// log.Fatalf("Unexpected error: %s", err.Error())
// }
} }

View File

@@ -13,7 +13,6 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"regexp" "regexp"
"strings"
"syscall" "syscall"
"time" "time"
@@ -22,7 +21,9 @@ import (
"github.com/akyaiy/GoSally-mvp/core/corestate" "github.com/akyaiy/GoSally-mvp/core/corestate"
gs "github.com/akyaiy/GoSally-mvp/core/general_server" gs "github.com/akyaiy/GoSally-mvp/core/general_server"
"github.com/akyaiy/GoSally-mvp/core/logs" "github.com/akyaiy/GoSally-mvp/core/logs"
"github.com/akyaiy/GoSally-mvp/core/run_manager"
"github.com/akyaiy/GoSally-mvp/core/sv1" "github.com/akyaiy/GoSally-mvp/core/sv1"
"github.com/akyaiy/GoSally-mvp/core/update"
"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"
"github.com/go-chi/cors" "github.com/go-chi/cors"
@@ -39,6 +40,7 @@ var runCmd = &cobra.Command{
nodeApp.InitialHooks( nodeApp.InitialHooks(
func(cs *corestate.CoreState, x *app.AppX) { func(cs *corestate.CoreState, x *app.AppX) {
x.Config = compositor
x.Log.SetOutput(os.Stdout) x.Log.SetOutput(os.Stdout)
x.Log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage))) x.Log.SetPrefix(logs.SetBrightBlack(fmt.Sprintf("(%s) ", cs.Stage)))
x.Log.SetFlags(log.Ldate | log.Ltime) x.Log.SetFlags(log.Ldate | log.Ltime)
@@ -49,29 +51,27 @@ var runCmd = &cobra.Command{
*cs = *corestate.NewCorestate(&corestate.CoreState{ *cs = *corestate.NewCorestate(&corestate.CoreState{
UUID32DirName: "uuid", UUID32DirName: "uuid",
NodeBinName: filepath.Base(os.Args[0]), NodeBinName: filepath.Base(os.Args[0]),
NodeVersion: config.GetUpdateConsts().GetNodeVersion(), NodeVersion: config.NodeVersion,
MetaDir: "./.meta", MetaDir: "./.meta",
Stage: corestate.StagePreInit, Stage: corestate.StagePreInit,
RM: corestate.NewRM(),
StartTimestampUnix: time.Now().Unix(), StartTimestampUnix: time.Now().Unix(),
}) })
}, },
func(cs *corestate.CoreState, x *app.AppX) { func(cs *corestate.CoreState, x *app.AppX) {
x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage))) x.Log.SetPrefix(logs.SetBlue(fmt.Sprintf("(%s) ", cs.Stage)))
x.Config = config.NewCompositor()
if err := x.Config.LoadEnv(); err != nil { if err := x.Config.LoadEnv(); err != nil {
x.Log.Fatalf("env load error: %s", err) x.Log.Fatalf("env load error: %s", err)
} }
cs.NodePath = x.Config.Env.NodePath cs.NodePath = x.Config.Env.NodePath
if cfgPath := config.ConfigPath; cfgPath != "" { if cfgPath := x.Config.CMDLine.Run.ConfigPath; cfgPath != "" {
x.Config.Env.ConfigPath = cfgPath x.Config.Env.ConfigPath = cfgPath
} }
if err := x.Config.LoadConf(x.Config.Env.ConfigPath); err != nil { if err := x.Config.LoadConf(x.Config.Env.ConfigPath); err != nil {
x.Log.Fatalf("conf load error: %s", err) x.Log.Fatalf("conf load error: %s", err)
} }
}, },
func(cs *corestate.CoreState, x *app.AppX) { func(cs *corestate.CoreState, x *app.AppX) {
@@ -92,38 +92,40 @@ var runCmd = &cobra.Command{
}, },
func(cs *corestate.CoreState, x *app.AppX) { func(cs *corestate.CoreState, x *app.AppX) {
if x.Config.Env.ParentStagePID != os.Getpid() || x.Config.Env.ParentStagePID == -1 { if x.Config.Env.ParentStagePID != os.Getpid() {
if os.TempDir() != "/tmp" {
x.Log.Printf("%s: %s", logs.SetYellow("warning"), "non-standard value specified for temporary directory")
}
// still pre-init stage // still pre-init stage
func(cs *corestate.CoreState, x *app.AppX) { runDir, err := run_manager.Create(cs.UUID32)
runDir, err := cs.RM.Create(cs.UUID32)
if err != nil { if err != nil {
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
cs.RunDir = runDir cs.RunDir = runDir
input, err := os.Open(os.Args[0]) input, err := os.Open(os.Args[0])
if err != nil { if err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
if err := cs.RM.Set(cs.NodeBinName); err != nil { if err := run_manager.Set(cs.NodeBinName); err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
fmgr := cs.RM.File(cs.NodeBinName) fmgr := run_manager.File(cs.NodeBinName)
output, err := fmgr.Open() output, err := fmgr.Open()
if err != nil { if err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
if _, err := io.Copy(output, input); err != nil { if _, err := io.Copy(output, input); err != nil {
fmgr.Close() fmgr.Close()
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil { if err := os.Chmod(filepath.Join(cs.RunDir, cs.NodeBinName), 0755); err != nil {
fmgr.Close() fmgr.Close()
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
input.Close() input.Close()
@@ -132,23 +134,12 @@ var runCmd = &cobra.Command{
runArgs[0] = filepath.Join(cs.RunDir, cs.NodeBinName) runArgs[0] = filepath.Join(cs.RunDir, cs.NodeBinName)
// prepare environ // prepare environ
env := os.Environ() env := utils.SetEviron(os.Environ(), fmt.Sprintf("GS_PARENT_PID=%d", os.Getpid()))
var filtered []string if err := syscall.Exec(runArgs[0], runArgs, env); err != nil {
for _, e := range env { run_manager.Clean()
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()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
}(cs, x)
} }
x.Log.Printf("Node uuid is %s", cs.UUID32) x.Log.Printf("Node uuid is %s", cs.UUID32)
}, },
@@ -158,30 +149,30 @@ var runCmd = &cobra.Command{
cs.Stage = corestate.StagePostInit cs.Stage = corestate.StagePostInit
x.Log.SetPrefix(logs.SetYellow(fmt.Sprintf("(%s) ", cs.Stage))) x.Log.SetPrefix(logs.SetYellow(fmt.Sprintf("(%s) ", cs.Stage)))
cs.RunDir = cs.RM.Toggle() cs.RunDir = run_manager.Toggle()
exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir) exist, err := utils.ExistsMatchingDirs(filepath.Join(os.TempDir(), fmt.Sprintf("/*-%s-%s", cs.UUID32, "gosally-runtime")), cs.RunDir)
if err != nil { if err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
if exist { if exist {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unable to continue node operation: A node with the same identifier was found in the runtime environment") 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 { if err := run_manager.Set("run.lock"); err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
lockPath, err := cs.RM.Get("run.lock") lockPath, err := run_manager.Get("run.lock")
if err != nil { if err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
lockFile := ini.Empty() lockFile := ini.Empty()
secRun, err := lockFile.NewSection("runtime") secRun, err := lockFile.NewSection("runtime")
if err != nil { if err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
secRun.Key("pid").SetValue(fmt.Sprintf("%d/%d", os.Getpid(), x.Config.Env.ParentStagePID)) secRun.Key("pid").SetValue(fmt.Sprintf("%d/%d", os.Getpid(), x.Config.Env.ParentStagePID))
@@ -192,7 +183,7 @@ var runCmd = &cobra.Command{
err = lockFile.SaveTo(lockPath) err = lockFile.SaveTo(lockPath)
if err != nil { if err != nil {
cs.RM.Clean() run_manager.Clean()
x.Log.Fatalf("Unexpected failure: %s", err.Error()) x.Log.Fatalf("Unexpected failure: %s", err.Error())
} }
}, },
@@ -208,22 +199,20 @@ var runCmd = &cobra.Command{
nodeApp.Run(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error { nodeApp.Run(func(ctx context.Context, cs *corestate.CoreState, x *app.AppX) error {
ctxMain, cancelMain := context.WithCancel(ctx) ctxMain, cancelMain := context.WithCancel(ctx)
runLockFile := cs.RM.File("run.lock") runLockFile := run_manager.File("run.lock")
_, err := runLockFile.Open() _, err := runLockFile.Open()
if err != nil { if err != nil {
x.Log.Fatalf("cannot open run.lock: %s", err) x.Log.Fatalf("cannot open run.lock: %s", err)
} }
go func() { _, err = runLockFile.Watch(ctxMain, func() {
err := runLockFile.Watch(ctxMain, func() {
x.Log.Printf("run.lock was touched") x.Log.Printf("run.lock was touched")
cs.RM.Clean() run_manager.Clean()
cancelMain() cancelMain()
}) })
if err != nil { if err != nil {
x.Log.Printf("watch error: %s", err) x.Log.Printf("watch error: %s", err)
} }
}()
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{ serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
Log: *x.SLog, Log: *x.SLog,
@@ -246,7 +235,7 @@ var runCmd = &cobra.Command{
AllowCredentials: true, AllowCredentials: true,
MaxAge: 300, MaxAge: 300,
})) }))
r.Route(config.GetServerConsts().GetApiRoute()+config.GetServerConsts().GetComDirRoute(), func(r chi.Router) { r.Route(config.ApiRoute+config.ComDirRoute, func(r chi.Router) {
r.Get("/", s.HandleList) r.Get("/", s.HandleList)
r.Get("/{cmd}", s.Handle) r.Get("/{cmd}", s.Handle)
}) })
@@ -285,27 +274,44 @@ var runCmd = &cobra.Command{
x.SLog.Error("Failed to start HTTP server", slog.String("error", err.Error())) x.SLog.Error("Failed to start HTTP server", slog.String("error", err.Error()))
} }
} }
srv.Shutdown(ctxMain)
}() }()
if err := srv.Shutdown(ctxMain); err != nil { if x.Config.Conf.Updates.UpdatesEnabled {
x.Log.Printf("%s", fmt.Sprintf("Failed to shutdown server gracefully: %s", err.Error())) go func() {
x.Updated = update.NewUpdater(ctxMain, x.Log, x.Config.Conf, x.Config.Env)
x.Updated.Shutdownfunc(cancelMain)
for {
isNewUpdate, err := x.Updated.CkeckUpdates()
if err != nil {
x.Log.Printf("Failed to check for updates: %s", err.Error())
}
if isNewUpdate {
if err := x.Updated.Update(); err != nil {
x.Log.Printf("Failed to update: %s", err.Error())
} else { } else {
x.Log.Printf("The server shut down successfully") x.Log.Printf("Update completed successfully")
}
}
time.Sleep(x.Config.Conf.Updates.CheckInterval)
}
}()
} }
<-ctxMain.Done() <-ctxMain.Done()
x.Log.Println("cleaning up...") x.Log.Println("cleaning up...")
if err := cs.RM.Clean(); err != nil { if err := run_manager.Clean(); err != nil {
x.Log.Printf("cleanup error: %s", err) x.Log.Printf("cleanup error: %s", err)
} }
x.Log.Println("bye!") x.Log.Println("bye!")
return nil return nil
}) })
}, },
} }
func init() { func init() {
runCmd.Flags().StringVarP(&config.ConfigPath, "config", "c", "./config.yaml", "Path to configuration file")
rootCmd.AddCommand(runCmd) rootCmd.AddCommand(runCmd)
} }

View File

@@ -2,10 +2,6 @@
--- #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"

View File

@@ -16,6 +16,6 @@ tls:
com_dir: "com/" com_dir: "com/"
updates: updates:
enabled: false enabled: true
check-interval: 1h check-interval: 1h
repository_url: "https://repo.serve.lv/raw/go-sally" repository_url: "https://repo.serve.lv/raw/go-sally"

View File

@@ -2,8 +2,11 @@ package config
import ( import (
"fmt" "fmt"
"reflect"
"strconv"
"strings" "strings"
"github.com/spf13/cobra"
"github.com/spf13/viper" "github.com/spf13/viper"
) )
@@ -52,12 +55,6 @@ func (c *Compositor) LoadConf(path string) error {
v.SetDefault("updates.check_interval", "2h") v.SetDefault("updates.check_interval", "2h")
v.SetDefault("updates.wanted_version", "latest-stable") v.SetDefault("updates.wanted_version", "latest-stable")
// поддержка ENV-переопределений
v.SetEnvPrefix("GOSALLY")
v.SetEnvKeyReplacer(strings.NewReplacer(".", "_"))
v.AutomaticEnv()
// читаем YAML
if err := v.ReadInConfig(); err != nil { if err := v.ReadInConfig(); err != nil {
return fmt.Errorf("error reading config: %w", err) return fmt.Errorf("error reading config: %w", err)
} }
@@ -67,6 +64,116 @@ func (c *Compositor) LoadConf(path string) error {
return fmt.Errorf("error unmarshaling config: %w", err) return fmt.Errorf("error unmarshaling config: %w", err)
} }
c.Conf = &Conf{}
c.Conf = &cfg c.Conf = &cfg
return nil return nil
} }
func (c *Compositor) LoadCMDLine(root *cobra.Command) {
cmdLine := &CMDLine{}
c.CMDLine = cmdLine
t := reflect.TypeOf(cmdLine).Elem()
v := reflect.ValueOf(cmdLine).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
fieldVal := v.Field(i)
ptr := fieldVal.Addr().Interface()
use := strings.ToLower(field.Name)
var cmd *cobra.Command
for _, sub := range root.Commands() {
if sub.Use == use {
cmd = sub
break
}
}
if use == root.Use {
cmd = root
}
if cmd == nil {
continue
}
Unmarshal(cmd, ptr)
}
}
func Unmarshal(cmd *cobra.Command, target any) {
t := reflect.TypeOf(target).Elem()
v := reflect.ValueOf(target).Elem()
for i := 0; i < t.NumField(); i++ {
field := t.Field(i)
valPtr := v.Field(i).Addr().Interface()
full := field.Tag.Get("full")
short := field.Tag.Get("short")
def := field.Tag.Get("def")
desc := field.Tag.Get("desc")
isPersistent := field.Tag.Get("persistent") == "true"
flagSet := cmd.Flags()
if isPersistent {
flagSet = cmd.PersistentFlags()
}
switch field.Type.Kind() {
case reflect.String:
flagSet.StringVarP(valPtr.(*string), full, short, def, desc)
case reflect.Bool:
defVal, err := strconv.ParseBool(def)
if err != nil && def != "" {
fmt.Printf("warning: cannot parse default bool: %q\n", def)
}
flagSet.BoolVarP(valPtr.(*bool), full, short, defVal, desc)
case reflect.Int:
defVal, err := strconv.Atoi(def)
if err != nil && def != "" {
fmt.Printf("warning: cannot parse default int: %q\n", def)
}
flagSet.IntVarP(valPtr.(*int), full, short, defVal, desc)
case reflect.Slice:
elemKind := field.Type.Elem().Kind()
switch elemKind {
case reflect.String:
defVals := []string{}
if def != "" {
defVals = strings.Split(def, ",")
}
flagSet.StringSliceVarP(valPtr.(*[]string), full, short, defVals, desc)
case reflect.Int:
var intVals []int
if def != "" {
for _, s := range strings.Split(def, ",") {
s = strings.TrimSpace(s)
if s == "" {
continue
}
n, err := strconv.Atoi(s)
if err != nil {
fmt.Printf("warning: cannot parse int in slice: %q\n", s)
continue
}
intVals = append(intVals, n)
}
}
flagSet.IntSliceVarP(valPtr.(*[]int), full, short, intVals, desc)
default:
fmt.Printf("unsupported slice element type: %s\n", elemKind)
}
default:
fmt.Printf("unsupported field type: %s\n", field.Type.Kind())
}
}
}

View File

@@ -6,14 +6,13 @@ import (
"time" "time"
) )
var ConfigPath string
type CompositorContract interface { type CompositorContract interface {
LoadEnv() error LoadEnv() error
LoadConf(path string) error LoadConf(path string) error
} }
type Compositor struct { type Compositor struct {
CMDLine *CMDLine
Conf *Conf Conf *Conf
Env *Env Env *Env
} }
@@ -57,3 +56,17 @@ type Env struct {
NodePath string `mapstructure:"node_path"` NodePath string `mapstructure:"node_path"`
ParentStagePID int `mapstructure:"parent_pid"` ParentStagePID int `mapstructure:"parent_pid"`
} }
type CMDLine struct {
Run Run
Node Root
}
type Root struct {
Debug bool `persistent:"true" full:"debug" short:"d" def:"false" desc:"Set debug mode"`
}
type Run struct {
ConfigPath string `persistent:"true" full:"config" short:"c" def:"./config.yaml" desc:"Path to configuration file"`
Test []int `persistent:"true" full:"test" short:"t" def:"" desc:"js test"`
}

View File

@@ -27,25 +27,8 @@ var UpdateDownloadPath string = os.TempDir()
var MetaDir string = "./.meta" var MetaDir string = "./.meta"
type _internalConsts struct{} func init() {
type _serverConsts struct{}
type _updateConsts struct{}
func GetUpdateConsts() _updateConsts { return _updateConsts{} }
func (_ _updateConsts) GetNodeVersion() string {
if NodeVersion == "" { if NodeVersion == "" {
return "v0.0.0-none" NodeVersion = "v0.0.0-none"
} }
return NodeVersion
} }
func (_ _updateConsts) GetActualFileName() string { return ActualFileName }
func (_ _updateConsts) GetUpdateArchiveName() string { return UpdateArchiveName }
func (_ _updateConsts) GetUpdateDownloadPath() string { return UpdateDownloadPath }
func GetInternalConsts() _internalConsts { return _internalConsts{} }
func (_ _internalConsts) GetUUIDLength() int { return UUIDLength }
func (_ _internalConsts) GetMetaDir() string { return MetaDir }
func GetServerConsts() _serverConsts { return _serverConsts{} }
func (_ _serverConsts) GetApiRoute() string { return ApiRoute }
func (_ _serverConsts) GetComDirRoute() string { return ComDirRoute }

View File

@@ -28,7 +28,7 @@ func readNodeUUIDRaw(p string) ([]byte, error) {
if err != nil { if err != nil {
return data, err return data, err
} }
if len(data) != config.GetInternalConsts().GetUUIDLength() { if len(data) != config.UUIDLength {
return data, errors.New("decoded UUID length mismatch") return data, errors.New("decoded UUID length mismatch")
} }
return data, nil return data, nil

View File

@@ -1,115 +0,0 @@
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
}

View File

@@ -1,16 +1,10 @@
package corestate package corestate
import (
"context"
"os"
)
// CoreStateContract is interface for CoreState. // CoreStateContract is interface for CoreState.
// CoreState is a structure that contains the basic meta-information vital to the node. // 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, // The interface contains functionality for working with the Runtime directory and its files,
// and access to low-level logging in stdout // and access to low-level logging in stdout
type CoreStateContract interface { type CoreStateContract interface {
RuntimeDir() RunManagerContract
} }
type CoreState struct { type CoreState struct {
@@ -27,36 +21,4 @@ type CoreState struct {
NodePath string NodePath string
MetaDir string MetaDir string
RunDir 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
} }

View File

@@ -1,4 +1,4 @@
package corestate package run_manager
import ( import (
"context" "context"
@@ -8,16 +8,16 @@ import (
"time" "time"
) )
func (r *RunManager) File(index string) RunFileManagerContract { func File(index string) RunFileManagerContract {
value, ok := (*r.indexedPaths)[index] value, ok := indexedPaths[index]
if !ok { if !ok {
err := r.indexPaths() err := indexPaths()
if err != nil { if err != nil {
return &RunFileManager{ return &RunFileManager{
err: err, err: err,
} }
} }
value, ok = (*r.indexedPaths)[index] value, ok = indexedPaths[index]
if !ok { if !ok {
return &RunFileManager{ return &RunFileManager{
err: fmt.Errorf("cannot detect file under index %s", index), err: fmt.Errorf("cannot detect file under index %s", index),
@@ -45,22 +45,24 @@ func (r *RunFileManager) Close() error {
return r.file.Close() return r.file.Close()
} }
func (r *RunFileManager) Watch(ctx context.Context, callback func()) error { func (r *RunFileManager) Watch(parentCtx context.Context, callback func()) (context.CancelFunc, error) {
if r.err != nil { if r.err != nil {
return r.err return nil, r.err
} }
if r.file == nil { if r.file == nil {
return fmt.Errorf("file is not opened") return nil, fmt.Errorf("file is not opened")
} }
info, err := r.file.Stat() info, err := r.file.Stat()
if err != nil { if err != nil {
return err return nil, err
} }
origStat := info.Sys().(*syscall.Stat_t) origStat := info.Sys().(*syscall.Stat_t)
origIno := origStat.Ino origIno := origStat.Ino
origModTime := info.ModTime() origModTime := info.ModTime()
ctx, cancel := context.WithCancel(parentCtx)
go func() { go func() {
for { for {
select { select {
@@ -89,5 +91,5 @@ func (r *RunFileManager) Watch(ctx context.Context, callback func()) error {
} }
}() }()
return nil return cancel, nil
} }

View File

@@ -0,0 +1,158 @@
package run_manager
import (
"context"
"fmt"
"os"
"path/filepath"
"github.com/akyaiy/GoSally-mvp/core/utils"
)
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
}
var (
created bool
runDir string
indexedPaths = make(map[string]string)
)
type RunFileManagerContract interface {
Open() (*os.File, error)
Close() error
Watch(parentCtx context.Context, callback func()) (context.CancelFunc, error)
}
type RunFileManager struct {
err error
indexedPath string
file *os.File
}
// func (c *CoreState) RuntimeDir() RunManagerContract {
// return c.RM
// }
// Create creates a temp directory
func Create(uuid32 string) (string, error) {
if created {
return runDir, fmt.Errorf("runtime directory is already created")
}
path, err := os.MkdirTemp("", fmt.Sprintf("*-%s-%s", uuid32, "gosally-runtime"))
if err != nil {
return "", err
}
runDir = path
created = true
return path, nil
}
func Clean() error {
created = false
indexedPaths = nil
return utils.CleanTempRuntimes(runDir)
}
// Quite dangerous and goofy.
// TODO: implement a better variant of runDir indexing on the second stage of initialization
func Toggle() string {
runDir = filepath.Dir(os.Args[0])
created = true
return runDir
}
func Get(index string) (string, error) {
if !created {
return "", fmt.Errorf("runtime directory is not created")
}
if indexedPaths == nil {
err := indexPaths()
if err != nil {
return "", nil
}
}
if indexedPaths == nil {
return "", fmt.Errorf("indexedPaths is nil")
}
value, ok := indexedPaths[index]
if !ok {
err := indexPaths()
if err != nil {
return "", err
}
value, ok = indexedPaths[index]
if !ok {
return "", fmt.Errorf("cannot detect file under index %s", index)
}
}
return value, nil
}
func Set(index string) error {
if !created {
return fmt.Errorf("runtime directory is not created")
}
fullPath := filepath.Join(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 indexedPaths == nil {
err = indexPaths()
if err != nil {
return err
}
} else {
indexedPaths[index] = fullPath
}
return nil
}
func SetDir(index string) error {
if !created {
return fmt.Errorf("runtime directory is not created")
}
fullPath := filepath.Join(runDir, index)
err := os.MkdirAll(fullPath, 0755)
if err != nil {
return err
}
return nil
}
func indexPaths() error {
if !created {
return fmt.Errorf("runtime directory is not created")
}
i, err := utils.IndexPaths(runDir)
if err != nil {
return err
}
indexedPaths = i
return nil
}
func RuntimeDir() string {
return runDir
}

View File

@@ -18,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(int(config.GetInternalConsts().GetUUIDLength())) uuid16, err := utils.NewUUID(int(config.UUIDLength))
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()))
@@ -127,7 +127,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
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")) uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
response := ResponseFormat{ response := ResponseFormat{
ResponsibleAgentUUID: uuid32, ResponsibleAgentUUID: uuid32,
RequestedCommand: cmd, RequestedCommand: cmd,

View File

@@ -16,7 +16,7 @@ import (
// 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(int(config.GetInternalConsts().GetUUIDLength())) uuid16, err := utils.NewUUID(int(config.UUIDLength))
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()))
@@ -111,7 +111,7 @@ 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")) uuid32, _ := corestate.GetNodeUUID(filepath.Join(config.MetaDir, "uuid"))
response := ResponseFormat{ response := ResponseFormat{
ResponsibleAgentUUID: uuid32, ResponsibleAgentUUID: uuid32,
RequestedCommand: "list", RequestedCommand: "list",

View File

@@ -6,16 +6,18 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "io"
"log/slog" "log"
"net/http" "net/http"
"os" "os"
"os/exec"
"path/filepath" "path/filepath"
"regexp"
"strconv" "strconv"
"strings" "strings"
"syscall"
"github.com/akyaiy/GoSally-mvp/core/config" "github.com/akyaiy/GoSally-mvp/core/config"
"github.com/akyaiy/GoSally-mvp/core/run_manager"
"github.com/akyaiy/GoSally-mvp/core/utils"
"golang.org/x/net/context"
) )
const ( const (
@@ -36,14 +38,20 @@ type UpdaterContract interface {
} }
type Updater struct { type Updater struct {
Log slog.Logger log *log.Logger
Config *config.Conf config *config.Conf
env *config.Env
ctx context.Context
cancel context.CancelFunc
} }
func NewUpdater(log slog.Logger, cfg *config.Conf) *Updater { func NewUpdater(ctx context.Context, log *log.Logger, cfg *config.Conf, env *config.Env) *Updater {
return &Updater{ return &Updater{
Log: log, log: log,
Config: cfg, config: cfg,
env: env,
ctx: ctx,
} }
} }
@@ -80,11 +88,11 @@ func isVersionNewer(current, latest Version) bool {
if i < len(currentParts) { if i < len(currentParts) {
cur, err := strconv.Atoi(currentParts[i]) cur, err := strconv.Atoi(currentParts[i])
if err != nil { if err != nil {
cur = 0 // или можно обработать ошибку иначе cur = 0
} }
curPart = cur curPart = cur
} else { } else {
curPart = 0 // Если части в current меньше, считаем недостающие нулями curPart = 0
} }
if i < len(latestParts) { if i < len(latestParts) {
@@ -103,31 +111,15 @@ func isVersionNewer(current, latest Version) bool {
if curPart > latPart { if curPart > latPart {
return false return false
} }
// если равны — идём дальше
} }
return false // все части равны, значит не новее return false
} }
// if len(currentParts) >= 1 && len(latestParts) >= 1 {
// if currentParts[0] < latestParts[0] {
// if len(currentParts) < 2 || len(latestParts) < 2 {
// if currentParts[1] < latestParts[1] {
// return true
// }
// if currentParts[1] > latestParts[1] {
// return false
// }
// }
// if currentParts[0] > latestParts[0] {
// return false
// }
// }
// GetCurrentVersion reads the current version from the version file and returns it along with the branch. // GetCurrentVersion reads the current version from the version file and returns it along with the branch.
func (u *Updater) GetCurrentVersion() (Version, Branch, error) { func (u *Updater) GetCurrentVersion() (Version, Branch, error) {
version, branch, err := splitVersionString(string(config.GetUpdateConsts().GetNodeVersion())) version, branch, err := splitVersionString(string(config.NodeVersion))
if err != nil { if err != nil {
u.Log.Error("Failed to parse version string", slog.String("version", string(config.GetUpdateConsts().GetNodeVersion())), slog.String("error", err.Error())) u.log.Printf("Failed to parse version string: %s", err.Error())
return "", "", err return "", "", err
} }
switch branch { switch branch {
@@ -139,28 +131,28 @@ func (u *Updater) GetCurrentVersion() (Version, Branch, error) {
} }
func (u *Updater) GetLatestVersion(updateBranch Branch) (Version, Branch, error) { func (u *Updater) GetLatestVersion(updateBranch Branch) (Version, Branch, error) {
repoURL := u.Config.Updates.RepositoryURL repoURL := u.config.Updates.RepositoryURL
if repoURL == "" { if repoURL == "" {
u.Log.Error("RepositoryURL is empty in config") u.log.Printf("Failed to get latest version: %s", "RepositoryURL is empty in config")
return "", "", errors.New("repository URL is empty") return "", "", errors.New("repository URL is empty")
} }
if !strings.HasPrefix(repoURL, "http://") && !strings.HasPrefix(repoURL, "https://") { if !strings.HasPrefix(repoURL, "http://") && !strings.HasPrefix(repoURL, "https://") {
u.Log.Error("RepositoryURL does not start with http:// or https://", slog.String("RepositoryURL", repoURL)) u.log.Printf("Failed to get latest version: %s: %s", "RepositoryURL does not start with http:// or https:/", repoURL)
return "", "", errors.New("repository URL must start with http:// or https://") return "", "", errors.New("repository URL must start with http:// or https://")
} }
response, err := http.Get(repoURL + "/" + config.GetUpdateConsts().GetActualFileName()) response, err := http.Get(repoURL + "/" + config.ActualFileName)
if err != nil { if err != nil {
u.Log.Error("Failed to fetch latest version", slog.String("error", err.Error())) u.log.Printf("Failed to fetch latest version: %s", err.Error())
return "", "", err return "", "", err
} }
defer response.Body.Close() defer response.Body.Close()
if response.StatusCode != http.StatusOK { if response.StatusCode != http.StatusOK {
u.Log.Error("Failed to fetch latest version", slog.Int("status", response.StatusCode)) u.log.Printf("Failed to fetch latest version: HTTP status %d", response.StatusCode)
return "", "", errors.New("failed to fetch latest version, status code: " + http.StatusText(response.StatusCode)) return "", "", errors.New("failed to fetch latest version, status code: " + http.StatusText(response.StatusCode))
} }
data, err := io.ReadAll(response.Body) data, err := io.ReadAll(response.Body)
if err != nil { if err != nil {
u.Log.Error("Failed to read latest version response", slog.String("error", err.Error())) u.log.Printf("Failed to read latest version response: %s", err.Error())
return "", "", err return "", "", err
} }
lines := strings.Split(string(data), "\n") lines := strings.Split(string(data), "\n")
@@ -171,14 +163,13 @@ func (u *Updater) GetLatestVersion(updateBranch Branch) (Version, Branch, error)
} }
version, branch, err := splitVersionString(string(line)) version, branch, err := splitVersionString(string(line))
if err != nil { if err != nil {
u.Log.Error("Failed to parse version string", slog.String("version", string(line)), slog.String("error", err.Error())) u.log.Printf("Failed to parse version string: %s", err.Error())
return "", "", err return "", "", err
} }
if branch == updateBranch { if branch == updateBranch {
return Version(version), Branch(branch), nil return Version(version), Branch(branch), nil
} }
} }
u.Log.Warn("No version found for branch", slog.String("branch", string(updateBranch)))
return "", "", errors.New("no version found for branch: " + string(updateBranch)) return "", "", errors.New("no version found for branch: " + string(updateBranch))
} }
@@ -198,146 +189,145 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, error) {
} }
func (u *Updater) Update() error { func (u *Updater) Update() error {
if !(u.Config.Updates.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("", "*-gosally-update")
if err != nil { if err := run_manager.SetDir("update"); err != nil {
return errors.New("failed to create temp dir " + err.Error()) return fmt.Errorf("failed to create update dir: %w", err)
} }
downloadPath := filepath.Join(run_manager.RuntimeDir(), "update")
_, currentBranch, err := u.GetCurrentVersion() _, currentBranch, err := u.GetCurrentVersion()
if err != nil { if err != nil {
return errors.New("failed to get current version: " + err.Error()) return fmt.Errorf("failed to get current version: %w", err)
} }
latestVersion, latestBranch, err := u.GetLatestVersion(currentBranch) latestVersion, latestBranch, err := u.GetLatestVersion(currentBranch)
if err != nil { if err != nil {
return errors.New("failed to get latest version: " + err.Error()) return fmt.Errorf("failed to get latest version: %w", err)
} }
updateArchiveName := config.GetUpdateConsts().GetUpdateArchiveName() + ".v" + string(latestVersion) + "-" + string(latestBranch)
updateDest := u.Config.Updates.RepositoryURL + "/" + updateArchiveName + ".tar.gz" updateArchiveName := fmt.Sprintf("%s.v%s-%s", config.UpdateArchiveName, latestVersion, latestBranch)
updateDest := fmt.Sprintf("%s/%s.%s", u.config.Updates.RepositoryURL, updateArchiveName, "tar.gz")
resp, err := http.Get(updateDest) resp, err := http.Get(updateDest)
if err != nil { if err != nil {
return errors.New("failed to fetch latest version archive: " + err.Error()) return fmt.Errorf("failed to fetch archive: %w", err)
} }
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode != http.StatusOK { if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body) body, _ := io.ReadAll(resp.Body)
return errors.New("failed to fetch latest version archive: status " + resp.Status + ", body: " + string(body)) return fmt.Errorf("unexpected HTTP status: %s, body: %s", resp.Status, body)
} }
gzReader, err := gzip.NewReader(resp.Body) gzReader, err := gzip.NewReader(resp.Body)
if err != nil { if err != nil {
return errors.New("failed to create gzip reader: " + err.Error()) return fmt.Errorf("gzip reader error: %w", err)
} }
defer gzReader.Close() defer gzReader.Close()
tarReader := tar.NewReader(gzReader) tarReader := tar.NewReader(gzReader)
for { for {
header, err := tarReader.Next() header, err := tarReader.Next()
if err == io.EOF { if err == io.EOF {
break // archive is fully read break
} }
if err != nil { if err != nil {
return errors.New("failed to read tar header: " + err.Error()) return fmt.Errorf("tar read error: %w", err)
} }
targetPath := filepath.Join(downloadPath, header.Name) relativeParts := strings.SplitN(header.Name, string(os.PathSeparator), 2)
if len(relativeParts) < 2 {
// It's either a top level directory or garbage.
continue
}
cleanName := relativeParts[1]
targetPath := filepath.Join(downloadPath, cleanName)
switch header.Typeflag { switch header.Typeflag {
case tar.TypeDir: case tar.TypeDir:
// Создаём директорию
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil { if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
return errors.New("failed to create directory: " + err.Error()) return fmt.Errorf("mkdir error: %w", err)
} }
case tar.TypeReg: case tar.TypeReg:
// Создаём директорию, если её ещё нет if err := run_manager.Set(filepath.Join("update", cleanName)); err != nil {
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil { return fmt.Errorf("set file error: %w", err)
return errors.New("failed to create directory for file: " + err.Error())
} }
// Создаём файл f := run_manager.File(filepath.Join("update", cleanName))
outFile, err := os.Create(targetPath) outFile, err := f.Open()
if err != nil { if err != nil {
return errors.New("failed to create file: " + err.Error()) return fmt.Errorf("open file error: %w", err)
} }
// Копируем содержимое
if _, err := io.Copy(outFile, tarReader); err != nil { if _, err := io.Copy(outFile, tarReader); err != nil {
outFile.Close() outFile.Close()
return errors.New("failed to copy file content: " + err.Error()) return fmt.Errorf("copy file error: %w", err)
} }
outFile.Close() outFile.Close()
default: default:
return errors.New("unsupported tar entry type: " + string(header.Typeflag)) return fmt.Errorf("unsupported tar type: %v", header.Typeflag)
} }
} }
return u.InstallAndRestart(filepath.Join(downloadPath, updateArchiveName, "node"))
}
func (u *Updater) InstallAndRestart(newBinaryPath string) error { return u.InstallAndRestart()
nodePath := os.Getenv("NODE_PATH") }
func (u *Updater) InstallAndRestart() error {
nodePath := u.env.NodePath
if nodePath == "" { if nodePath == "" {
return errors.New("NODE_PATH environment variable is not set") return errors.New("GS_NODE_PATH environment variable is not set")
} }
installDir := filepath.Join(nodePath, "bin") installDir := filepath.Join(nodePath, "bin")
targetPath := filepath.Join(installDir, "node") targetPath := filepath.Join(installDir, "node")
// Копируем новый бинарник f := run_manager.File("update/node")
input, err := os.Open(newBinaryPath) input, err := f.Open()
if err != nil { if err != nil {
return err return fmt.Errorf("cannot open new binary: %w", err)
} }
defer f.Close()
output, err := os.Create(targetPath) output, err := os.Create(targetPath)
if err != nil { if err != nil {
return err return fmt.Errorf("cannot create target binary: %w", err)
} }
if _, err := io.Copy(output, input); err != nil { if _, err := io.Copy(output, input); err != nil {
return err output.Close()
} return fmt.Errorf("copy failed: %w", err)
if err := os.Chmod(targetPath, 0755); err != nil {
return errors.New("failed to chmod file: " + err.Error())
}
input.Close()
reSafeTmpDir := regexp.QuoteMeta(os.TempDir())
toClean := regexp.MustCompile(
fmt.Sprintf(`^(%s/\d+-gosally-update/)`, reSafeTmpDir),
).FindStringSubmatch(newBinaryPath)
if len(toClean) > 1 {
os.RemoveAll(toClean[0])
} }
output.Close() output.Close()
// Запускаем новый процесс
u.Log.Info("Launching new version...", slog.String("path", targetPath))
cmd := exec.Command(targetPath, os.Args[1:]...)
cmd.Env = os.Environ()
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
err = nil if err := os.Chmod(targetPath, 0755); err != nil {
if err = cmd.Start(); err != nil { return fmt.Errorf("failed to chmod: %w", err)
return err
}
u.Log.Info("Shutting down")
os.Exit(0)
return errors.New("failed to shutdown the process")
} }
// func (u *Updater) Update() error { u.log.Printf("Launching new version: path is %s", targetPath)
// if !(u.Config.UpdatesEnabled && u.Config.Updates.AllowUpdates && u.Config.Updates.AllowDowngrades) { // cmd := exec.Command(targetPath, os.Args[1:]...)
// u.Log.Info("Updates are disabled in config, skipping update") // cmd.Env = os.Environ()
// return nil // cmd.Stdout = os.Stdout
// } // cmd.Stderr = os.Stderr
// wantedVersion := u.Config.Updates.WantedVersion // cmd.Stdin = os.Stdin
// _, wantedBranch, _ := splitVersionString(wantedVersion) args := os.Args
// newVersion, newBranch, err := u.GetLatestVersion(wantedBranch) args[0] = targetPath
env := utils.SetEviron(os.Environ(), "GS_PARENT_PID=-1")
run_manager.Clean()
return syscall.Exec(targetPath, args, env)
//u.cancel()
// TODO: fix this crap and find a better way to update without errors
// for {
// _, err := run_manager.Get("run.lock")
// if err != nil { // if err != nil {
// return err // break
// }
// if wantedBranch != newBranch {
// u.Log.Info("Wanted version branch does not match latest version branch: updating wanted branch",
// slog.String("wanted_branch", string(wantedBranch)),
// slog.String("latest_branch", string(newBranch)),
// )
// } // }
// } // }
// return cmd.Start()
}
func (u *Updater) Shutdownfunc(f context.CancelFunc) {
u.cancel = f
}

View File

@@ -1,11 +1,34 @@
package utils package utils
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"reflect" "reflect"
"strings"
) )
func SetEviron(eviron []string, envs ...string) []string {
envMap := make(map[string]string)
for _, e := range eviron {
parts := strings.SplitN(e, "=", 2)
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
for _, e := range envs {
parts := strings.SplitN(e, "=", 2)
if len(parts) == 2 {
envMap[parts[0]] = parts[1]
}
}
newEviron := make([]string, 0, len(envMap))
for k, v := range envMap {
newEviron = append(newEviron, fmt.Sprintf("%s=%s", k, v))
}
return newEviron
}
func CleanTempRuntimes(pattern string) error { func CleanTempRuntimes(pattern string) error {
matches, err := filepath.Glob(pattern) matches, err := filepath.Glob(pattern)
if err != nil { if err != nil {
@@ -42,7 +65,7 @@ func ExistsMatchingDirs(pattern, exclude string) (bool, error) {
return false, nil return false, nil
} }
func IndexPaths(runDir string) (*map[string]string, error) { func IndexPaths(runDir string) (map[string]string, error) {
indexed := make(map[string]string) indexed := make(map[string]string)
err := filepath.Walk(runDir, func(path string, info os.FileInfo, err error) error { err := filepath.Walk(runDir, func(path string, info os.FileInfo, err error) error {
@@ -67,7 +90,7 @@ func IndexPaths(runDir string) (*map[string]string, error) {
return nil, err return nil, err
} }
return &indexed, nil return indexed, nil
} }
func IsFullyInitialized(i any) bool { func IsFullyInitialized(i any) bool {

48
core/utils/utils_test.go Normal file
View File

@@ -0,0 +1,48 @@
package utils
import (
"fmt"
"reflect"
"sort"
"testing"
)
func TestFunc_SetEviron(t *testing.T) {
tests := []struct {
eviron []string
envs []string
want []string
}{
{
[]string{"ENV1=1", "ENV2=2", "ENV3=4"},
[]string{"ENV3=3"},
[]string{"ENV1=1", "ENV2=2", "ENV3=3"},
},
{
[]string{"ENV1=1", "ENV2=5", "ENV3=4"},
[]string{"ENV2=2", "ENV3=3"},
[]string{"ENV1=1", "ENV2=2", "ENV3=3"},
},
{
[]string{"ENV1=1", "ENV2=2", "ENV3=3"},
[]string{"ENV4=4"},
[]string{"ENV1=1", "ENV2=2", "ENV3=3", "ENV4=4"},
},
{
[]string{"ENV1=1", "ENV2=2", "ENV3=4"},
[]string{"ENV3=2", "ENV3=3"},
[]string{"ENV1=1", "ENV2=2", "ENV3=3"},
},
}
for _, tt := range tests {
t.Run(fmt.Sprintf("in %q set new %q", tt.eviron, tt.envs), func(t *testing.T) {
got := SetEviron(tt.eviron, tt.envs...)
sort.Strings(got)
sort.Strings(tt.want)
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("SetEviron(%q, %q) = got %v; want %v", tt.eviron, tt.envs, got, tt.want)
}
})
}
}

View File

@@ -26,15 +26,15 @@ func NewUUID(length int) (string, error) {
} }
func NewUUID32() (string, error) { func NewUUID32() (string, error) {
return NewUUID(config.GetInternalConsts().GetUUIDLength()) return NewUUID(config.UUIDLength)
} }
func NewUUID32Raw() ([]byte, error) { func NewUUID32Raw() ([]byte, error) {
data, err := NewUUIDRaw(config.GetInternalConsts().GetUUIDLength()) data, err := NewUUIDRaw(config.UUIDLength)
if err != nil { if err != nil {
return data, err return data, err
} }
if len(data) != config.GetInternalConsts().GetUUIDLength() { if len(data) != config.UUIDLength {
return data, errors.New("unexpected UUID length") return data, errors.New("unexpected UUID length")
} }
return data, nil return data, nil

View File

@@ -1,6 +1,8 @@
package main package main
import "github.com/akyaiy/GoSally-mvp/cmd" import (
"github.com/akyaiy/GoSally-mvp/cmd"
)
func main() { func main() {
cmd.Execute() cmd.Execute()