mirror of
https://github.com/akyaiy/GoSally-mvp.git
synced 2026-01-05 13:52:26 +00:00
Fixing updates
This commit is contained in:
@@ -6,16 +6,18 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"log/slog"
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strconv"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"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 (
|
||||
@@ -36,14 +38,20 @@ type UpdaterContract interface {
|
||||
}
|
||||
|
||||
type Updater struct {
|
||||
Log slog.Logger
|
||||
Config *config.Conf
|
||||
log *log.Logger
|
||||
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{
|
||||
Log: log,
|
||||
Config: cfg,
|
||||
log: log,
|
||||
config: cfg,
|
||||
env: env,
|
||||
ctx: ctx,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,11 +88,11 @@ func isVersionNewer(current, latest Version) bool {
|
||||
if i < len(currentParts) {
|
||||
cur, err := strconv.Atoi(currentParts[i])
|
||||
if err != nil {
|
||||
cur = 0 // или можно обработать ошибку иначе
|
||||
cur = 0
|
||||
}
|
||||
curPart = cur
|
||||
} else {
|
||||
curPart = 0 // Если части в current меньше, считаем недостающие нулями
|
||||
curPart = 0
|
||||
}
|
||||
|
||||
if i < len(latestParts) {
|
||||
@@ -103,31 +111,15 @@ func isVersionNewer(current, latest Version) bool {
|
||||
if curPart > latPart {
|
||||
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.
|
||||
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 {
|
||||
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
|
||||
}
|
||||
switch branch {
|
||||
@@ -139,28 +131,28 @@ func (u *Updater) GetCurrentVersion() (Version, Branch, error) {
|
||||
}
|
||||
|
||||
func (u *Updater) GetLatestVersion(updateBranch Branch) (Version, Branch, error) {
|
||||
repoURL := u.Config.Updates.RepositoryURL
|
||||
repoURL := u.config.Updates.RepositoryURL
|
||||
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")
|
||||
}
|
||||
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://")
|
||||
}
|
||||
response, err := http.Get(repoURL + "/" + config.GetUpdateConsts().GetActualFileName())
|
||||
response, err := http.Get(repoURL + "/" + config.ActualFileName)
|
||||
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
|
||||
}
|
||||
defer response.Body.Close()
|
||||
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))
|
||||
}
|
||||
data, err := io.ReadAll(response.Body)
|
||||
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
|
||||
}
|
||||
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))
|
||||
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
|
||||
}
|
||||
if branch == updateBranch {
|
||||
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))
|
||||
}
|
||||
|
||||
@@ -198,146 +189,145 @@ func (u *Updater) CkeckUpdates() (IsNewUpdate, 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")
|
||||
}
|
||||
downloadPath, err := os.MkdirTemp("", "*-gosally-update")
|
||||
if err != nil {
|
||||
return errors.New("failed to create temp dir " + err.Error())
|
||||
|
||||
if err := run_manager.SetDir("update"); err != nil {
|
||||
return fmt.Errorf("failed to create update dir: %w", err)
|
||||
}
|
||||
|
||||
downloadPath := filepath.Join(run_manager.RuntimeDir(), "update")
|
||||
|
||||
_, currentBranch, err := u.GetCurrentVersion()
|
||||
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)
|
||||
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)
|
||||
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()
|
||||
|
||||
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))
|
||||
return fmt.Errorf("unexpected HTTP status: %s, body: %s", resp.Status, body)
|
||||
}
|
||||
|
||||
gzReader, err := gzip.NewReader(resp.Body)
|
||||
if err != nil {
|
||||
return errors.New("failed to create gzip reader: " + err.Error())
|
||||
return fmt.Errorf("gzip reader error: %w", err)
|
||||
}
|
||||
defer gzReader.Close()
|
||||
|
||||
tarReader := tar.NewReader(gzReader)
|
||||
for {
|
||||
header, err := tarReader.Next()
|
||||
if err == io.EOF {
|
||||
break // archive is fully read
|
||||
break
|
||||
}
|
||||
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 {
|
||||
case tar.TypeDir:
|
||||
// Создаём директорию
|
||||
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:
|
||||
// Создаём директорию, если её ещё нет
|
||||
if err := os.MkdirAll(filepath.Dir(targetPath), 0755); err != nil {
|
||||
return errors.New("failed to create directory for file: " + err.Error())
|
||||
if err := run_manager.Set(filepath.Join("update", cleanName)); err != nil {
|
||||
return fmt.Errorf("set file error: %w", err)
|
||||
}
|
||||
// Создаём файл
|
||||
outFile, err := os.Create(targetPath)
|
||||
f := run_manager.File(filepath.Join("update", cleanName))
|
||||
outFile, err := f.Open()
|
||||
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 {
|
||||
outFile.Close()
|
||||
return errors.New("failed to copy file content: " + err.Error())
|
||||
return fmt.Errorf("copy file error: %w", err)
|
||||
}
|
||||
outFile.Close()
|
||||
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"))
|
||||
|
||||
return u.InstallAndRestart()
|
||||
}
|
||||
|
||||
func (u *Updater) InstallAndRestart(newBinaryPath string) error {
|
||||
nodePath := os.Getenv("NODE_PATH")
|
||||
func (u *Updater) InstallAndRestart() error {
|
||||
|
||||
nodePath := u.env.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")
|
||||
targetPath := filepath.Join(installDir, "node")
|
||||
|
||||
// Копируем новый бинарник
|
||||
input, err := os.Open(newBinaryPath)
|
||||
f := run_manager.File("update/node")
|
||||
input, err := f.Open()
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("cannot open new binary: %w", err)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
output, err := os.Create(targetPath)
|
||||
if err != nil {
|
||||
return err
|
||||
return fmt.Errorf("cannot create target binary: %w", 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()
|
||||
|
||||
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()
|
||||
return fmt.Errorf("copy failed: %w", err)
|
||||
}
|
||||
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 := os.Chmod(targetPath, 0755); err != nil {
|
||||
return fmt.Errorf("failed to chmod: %w", err)
|
||||
}
|
||||
u.Log.Info("Shutting down")
|
||||
os.Exit(0)
|
||||
return errors.New("failed to shutdown the process")
|
||||
|
||||
u.log.Printf("Launching new version: path is %s", targetPath)
|
||||
// cmd := exec.Command(targetPath, os.Args[1:]...)
|
||||
// cmd.Env = os.Environ()
|
||||
// cmd.Stdout = os.Stdout
|
||||
// cmd.Stderr = os.Stderr
|
||||
// cmd.Stdin = os.Stdin
|
||||
args := os.Args
|
||||
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 {
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
|
||||
// return cmd.Start()
|
||||
}
|
||||
|
||||
// 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")
|
||||
// return nil
|
||||
// }
|
||||
// wantedVersion := u.Config.Updates.WantedVersion
|
||||
// _, wantedBranch, _ := splitVersionString(wantedVersion)
|
||||
// newVersion, newBranch, err := u.GetLatestVersion(wantedBranch)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// 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)),
|
||||
// )
|
||||
// }
|
||||
// }
|
||||
func (u *Updater) Shutdownfunc(f context.CancelFunc) {
|
||||
u.cancel = f
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user