Implement automatic update functionality and improve server initialization; add NODE_PATH to Makefile, enhance logging, and update README

This commit is contained in:
alex
2025-07-05 22:13:16 +03:00
parent 2fdc32ce9f
commit 66f3d12412
14 changed files with 271 additions and 72 deletions

View File

@@ -2,6 +2,8 @@ APP_NAME := node
BIN_DIR := bin BIN_DIR := bin
GOPATH := $(shell go env GOPATH) GOPATH := $(shell go env GOPATH)
export CONFIG_PATH := ./config.yaml export CONFIG_PATH := ./config.yaml
export NODE_PATH := $(shell pwd)
LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/core/config.NodeVersion=v0.0.1-dev' LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/core/config.NodeVersion=v0.0.1-dev'
CGO_CFLAGS := -I/usr/local/include CGO_CFLAGS := -I/usr/local/include
CGO_LDFLAGS := -L/usr/local/lib -llua5.1 -lm -ldl CGO_LDFLAGS := -L/usr/local/lib -llua5.1 -lm -ldl
@@ -38,6 +40,10 @@ runq: build
@echo "Running!" @echo "Running!"
./$(BIN_DIR)/$(APP_NAME) | jq ./$(BIN_DIR)/$(APP_NAME) | jq
pure-run:
@echo "Running!"
./$(BIN_DIR)/$(APP_NAME) | jq
test: test:
@go test ./... | grep -v '^?' || true @go test ./... | grep -v '^?' || true

View File

@@ -4,7 +4,7 @@
System that allows you to build your own infrastructure based on identical nodes and various scripts written using built-in Lua 5.1, shebang scripts (scripts that start with the `#!` symbols), compiled binaries. System that allows you to build your own infrastructure based on identical nodes and various scripts written using built-in Lua 5.1, shebang scripts (scripts that start with the `#!` symbols), compiled binaries.
### Features ### Features
Go Sally is not viable at the moment, but it already has the ability to run embedded scripts, log slog events to stdout, and handle RPC like requests. Go Sally is not viable at the moment, but it already has the ability to run embedded scripts, log slog events to stdout, handle RPC like requests, and independent automatic update from the repository (my pride, to be honest).
### Example of use ### Example of use
The basic directory tree looks something like this The basic directory tree looks something like this

View File

