mirror of
https://github.com/0xJacky/nginx-ui.git
synced 2025-05-11 02:15:48 +02:00
363 lines
8.3 KiB
Go
363 lines
8.3 KiB
Go
package upgrader
|
|
|
|
import (
|
|
"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
|
|
}
|