123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355 |
- package upgrader
- import (
- "encoding/json"
- "fmt"
- _github "github.com/0xJacky/Nginx-UI/.github"
- "github.com/0xJacky/Nginx-UI/frontend"
- "github.com/0xJacky/Nginx-UI/server/internal/helper"
- "github.com/0xJacky/Nginx-UI/server/internal/logger"
- "github.com/0xJacky/Nginx-UI/server/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 := frontend.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
- }
- 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", helper.DigestSHA512(tarName))
- logger.Debug("DownloadLatestRelease digestFileContent", digestFileContent)
- if digestFileContent != helper.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 = helper.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
- }
|