@@ -1,19 +1,21 @@
package main package main
import ( import (
"fmt"
"log/slog" "log/slog"
"net" "net"
"net/http" "net/http"
"regexp" "regexp"
"time"
"golang.org/x/net/netutil" "golang.org/x/net/netutil"
"github.com/akyaiy/GoSally-mvp/core/config" "github.com/akyaiy/GoSally-mvp/core/config"
gs "github.com/akyaiy/GoSally-mvp/core/general_server" gs "github.com/akyaiy/GoSally-mvp/core/general_server"
_ "github.com/akyaiy/GoSally-mvp/core/init"
"github.com/akyaiy/GoSally-mvp/core/logs" "github.com/akyaiy/GoSally-mvp/core/logs"
"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/update"
"github.com/go-chi/cors"
"github.com/go-chi/chi/v5" "github.com/go-chi/chi/v5"
) )
@@ -27,20 +29,37 @@ func init() {
log = logs.SetupLogger(cfg.Mode) log = logs.SetupLogger(cfg.Mode)
log = log.With("mode", cfg.Mode) log = log.With("mode", cfg.Mode)
log.Info("Initializing server", slog.String("address", cfg.HTTPServer.Address)) currentV, currentB, _ := update.NewUpdater(*log, cfg).GetCurrentVersion()
log.Info("Initializing server", slog.String("address", cfg.HTTPServer.Address), slog.String("version", string(currentV)+"-"+string(currentB)))
log.Debug("Server running in debug mode") log.Debug("Server running in debug mode")
} }
func UpdateDaemon(u *update.Updater, cfg config.ConfigConf) {
for {
isNewUpdate, err := u.CkeckUpdates()
if err != nil {
log.Error("Failed to check for updates", slog.String("error", err.Error()))
}
if isNewUpdate {
log.Info("New update available, starting update process...")
err = u.Update()
if err != nil {
log.Error("Failed to update", slog.String("error", err.Error()))
} else {
log.Info("Update completed successfully")
}
} else {
log.Info("No new updates available")
}
time.Sleep(cfg.CheckInterval)
}
}
func main() { func main() {
updater := update.NewUpdater(*log, cfg) updater := update.NewUpdater(*log, cfg)
versuion, versionType, _ := updater.GetCurrentVersion() go UpdateDaemon(updater, *cfg)
fmt.Printf("Current version: %s (%s)\n", versuion, versionType)
ver, vert, _ := updater.GetLatestVersion(versionType)
fmt.Printf("Latest version: %s (%s)\n", ver, vert)
fmt.Println("Checking for updates...")
isNewUpdate, _ := updater.CkeckUpdates()
fmt.Println("Update check result:", isNewUpdate)
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{ serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
Log: *log, Log: *log,
Config: cfg, Config: cfg,
@@ -54,6 +73,13 @@ func main() {
}, serverv1) }, serverv1)
r := chi.NewRouter() r := chi.NewRouter()
r.Use(cors.Handler(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
AllowCredentials: true,
MaxAge: 300,
}))
r.Route(config.GetServerConsts().GetApiRoute()+config.GetServerConsts().GetComDirRoute(), func(r chi.Router) { r.Route(config.GetServerConsts().GetApiRoute()+config.GetServerConsts().GetComDirRoute(), func(r chi.Router) {
r.Get("/", s.HandleList) r.Get("/", s.HandleList)
r.Get("/{cmd}", s.Handle) r.Get("/{cmd}", s.Handle)

View File

@@ -1,7 +1,7 @@
mode: "dev" mode: "dev"
http_server: http_server:
address: "0.0.0.0:8080" address: "192.168.1.176:8080"
timeout: 3s timeout: 3s
idle_timeout: 30s idle_timeout: 30s
api: api:
@@ -23,10 +23,5 @@ com_dir: "com/"
updates: updates:
enabled: true enabled: true
allow-auto-updates: true
allow-updates: true
allow-downgrades: false
check-interval: 1h check-interval: 1h
repository_url: "https://repo.serve.lv/raw/go-sally" repository_url: "https://repo.serve.lv/raw/go-sally"
wanted-version: "latest-stable"

View File

@@ -43,18 +43,16 @@ type Internal struct {
} }
type Updates struct { type Updates struct {
UpdatesEnabled bool `yaml:"enabled" env-default:"false"` UpdatesEnabled bool `yaml:"enabled" env-default:"false"`
AllowAutoUpdates bool `yaml:"allow_auto_updates" env-default:"false"` CheckInterval time.Duration `yaml:"check_interval" env-default:"2h"`
AllowUpdates bool `yaml:"allow_updates" env-default:"false"` RepositoryURL string `yaml:"repository_url" env-default:""`
AllowDowngrades bool `yaml:"allow_downgrades" env-default:"false"` WantedVersion string `yaml:"wanted_version" env-default:"latest-stable"`
CheckInterval time.Duration `yaml:"check_interval" env-default:"2h"`
RepositoryURL string `yaml:"repository_url" env-default:""`
WantedVersion string `yaml:"wanted_version" env-default:"latest-stable"`
} }
// ConfigEnv structure for environment variables // ConfigEnv structure for environment variables
type ConfigEnv struct { type ConfigEnv struct {
ConfigPath string `env:"CONFIG_PATH" env-default:"./cfg/config.yaml"` ConfigPath string `env:"CONFIG_PATH" env-default:"./cfg/config.yaml"`
NodePath string `env:"NODE_PATH" env-default:"./"`
} }
// MustLoadConfig loads the configuration from the specified path and environment variables. // MustLoadConfig loads the configuration from the specified path and environment variables.

View File

@@ -1,7 +1,9 @@
package config package config
import "os"
// UUIDLength is uuids length for sessions. By default it is 16 bytes. // UUIDLength is uuids length for sessions. By default it is 16 bytes.
var UUIDLength byte = 4 var UUIDLength byte = 16
// ApiRoute setting for go-chi for main route for api requests // ApiRoute setting for go-chi for main route for api requests
var ApiRoute string = "/api/{ver}" var ApiRoute string = "/api/{ver}"
@@ -17,6 +19,12 @@ var NodeVersion string
// In the repository, the file specified in the variable contains the current information about updates // In the repository, the file specified in the variable contains the current information about updates
var ActualFileName string = "actual.txt" var ActualFileName string = "actual.txt"
// UpdateArchiveName is the name of the archive that will be used for updates.
var UpdateArchiveName string = "gosally-node"
// UpdateInstallPath is the path where the update will be installed.
var UpdateDownloadPath string = os.TempDir()
type _internalConsts struct{} type _internalConsts struct{}
type _serverConsts struct{} type _serverConsts struct{}
type _updateConsts struct{} type _updateConsts struct{}
@@ -28,7 +36,9 @@ func (_ _updateConsts) GetNodeVersion() string {
} }
return NodeVersion return NodeVersion
} }
func (_ _updateConsts) GetActualFileName() string { return ActualFileName } func (_ _updateConsts) GetActualFileName() string { return ActualFileName }
func (_ _updateConsts) GetUpdateArchiveName() string { return UpdateArchiveName }
func (_ _updateConsts) GetUpdateDownloadPath() string { return UpdateDownloadPath }
func GetInternalConsts() _internalConsts { return _internalConsts{} } func GetInternalConsts() _internalConsts { return _internalConsts{} }
func (_ _internalConsts) GetUUIDLength() byte { return UUIDLength } func (_ _internalConsts) GetUUIDLength() byte { return UUIDLength }

