| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363 | package upgraderimport (	"encoding/json"	"fmt"	_github "github.com/0xJacky/Nginx-UI/.github"	"github.com/0xJacky/Nginx-UI/app"	helper2 "github.com/0xJacky/Nginx-UI/internal/helper"	"github.com/0xJacky/Nginx-UI/internal/logger"	"github.com/0xJacky/Nginx-UI/settings"	"github.com/pkg/errors"	"io"	"net/http"	"net/url"	"os"	"path/filepath"	"runtime"	"strconv"	"strings"	"time")const (	GithubLatestReleaseAPI = "https://api.github.com/repos/0xJacky/nginx-ui/releases/latest"	GithubReleasesListAPI  = "https://api.github.com/repos/0xJacky/nginx-ui/releases")type RuntimeInfo struct {	OS     string `json:"os"`	Arch   string `json:"arch"`	ExPath string `json:"ex_path"`}func GetRuntimeInfo() (r RuntimeInfo, err error) {	ex, err := os.Executable()	if err != nil {		err = errors.Wrap(err, "service.GetRuntimeInfo os.Executable() err")		return	}	realPath, err := filepath.EvalSymlinks(ex)	if err != nil {		err = errors.Wrap(err, "service.GetRuntimeInfo filepath.EvalSymlinks() err")		return	}	r = RuntimeInfo{		OS:     runtime.GOOS,		Arch:   runtime.GOARCH,		ExPath: realPath,	}	return}type TReleaseAsset struct {	Name               string `json:"name"`	BrowserDownloadUrl string `json:"browser_download_url"`	Size               uint   `json:"size"`}type TRelease struct {	TagName     string          `json:"tag_name"`	Name        string          `json:"name"`	PublishedAt time.Time       `json:"published_at"`	Body        string          `json:"body"`	Prerelease  bool            `json:"prerelease"`	Assets      []TReleaseAsset `json:"assets"`}func (t *TRelease) GetAssetsMap() (m map[string]TReleaseAsset) {	m = make(map[string]TReleaseAsset)	for _, v := range t.Assets {		m[v.Name] = v	}	return}func getLatestRelease() (data TRelease, err error) {	resp, err := http.Get(GithubLatestReleaseAPI)	if err != nil {		err = errors.Wrap(err, "service.getLatestRelease http.Get err")		return	}	body, err := io.ReadAll(resp.Body)	if err != nil {		err = errors.Wrap(err, "service.getLatestRelease io.ReadAll err")		return	}	defer resp.Body.Close()	if resp.StatusCode != 200 {		err = errors.New(string(body))		return	}	err = json.Unmarshal(body, &data)	if err != nil {		err = errors.Wrap(err, "service.getLatestRelease json.Unmarshal err")		return	}	return}func getLatestPrerelease() (data TRelease, err error) {	resp, err := http.Get(GithubReleasesListAPI)	if err != nil {		err = errors.Wrap(err, "service.getLatestPrerelease http.Get err")		return	}	body, err := io.ReadAll(resp.Body)	if err != nil {		err = errors.Wrap(err, "service.getLatestPrerelease io.ReadAll err")		return	}	defer resp.Body.Close()	if resp.StatusCode != 200 {		err = errors.New(string(body))		return	}	var releaseList []TRelease	err = json.Unmarshal(body, &releaseList)	if err != nil {		err = errors.Wrap(err, "service.getLatestPrerelease json.Unmarshal err")		return	}	latestDate := time.Time{}	for _, release := range releaseList {		if release.Prerelease && release.PublishedAt.After(latestDate) {			data = release			latestDate = release.PublishedAt		}	}	return}func GetRelease(channel string) (data TRelease, err error) {	switch channel {	default:		fallthrough	case "stable":		return getLatestRelease()	case "prerelease":		return getLatestPrerelease()	}}type CurVersion struct {	Version    string `json:"version"`	BuildID    int    `json:"build_id"`	TotalBuild int    `json:"total_build"`}func GetCurrentVersion() (c CurVersion, err error) {	verJson, err := app.DistFS.ReadFile("dist/version.json")	if err != nil {		err = errors.Wrap(err, "service.GetCurrentVersion ReadFile err")		return	}	err = json.Unmarshal(verJson, &c)	if err != nil {		err = errors.Wrap(err, "service.GetCurrentVersion json.Unmarshal err")		return	}	return}type Upgrader struct {	Release TRelease	RuntimeInfo}func NewUpgrader(channel string) (u *Upgrader, err error) {	data, err := GetRelease(channel)	if err != nil {		return	}	runtimeInfo, err := GetRuntimeInfo()	if err != nil {		return	}	u = &Upgrader{		Release:     data,		RuntimeInfo: runtimeInfo,	}	return}type ProgressWriter struct {	io.Writer	totalSize    int64	currentSize  int64	progressChan chan<- float64}func (pw *ProgressWriter) Write(p []byte) (int, error) {	n, err := pw.Writer.Write(p)	pw.currentSize += int64(n)	progress := float64(pw.currentSize) / float64(pw.totalSize) * 100	pw.progressChan <- progress	return n, err}func downloadRelease(url string, dir string, progressChan chan float64) (tarName string, err error) {	client := &http.Client{}	req, err := http.NewRequest("GET", url, nil)	if err != nil {		return	}	resp, err := client.Do(req)	if err != nil {		return	}	defer resp.Body.Close()	totalSize, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)	if err != nil {		return	}	file, err := os.CreateTemp(dir, "nginx-ui-temp-*.tar.gz")	if err != nil {		err = errors.Wrap(err, "service.DownloadLatestRelease CreateTemp error")		return	}	defer file.Close()	progressWriter := &ProgressWriter{Writer: file, totalSize: totalSize, progressChan: progressChan}	multiWriter := io.MultiWriter(progressWriter)	_, err = io.Copy(multiWriter, resp.Body)	close(progressChan)	tarName = file.Name()	return}func (u *Upgrader) DownloadLatestRelease(progressChan chan float64) (tarName string, err error) {	bytes, err := _github.DistFS.ReadFile("build/build_info.json")	if err != nil {		err = errors.Wrap(err, "service.DownloadLatestRelease Read build_info.json error")		return	}	type buildArch struct {		Arch string `json:"arch"`		Name string `json:"name"`	}	var buildJson map[string]map[string]buildArch	_ = json.Unmarshal(bytes, &buildJson)	build, ok := buildJson[u.OS]	if !ok {		err = errors.Wrap(err, "os not support upgrade")		return	}	arch, ok := build[u.Arch]	if !ok {		err = errors.Wrap(err, "arch not support upgrade")		return	}	assetsMap := u.Release.GetAssetsMap()	// asset	asset, ok := assetsMap[fmt.Sprintf("nginx-ui-%s.tar.gz", arch.Name)]	if !ok {		err = errors.Wrap(err, "upgrader core asset is empty")		return	}	downloadUrl := asset.BrowserDownloadUrl	if downloadUrl == "" {		err = errors.New("upgrader core downloadUrl is empty")		return	}	// digest	digest, ok := assetsMap[fmt.Sprintf("nginx-ui-%s.tar.gz.digest", arch.Name)]	if !ok || digest.BrowserDownloadUrl == "" {		err = errors.New("upgrader core digest is empty")		return	}	if settings.ServerSettings.GithubProxy != "" {		digest.BrowserDownloadUrl, err = url.JoinPath(settings.ServerSettings.GithubProxy, digest.BrowserDownloadUrl)		if err != nil {			err = errors.Wrap(err, "service.DownloadLatestRelease url.JoinPath error")			return		}	}	resp, err := http.Get(digest.BrowserDownloadUrl)	if err != nil {		err = errors.Wrap(err, "upgrader core download digest fail")		return	}	defer resp.Body.Close()	dir := filepath.Dir(u.ExPath)	if settings.ServerSettings.GithubProxy != "" {		downloadUrl, err = url.JoinPath(settings.ServerSettings.GithubProxy, downloadUrl)		if err != nil {			err = errors.Wrap(err, "service.DownloadLatestRelease url.JoinPath error")			return		}	}	tarName, err = downloadRelease(downloadUrl, dir, progressChan)	if err != nil {		err = errors.Wrap(err, "service.DownloadLatestRelease downloadFile error")		return	}	// check tar digest	digestFileBytes, err := io.ReadAll(resp.Body)	if err != nil {		err = errors.Wrap(err, "digestFileContent read error")		return	}	digestFileContent := strings.TrimSpace(string(digestFileBytes))	logger.Debug("DownloadLatestRelease tar digest", helper2.DigestSHA512(tarName))	logger.Debug("DownloadLatestRelease digestFileContent", digestFileContent)	if digestFileContent != helper2.DigestSHA512(tarName) {		err = errors.Wrap(err, "digest not equal")		return	}	return}func (u *Upgrader) PerformCoreUpgrade(exPath string, tarPath string) (err error) {	dir := filepath.Dir(exPath)	err = helper2.UnTar(dir, tarPath)	if err != nil {		err = errors.Wrap(err, "PerformCoreUpgrade unTar error")		return	}	err = os.Rename(filepath.Join(dir, "nginx-ui"), exPath)	if err != nil {		err = errors.Wrap(err, "PerformCoreUpgrade rename error")		return	}	err = os.Remove(tarPath)	if err != nil {		err = errors.Wrap(err, "PerformCoreUpgrade remove tar error")		return	}	return}
 |