mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-03 19:52:25 +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
|
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
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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"
|
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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()))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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)
|
||||||
|
|||||||
@@ -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
1
go.mod
@@ -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
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/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=
|
||||||
|
|||||||
Reference in New Issue
Block a user