45
core/init/init.go Normal file
View File

@@ -0,0 +1,45 @@
package init
import (
"io"
"log"
"os"
"path/filepath"
"strings"
"syscall"
)
func init() {
if strings.HasPrefix(os.Args[0], "/tmp") {
return
}
runPath, err := os.MkdirTemp("", "*-gs-runtime")
log.SetOutput(os.Stderr)
input, err := os.Open(os.Args[0])
if err != nil {
log.Fatalf("Failed to init node: %s", err)
}
runBinaryPath := filepath.Join(runPath, "node")
output, err := os.Create(runBinaryPath)
if err != nil {
log.Fatalf("Failed to init node: %s", err)
}
if _, err := io.Copy(output, input); err != nil {
log.Fatalf("Failed to init node: %s", err)
}
// Делаем исполняемым (на всякий случай)
if err := os.Chmod(runBinaryPath, 0755); err != nil {
log.Fatalf("Failed to init node: %s", err)
}
input.Close()
output.Close()
runArgs := os.Args
runArgs[0] = runBinaryPath
if err := syscall.Exec(runBinaryPath, runArgs, append(os.Environ(), "GS_RUNTIME_PATH=" + runPath)); err != nil {
log.Fatalf("Failed to init node: %s", err)
}
}

View File

@@ -20,39 +20,39 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
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()))
utils.WriteJSONError(h.w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error()) utils.WriteJSONError(w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error())
return return
} }
log := h.log.With( log := h.log.With(
slog.Group("request", slog.Group("request",
slog.String("version", h.GetVersion()), slog.String("version", h.GetVersion()),
slog.String("url", h.r.URL.String()), slog.String("url", r.URL.String()),
slog.String("method", h.r.Method), slog.String("method", r.Method),
), ),
slog.Group("connection", slog.Group("connection",
slog.String("connection-uuid", uuid16), slog.String("connection-uuid", uuid16),
slog.String("remote", h.r.RemoteAddr), slog.String("remote", r.RemoteAddr),
), ),
) )
log.Info("Received request") log.Info("Received request")
cmd := chi.URLParam(h.r, "cmd") cmd := chi.URLParam(r, "cmd")
if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) { if !h.allowedCmd.MatchString(string([]rune(cmd)[0])) || !h.listAllowedCmd.MatchString(cmd) {
log.Error("HTTP request error", log.Error("HTTP request error",
slog.String("error", "invalid command"), slog.String("error", "invalid command"),
slog.String("cmd", cmd), slog.String("cmd", cmd),
slog.Int("status", http.StatusBadRequest)) slog.Int("status", http.StatusBadRequest))
utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid command") utils.WriteJSONError(w, http.StatusBadRequest, "invalid command")
return return
} }
scriptPath := h.comMatch(chi.URLParam(h.r, "ver"), cmd) scriptPath := h.comMatch(chi.URLParam(r, "ver"), cmd)
if scriptPath == "" { if scriptPath == "" {
log.Error("HTTP request error", log.Error("HTTP request error",
slog.String("error", "command not found"), slog.String("error", "command not found"),
slog.String("cmd", cmd), slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound)) slog.Int("status", http.StatusNotFound))
utils.WriteJSONError(h.w, http.StatusNotFound, "command not found") utils.WriteJSONError(w, http.StatusNotFound, "command not found")
return return
} }
@@ -62,7 +62,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
slog.String("error", "command not found"), slog.String("error", "command not found"),
slog.String("cmd", cmd), slog.String("cmd", cmd),
slog.Int("status", http.StatusNotFound)) slog.Int("status", http.StatusNotFound))
utils.WriteJSONError(h.w, http.StatusNotFound, "command not found") utils.WriteJSONError(w, http.StatusNotFound, "command not found")
return return
} }
@@ -70,7 +70,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
defer L.Close() defer L.Close()
paramsTable := L.NewTable() paramsTable := L.NewTable()
qt := h.r.URL.Query() qt := r.URL.Query()
for k, v := range qt { for k, v := range qt {
if len(v) > 0 { if len(v) > 0 {
L.SetField(paramsTable, k, lua.LString(v[0])) L.SetField(paramsTable, k, lua.LString(v[0]))
@@ -91,7 +91,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
if err := L.DoFile(prepareLuaEnv); err != nil { if err := L.DoFile(prepareLuaEnv); err != nil {
log.Error("Failed to prepare lua environment", log.Error("Failed to prepare lua environment",
slog.String("error", err.Error())) slog.String("error", err.Error()))
utils.WriteJSONError(h.w, http.StatusInternalServerError, "lua error: "+err.Error()) utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error())
return return
} }
} else { } else {
@@ -101,7 +101,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
if err := L.DoFile(scriptPath); err != nil { if err := L.DoFile(scriptPath); err != nil {
log.Error("Failed to execute lua script", log.Error("Failed to execute lua script",
slog.String("error", err.Error())) slog.String("error", err.Error()))
utils.WriteJSONError(h.w, http.StatusInternalServerError, "lua error: "+err.Error()) utils.WriteJSONError(w, http.StatusInternalServerError, "lua error: "+err.Error())
return return
} }
@@ -109,7 +109,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
tbl, ok := lv.(*lua.LTable) tbl, ok := lv.(*lua.LTable)
if !ok { if !ok {
log.Error("Lua global 'Out' is not a table") log.Error("Lua global 'Out' is not a table")
utils.WriteJSONError(h.w, http.StatusInternalServerError, "'Out' is not a table") utils.WriteJSONError(w, http.StatusInternalServerError, "'Out' is not a table")
return return
} }
@@ -117,7 +117,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
resultTbl, ok := resultVal.(*lua.LTable) resultTbl, ok := resultVal.(*lua.LTable)
if !ok { if !ok {
log.Error("Lua global 'Result' is not a table") log.Error("Lua global 'Result' is not a table")
utils.WriteJSONError(h.w, http.StatusInternalServerError, "'Result' is not a table") utils.WriteJSONError(w, http.StatusInternalServerError, "'Result' is not a table")
return return
} }
@@ -126,8 +126,8 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
out[key.String()] = utils.ConvertLuaTypesToGolang(value) out[key.String()] = utils.ConvertLuaTypesToGolang(value)
}) })
h.w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(h.w).Encode(out); err != nil { if err := json.NewEncoder(w).Encode(out); err != nil {
log.Error("Failed to encode JSON response", log.Error("Failed to encode JSON response",
slog.String("error", err.Error())) slog.String("error", err.Error()))
} }

