mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 04:52:26 +00:00
Implement automatic update functionality and improve server initialization; add NODE_PATH to Makefile, enhance logging, and update README
This commit is contained in:
6
Makefile
6
Makefile
@@ -2,6 +2,8 @@ APP_NAME := node
|
||||
BIN_DIR := bin
|
||||
GOPATH := $(shell go env GOPATH)
|
||||
export CONFIG_PATH := ./config.yaml
|
||||
export NODE_PATH := $(shell pwd)
|
||||
|
||||
LDFLAGS := -X 'github.com/akyaiy/GoSally-mvp/core/config.NodeVersion=v0.0.1-dev'
|
||||
CGO_CFLAGS := -I/usr/local/include
|
||||
CGO_LDFLAGS := -L/usr/local/lib -llua5.1 -lm -ldl
|
||||
@@ -38,6 +40,10 @@ runq: build
|
||||
@echo "Running!"
|
||||
./$(BIN_DIR)/$(APP_NAME) | jq
|
||||
|
||||
pure-run:
|
||||
@echo "Running!"
|
||||
./$(BIN_DIR)/$(APP_NAME) | jq
|
||||
|
||||
test:
|
||||
@go test ./... | grep -v '^?' || true
|
||||
|
||||
|
||||
@@ -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.
|
||||
|
||||
### 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
|
||||
The basic directory tree looks something like this
|
||||
|
||||
@@ -1,19 +1,21 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log/slog"
|
||||
"net"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"time"
|
||||
|
||||
"golang.org/x/net/netutil"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/config"
|
||||
gs "github.com/akyaiy/GoSally-mvp/core/general_server"
|
||||
_ "github.com/akyaiy/GoSally-mvp/core/init"
|
||||
"github.com/akyaiy/GoSally-mvp/core/logs"
|
||||
"github.com/akyaiy/GoSally-mvp/core/sv1"
|
||||
"github.com/akyaiy/GoSally-mvp/core/update"
|
||||
"github.com/go-chi/cors"
|
||||
|
||||
"github.com/go-chi/chi/v5"
|
||||
)
|
||||
@@ -27,20 +29,37 @@ func init() {
|
||||
log = logs.SetupLogger(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")
|
||||
}
|
||||
|
||||
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() {
|
||||
updater := update.NewUpdater(*log, cfg)
|
||||
versuion, versionType, _ := updater.GetCurrentVersion()
|
||||
fmt.Printf("Current version: %s (%s)\n", versuion, versionType)
|
||||
ver, vert, _ := updater.GetLatestVersion(versionType)
|
||||
fmt.Printf("Latest version: %s (%s)\n", ver, vert)
|
||||
go UpdateDaemon(updater, *cfg)
|
||||
|
||||
fmt.Println("Checking for updates...")
|
||||
isNewUpdate, _ := updater.CkeckUpdates()
|
||||
fmt.Println("Update check result:", isNewUpdate)
|
||||
serverv1 := sv1.InitV1Server(&sv1.HandlerV1InitStruct{
|
||||
Log: *log,
|
||||
Config: cfg,
|
||||
@@ -54,6 +73,13 @@ func main() {
|
||||
}, serverv1)
|
||||
|
||||
r := chi.NewRouter()
|
||||
r.Use(cors.Handler(cors.Options{
|
||||
AllowedOrigins: []string{"*"},
|
||||
AllowedMethods: []string{"GET", "POST", "OPTIONS"},
|
||||
AllowedHeaders: []string{"Accept", "Authorization", "Content-Type", "X-CSRF-Token"},
|
||||
AllowCredentials: true,
|
||||
MaxAge: 300,
|
||||
}))
|
||||
r.Route(config.GetServerConsts().GetApiRoute()+config.GetServerConsts().GetComDirRoute(), func(r chi.Router) {
|
||||
r.Get("/", s.HandleList)
|
||||
r.Get("/{cmd}", s.Handle)
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
mode: "dev"
|
||||
|
||||
http_server:
|
||||
address: "0.0.0.0:8080"
|
||||
address: "192.168.1.176:8080"
|
||||
timeout: 3s
|
||||
idle_timeout: 30s
|
||||
api:
|
||||
@@ -23,10 +23,5 @@ com_dir: "com/"
|
||||
|
||||
updates:
|
||||
enabled: true
|
||||
allow-auto-updates: true
|
||||
allow-updates: true
|
||||
allow-downgrades: false
|
||||
|
||||
check-interval: 1h
|
||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||
wanted-version: "latest-stable"
|
||||
repository_url: "https://repo.serve.lv/raw/go-sally"
|
||||
@@ -43,18 +43,16 @@ type Internal struct {
|
||||
}
|
||||
|
||||
type Updates struct {
|
||||
UpdatesEnabled bool `yaml:"enabled" env-default:"false"`
|
||||
AllowAutoUpdates bool `yaml:"allow_auto_updates" env-default:"false"`
|
||||
AllowUpdates bool `yaml:"allow_updates" env-default:"false"`
|
||||
AllowDowngrades bool `yaml:"allow_downgrades" env-default:"false"`
|
||||
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"`
|
||||
UpdatesEnabled bool `yaml:"enabled" env-default:"false"`
|
||||
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
|
||||
type ConfigEnv struct {
|
||||
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.
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
package config
|
||||
|
||||
import "os"
|
||||
|
||||
// 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
|
||||
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
|
||||
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 _serverConsts struct{}
|
||||
type _updateConsts struct{}
|
||||
@@ -28,7 +36,9 @@ func (_ _updateConsts) GetNodeVersion() string {
|
||||
}
|
||||
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 (_ _internalConsts) GetUUIDLength() byte { return UUIDLength }
|
||||
|
||||
45
core/init/init.go
Normal file
45
core/init/init.go
Normal 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)
|
||||
}
|
||||
}
|
||||
@@ -13,46 +13,46 @@ import (
|
||||
)
|
||||
|
||||
// HandlerV1 is the main handler for version 1 of the API.
|
||||
// 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
|
||||
func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
uuid16, err := utils.NewUUID()
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
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
|
||||
}
|
||||
log := h.log.With(
|
||||
slog.Group("request",
|
||||
slog.String("version", h.GetVersion()),
|
||||
slog.String("url", h.r.URL.String()),
|
||||
slog.String("method", h.r.Method),
|
||||
slog.String("url", r.URL.String()),
|
||||
slog.String("method", r.Method),
|
||||
),
|
||||
slog.Group("connection",
|
||||
slog.String("connection-uuid", uuid16),
|
||||
slog.String("remote", h.r.RemoteAddr),
|
||||
slog.String("remote", r.RemoteAddr),
|
||||
),
|
||||
)
|
||||
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) {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "invalid command"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid command")
|
||||
utils.WriteJSONError(w, http.StatusBadRequest, "invalid command")
|
||||
return
|
||||
}
|
||||
|
||||
scriptPath := h.comMatch(chi.URLParam(h.r, "ver"), cmd)
|
||||
scriptPath := h.comMatch(chi.URLParam(r, "ver"), cmd)
|
||||
if scriptPath == "" {
|
||||
log.Error("HTTP request error",
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
utils.WriteJSONError(h.w, http.StatusNotFound, "command not found")
|
||||
utils.WriteJSONError(w, http.StatusNotFound, "command not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
slog.String("error", "command not found"),
|
||||
slog.String("cmd", cmd),
|
||||
slog.Int("status", http.StatusNotFound))
|
||||
utils.WriteJSONError(h.w, http.StatusNotFound, "command not found")
|
||||
utils.WriteJSONError(w, http.StatusNotFound, "command not found")
|
||||
return
|
||||
}
|
||||
|
||||
@@ -70,7 +70,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
defer L.Close()
|
||||
|
||||
paramsTable := L.NewTable()
|
||||
qt := h.r.URL.Query()
|
||||
qt := r.URL.Query()
|
||||
for k, v := range qt {
|
||||
if len(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 {
|
||||
log.Error("Failed to prepare lua environment",
|
||||
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
|
||||
}
|
||||
} else {
|
||||
@@ -101,7 +101,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
if err := L.DoFile(scriptPath); err != nil {
|
||||
log.Error("Failed to execute lua script",
|
||||
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
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
tbl, ok := lv.(*lua.LTable)
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -117,7 +117,7 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
resultTbl, ok := resultVal.(*lua.LTable)
|
||||
if !ok {
|
||||
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
|
||||
}
|
||||
|
||||
@@ -126,8 +126,8 @@ func (h *HandlerV1) Handle(w http.ResponseWriter, r *http.Request) {
|
||||
out[key.String()] = utils.ConvertLuaTypesToGolang(value)
|
||||
})
|
||||
|
||||
h.w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(h.w).Encode(out); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(out); err != nil {
|
||||
log.Error("Failed to encode JSON response",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
@@ -18,18 +18,18 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
if err != nil {
|
||||
h.log.Error("Failed to generate UUID",
|
||||
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
|
||||
}
|
||||
log := h.log.With(
|
||||
slog.Group("request",
|
||||
slog.String("version", h.GetVersion()),
|
||||
slog.String("url", h.r.URL.String()),
|
||||
slog.String("method", h.r.Method),
|
||||
slog.String("url", r.URL.String()),
|
||||
slog.String("method", r.Method),
|
||||
),
|
||||
slog.Group("connection",
|
||||
slog.String("connection-uuid", uuid16),
|
||||
slog.String("remote", h.r.RemoteAddr),
|
||||
slog.String("remote", r.RemoteAddr),
|
||||
),
|
||||
)
|
||||
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 {
|
||||
log.Error("Failed to read commands directory",
|
||||
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
|
||||
}
|
||||
|
||||
apiVer := chi.URLParam(h.r, "ver")
|
||||
apiVer := chi.URLParam(r, "ver")
|
||||
|
||||
// Сначала ищем версионные
|
||||
for _, file := range files {
|
||||
@@ -110,8 +110,8 @@ func (h *HandlerV1) HandleList(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
log.Info("Session completed")
|
||||
|
||||
h.w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(h.w).Encode(commands); err != nil {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(commands); err != nil {
|
||||
h.log.Error("Failed to write JSON error response",
|
||||
slog.String("error", err.Error()))
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ package sv1
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"regexp"
|
||||
|
||||
"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.
|
||||
type HandlerV1 struct {
|
||||
w http.ResponseWriter
|
||||
r *http.Request
|
||||
|
||||
log slog.Logger
|
||||
|
||||
cfg *config.ConfigConf
|
||||
|
||||
@@ -2,21 +2,18 @@ package sv1
|
||||
|
||||
import (
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"regexp"
|
||||
|
||||
"github.com/akyaiy/GoSally-mvp/core/utils"
|
||||
)
|
||||
|
||||
func (h *HandlerV1) errNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid request")
|
||||
h.log.Error("HTTP request error",
|
||||
slog.String("remote", h.r.RemoteAddr),
|
||||
slog.String("method", h.r.Method),
|
||||
slog.String("url", h.r.URL.String()),
|
||||
slog.Int("status", http.StatusBadRequest))
|
||||
}
|
||||
// func (h *HandlerV1) errNotFound(w http.ResponseWriter, r *http.Request) {
|
||||
// utils.WriteJSONError(h.w, http.StatusBadRequest, "invalid request")
|
||||
// h.log.Error("HTTP request error",
|
||||
// slog.String("remote", h.r.RemoteAddr),
|
||||
// slog.String("method", h.r.Method),
|
||||
// slog.String("url", h.r.URL.String()),
|
||||
// slog.Int("status", http.StatusBadRequest))
|
||||
// }
|
||||
|
||||
func (h *HandlerV1) extractDescriptionStatic(path string) (string, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
package update
|
||||
|
||||
import (
|
||||
"archive/tar"
|
||||
"compress/gzip"
|
||||
"errors"
|
||||
"io"
|
||||
"log/slog"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
@@ -187,15 +192,133 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, error) {
|
||||
if currentVersion == latestVersion && currentBranch == latestBranch {
|
||||
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
|
||||
}
|
||||
|
||||
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 {
|
||||
// if !(u.Config.UpdatesEnabled && u.Config.Updates.AllowUpdates && u.Config.Updates.AllowDowngrades) {
|
||||
// u.Log.Info("Updates are disabled in config, skipping update")
|
||||
|
||||
1
go.mod
1
go.mod
@@ -11,6 +11,7 @@ require (
|
||||
|
||||
require (
|
||||
github.com/BurntSushi/toml v1.5.0 // indirect
|
||||
github.com/go-chi/cors v1.2.2
|
||||
github.com/joho/godotenv v1.5.1 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
olympos.io/encoding/edn v0.0.0-20201019073823-d3554ca0b0a3 // indirect
|
||||
|
||||
2
go.sum
2
go.sum
@@ -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/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/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/go.mod h1:a5aDzaJrLCQZsazHol1w8InnDcOX0OColm64SlIi6gk=
|
||||
github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0=
|
||||
|
||||
Reference in New Issue
Block a user