View File

@@ -18,18 +18,18 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
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()))
utils.WriteJSONError(h.w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error()) utils.WriteJSONError(w, http.StatusInternalServerError, "failed to generate UUID: "+err.Error())
return return
} }
log := h.log.With( log := h.log.With(
slog.Group("request", slog.Group("request",
slog.String("version", h.GetVersion()), slog.String("version", h.GetVersion()),
slog.String("url", h.r.URL.String()), slog.String("url", r.URL.String()),
slog.String("method", h.r.Method), slog.String("method", r.Method),
), ),
slog.Group("connection", slog.Group("connection",
slog.String("connection-uuid", uuid16), slog.String("connection-uuid", uuid16),
slog.String("remote", h.r.RemoteAddr), slog.String("remote", r.RemoteAddr),
), ),
) )
log.Info("Received request") log.Info("Received request")
@@ -46,11 +46,11 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
if files, err = os.ReadDir(h.cfg.ComDir); err != nil { if files, err = os.ReadDir(h.cfg.ComDir); err != nil {
log.Error("Failed to read commands directory", log.Error("Failed to read commands directory",
slog.String("error", err.Error())) slog.String("error", err.Error()))
utils.WriteJSONError(h.w, http.StatusInternalServerError, "failed to read commands directory: "+err.Error()) utils.WriteJSONError(w, http.StatusInternalServerError, "failed to read commands directory: "+err.Error())
return return
} }
apiVer := chi.URLParam(h.r, "ver") apiVer := chi.URLParam(r, "ver")
// Сначала ищем версионные // Сначала ищем версионные
for _, file := range files { for _, file := range files {
@@ -110,8 +110,8 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
log.Info("Session completed") log.Info("Session completed")
h.w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(h.w).Encode(commands); err != nil { if err := json.NewEncoder(w).Encode(commands); err != nil {
h.log.Error("Failed to write JSON error response", h.log.Error("Failed to write JSON error response",
slog.String("error", err.Error())) slog.String("error", err.Error()))
} }

View File

@@ -4,7 +4,6 @@ package sv1
import ( import (
"log/slog" "log/slog"
"net/http"
"regexp" "regexp"
"github.com/akyaiy/GoSally-mvp/core/config" "github.com/akyaiy/GoSally-mvp/core/config"
@@ -21,9 +20,6 @@ type HandlerV1InitStruct struct {
// HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests. // HandlerV1 implements the ServerV1UtilsContract and serves as the main handler for API requests.
type HandlerV1 struct { type HandlerV1 struct {
w http.ResponseWriter
r *http.Request
log slog.Logger log slog.Logger
cfg *config.ConfigConf cfg *config.ConfigConf

View File

@@ -2,21 +2,18 @@ package sv1
import ( import (
"log/slog" "log/slog"
"net/http"
"os" "os"
"regexp" "regexp"
"github.com/akyaiy/GoSally-mvp/core/utils"
) )
func (h *HandlerV1) errNotFound(w http.ResponseWriter, r *http.Request) { // func (h *HandlerV1) errNotFound(w http.ResponseWriter, r *http.Request) {
utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid request") // utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid request")
h.log.Error("HTTP request error", // h.log.Error("HTTP request error",
slog.String("remote", h.r.RemoteAddr), // slog.String("remote", h.r.RemoteAddr),
slog.String("method", h.r.Method), // slog.String("method", h.r.Method),
slog.String("url", h.r.URL.String()), // slog.String("url", h.r.URL.String()),
slog.Int("status", http.StatusBadRequest)) // slog.Int("status", http.StatusBadRequest))
} // }
func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) { func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) {
data, err := os.ReadFile(path) data, err := os.ReadFile(path)

View File

@@ -1,10 +1,15 @@
package update package update
import ( import (
"archive/tar"
"compress/gzip"
"errors" "errors"
"io" "io"
"log/slog" "log/slog"
"net/http" "net/http"
"os"
"os/exec"
"path/filepath"
"strconv" "strconv"
"strings" "strings"
@@ -187,15 +192,133 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, error) {
if currentVersion == latestVersion && currentBranch == latestBranch { if currentVersion == latestVersion && currentBranch == latestBranch {
return false, nil return false, nil
} }
u.Log.Info("New update available",
slog.String("current_version", string(currentVersion)),
slog.String("current_branch", string(currentBranch)),
slog.String("latest_version", string(latestVersion)),
slog.String("latest_branch", string(latestBranch)),
)
return true, nil return true, nil
} }
func (u *Updater) Update() error {
if !(u.Config.UpdatesEnabled) {
return errors.New("updates are disabled in config, skipping update")
}
downloadPath, err := os.MkdirTemp("", "*-gs-up")
if err != nil {
return errors.New("failed to create temp dir " + err.Error())
}
//defer os.RemoveAll(downloadPath)
_, currentBranch, err := u.GetCurrentVersion()
if err != nil {
return errors.New("failed to get current version: " + err.Error())
}
latestVersion, latestBranch, err := u.GetLatestVersion(currentBranch)
if err != nil {
return errors.New("failed to get latest version: " + err.Error())
}
updateArchiveName := config.GetUpdateConsts().GetUpdateArchiveName() + ".v" + string(latestVersion) + "-" + string(latestBranch)
updateDest := u.Config.Updates.RepositoryURL + "/" + updateArchiveName + ".tar.gz"
resp, err := http.Get(updateDest)
if err != nil {
return errors.New("failed to fetch latest version archive: " + err.Error())
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return errors.New("failed to fetch latest version archive: status " + resp.Status + ", body: " + string(body))
}
gzReader, err := gzip.NewReader(resp.Body)
if err != nil {
return errors.New("failed to create gzip reader: " + err.Error())
}
defer gzReader.Close()
tarReader := tar.NewReader(gzReader)
for {
header, err := tarReader.Next()
if err == io.EOF {
break // archive is fully read
}
if err != nil {
return errors.New("failed to read tar header: " + err.Error())
}
targetPath := filepath.Join(downloadPath, header.Name)
switch header.Typeflag {
case tar.TypeDir:
// Создаём директорию
if err := os.MkdirAll(targetPath, os.FileMode(header.Mode)); err != nil {
return errors.New("failed to create directory: " + err.Error())
}
case tar.TypeReg:
// Создаём директорию, если её ещё нет
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
return errors.New("failed to create directory for file: " + err.Error())
}
// Создаём файл
outFile, err := os.Create(targetPath)
if err != nil {
return errors.New("failed to create file: " + err.Error())
}
// Копируем содержимое
if _, err := io.Copy(outFile, tarReader); err != nil {
outFile.Close()
return errors.New("failed to copy file content: " + err.Error())
}
outFile.Close()
default:
return errors.New("unsupported tar entry type: " + string(header.Typeflag))
}
}
return u.InstallAndRestart(filepath.Join(downloadPath, updateArchiveName, "node"))
}
func (u *Updater) InstallAndRestart(newBinaryPath string) error {
nodePath := os.Getenv("NODE_PATH")
if nodePath == "" {
return errors.New("NODE_PATH environment variable is not set")
}
installDir := filepath.Join(nodePath, "bin")
targetPath := filepath.Join(installDir, "node")
// Копируем новый бинарник
input, err := os.Open(newBinaryPath)
if err != nil {
return err
}
output, err := os.Create(targetPath)
if err != nil {
return err
}
if _, err := io.Copy(output, input); err != nil {
return err
}
if err := os.Chmod(targetPath, 0755); err != nil {
return errors.New("failed to chmod file: " + err.Error())
}
input.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 = cmd.Start(); err != nil {
return err
}
// if err := syscall.Exec(targetPath, os.Args, os.Environ()); err != nil {
// u.Log.Error("Failed to run new version automatickly", slog.String("err", err.Error()))
// return err
// }
u.Log.Info("Shutting down")
os.Exit(0)
return errors.New("failed to shutdown the process")
}
// func (u *Updater) Update() error { // func (u *Updater) Update() error {
// if !(u.Config.UpdatesEnabled && u.Config.Updates.AllowUpdates && u.Config.Updates.AllowDowngrades) { // if !(u.Config.UpdatesEnabled && u.Config.Updates.AllowUpdates && u.Config.Updates.AllowDowngrades) {
// u.Log.Info("Updates are disabled in config, skipping update") // u.Log.Info("Updates are disabled in config, skipping update")

1
go.mod
View File

@@ -11,6 +11,7 @@ require (
require ( require (
github.com/BurntSushi/toml v1.5.0 // indirect github.com/BurntSushi/toml v1.5.0 // indirect
github.com/go-chi/cors v1.2.2
github.com/joho/godotenv v1.5.1 // indirect github.com/joho/godotenv v1.5.1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect

2
go.sum
View File

@@ -3,6 +3,8 @@ github.com/BurntSushi/toml v1.5.0 h1:W5quZX/G/csjUnuI8SUYlsHs9M38FC7znL0lIO+DvMg
github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/BurntSushi/toml v1.5.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho=
github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618= github.com/go-chi/chi/v5 v5.2.2 h1:CMwsvRVTbXVytCk1Wd72Zy1LAsAh9GxMmSNWLHCG618=
github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops= github.com/go-chi/chi/v5 v5.2.2/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
github.com/go-chi/cors v1.2.2 h1:Jmey33TE+b+rB7fT8MUy1u0I4L+NARQlK6LhzKPSyQE=
github.com/go-chi/cors v1.2.2/go.mod h1:sSbTewc+6wYHBBCW7ytsFSn836hqM7JxpglAy2Vzc58=
github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4= github.com/ilyakaznacheev/cleanenv v1.5.0 h1:0VNZXggJE2OYdXE87bfSSwGxeiGt9moSR2lOrsHHvr4=
github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk= github.com/ilyakaznacheev/cleanenv v1.5.0/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=