upgrade.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251
  1. package service
  2. import (
  3. "encoding/json"
  4. "fmt"
  5. _github "github.com/0xJacky/Nginx-UI/.github"
  6. "github.com/0xJacky/Nginx-UI/frontend"
  7. "github.com/0xJacky/Nginx-UI/server/pkg/helper"
  8. "github.com/0xJacky/Nginx-UI/server/settings"
  9. "github.com/pkg/errors"
  10. "io"
  11. "log"
  12. "net/http"
  13. "net/url"
  14. "os"
  15. "path/filepath"
  16. "runtime"
  17. "strconv"
  18. "time"
  19. )
  20. const GithubLatestReleaseAPI = "https://api.github.com/repos/0xJacky/nginx-ui/releases/latest"
  21. type RuntimeInfo struct {
  22. OS string `json:"os"`
  23. Arch string `json:"arch"`
  24. ExPath string `json:"ex_path"`
  25. }
  26. func GetRuntimeInfo() (r RuntimeInfo, err error) {
  27. ex, err := os.Executable()
  28. if err != nil {
  29. err = errors.Wrap(err, "service.GetRuntimeInfo os.Executable() err")
  30. return
  31. }
  32. realPath, err := filepath.EvalSymlinks(ex)
  33. if err != nil {
  34. err = errors.Wrap(err, "service.GetRuntimeInfo filepath.EvalSymlinks() err")
  35. return
  36. }
  37. r = RuntimeInfo{
  38. OS: runtime.GOOS,
  39. Arch: runtime.GOARCH,
  40. ExPath: realPath,
  41. }
  42. return
  43. }
  44. type TReleaseAsset struct {
  45. Name string `json:"name"`
  46. BrowserDownloadUrl string `json:"browser_download_url"`
  47. Size uint `json:"size"`
  48. }
  49. type TRelease struct {
  50. TagName string `json:"tag_name"`
  51. Name string `json:"name"`
  52. PublishedAt time.Time `json:"published_at"`
  53. Body string `json:"body"`
  54. Assets []TReleaseAsset `json:"assets"`
  55. RuntimeInfo
  56. }
  57. func GetRelease() (data TRelease, err error) {
  58. resp, err := http.Get(GithubLatestReleaseAPI)
  59. if err != nil {
  60. err = errors.Wrap(err, "service.GetReleaseList http.Get err")
  61. return
  62. }
  63. body, err := io.ReadAll(resp.Body)
  64. if err != nil {
  65. err = errors.Wrap(err, "service.GetReleaseList io.ReadAll err")
  66. return
  67. }
  68. defer resp.Body.Close()
  69. if resp.StatusCode != 200 {
  70. err = errors.New(string(body))
  71. log.Println(string(body))
  72. return
  73. }
  74. err = json.Unmarshal(body, &data)
  75. if err != nil {
  76. err = errors.Wrap(err, "service.GetReleaseList json.Unmarshal err")
  77. return
  78. }
  79. data.RuntimeInfo, err = GetRuntimeInfo()
  80. return
  81. }
  82. type CurVersion struct {
  83. Version string `json:"version"`
  84. BuildID int `json:"build_id"`
  85. TotalBuild int `json:"total_build"`
  86. }
  87. func GetCurrentVersion() (c CurVersion, err error) {
  88. verJson, err := frontend.DistFS.ReadFile("dist/version.json")
  89. if err != nil {
  90. err = errors.Wrap(err, "service.GetCurrentVersion ReadFile err")
  91. return
  92. }
  93. err = json.Unmarshal(verJson, &c)
  94. if err != nil {
  95. err = errors.Wrap(err, "service.GetCurrentVersion json.Unmarshal err")
  96. return
  97. }
  98. return
  99. }
  100. type Upgrader struct {
  101. Release TRelease
  102. }
  103. func NewUpgrader() (u *Upgrader, err error) {
  104. data, err := GetRelease()
  105. if err != nil {
  106. return
  107. }
  108. u = &Upgrader{
  109. Release: data,
  110. }
  111. return
  112. }
  113. type ProgressWriter struct {
  114. io.Writer
  115. totalSize int64
  116. currentSize int64
  117. progressChan chan<- float64
  118. }
  119. func (pw *ProgressWriter) Write(p []byte) (int, error) {
  120. n, err := pw.Writer.Write(p)
  121. pw.currentSize += int64(n)
  122. progress := float64(pw.currentSize) / float64(pw.totalSize) * 100
  123. pw.progressChan <- progress
  124. return n, err
  125. }
  126. func downloadRelease(url string, dir string, progressChan chan float64) (tarName string, err error) {
  127. client := &http.Client{}
  128. req, err := http.NewRequest("GET", url, nil)
  129. if err != nil {
  130. return
  131. }
  132. resp, err := client.Do(req)
  133. if err != nil {
  134. return
  135. }
  136. defer resp.Body.Close()
  137. totalSize, err := strconv.ParseInt(resp.Header.Get("Content-Length"), 10, 64)
  138. if err != nil {
  139. return
  140. }
  141. file, err := os.CreateTemp(dir, "nginx-ui-temp-*.tar.gz")
  142. if err != nil {
  143. err = errors.Wrap(err, "service.DownloadLatestRelease CreateTemp error")
  144. return
  145. }
  146. defer file.Close()
  147. progressWriter := &ProgressWriter{Writer: file, totalSize: totalSize, progressChan: progressChan}
  148. multiWriter := io.MultiWriter(progressWriter)
  149. _, err = io.Copy(multiWriter, resp.Body)
  150. close(progressChan)
  151. tarName = file.Name()
  152. return
  153. }
  154. func (u *Upgrader) DownloadLatestRelease(progressChan chan float64) (tarName string, err error) {
  155. bytes, err := _github.DistFS.ReadFile("build/build_info.json")
  156. if err != nil {
  157. err = errors.Wrap(err, "service.DownloadLatestRelease Read build_info.json error")
  158. return
  159. }
  160. type buildArch struct {
  161. Arch string `json:"arch"`
  162. Name string `json:"name"`
  163. }
  164. var buildJson map[string]map[string]buildArch
  165. _ = json.Unmarshal(bytes, &buildJson)
  166. build, ok := buildJson[u.Release.OS]
  167. if !ok {
  168. err = errors.Wrap(err, "os not support upgrade")
  169. return
  170. }
  171. arch, ok := build[u.Release.Arch]
  172. if !ok {
  173. err = errors.Wrap(err, "arch not support upgrade")
  174. return
  175. }
  176. var downloadUrl string
  177. for _, v := range u.Release.Assets {
  178. if fmt.Sprintf("nginx-ui-%s.tar.gz", arch.Name) == v.Name {
  179. downloadUrl = v.BrowserDownloadUrl
  180. break
  181. }
  182. }
  183. if downloadUrl == "" {
  184. err = errors.Wrap(err, "Nginx UI core downloadUrl is empty")
  185. return
  186. }
  187. dir := filepath.Dir(u.Release.ExPath)
  188. if settings.ServerSettings.GithubProxy != "" {
  189. downloadUrl, err = url.JoinPath(settings.ServerSettings.GithubProxy, downloadUrl)
  190. if err != nil {
  191. err = errors.Wrap(err, "service.DownloadLatestRelease url.JoinPath error")
  192. return
  193. }
  194. }
  195. tarName, err = downloadRelease(downloadUrl, dir, progressChan)
  196. if err != nil {
  197. err = errors.Wrap(err, "service.DownloadLatestRelease downloadFile error")
  198. return
  199. }
  200. return
  201. }
  202. func (u *Upgrader) PerformCoreUpgrade(exPath string, tarPath string) (err error) {
  203. dir := filepath.Dir(exPath)
  204. err = helper.UnTar(dir, tarPath)
  205. if err != nil {
  206. err = errors.Wrap(err, "PerformCoreUpgrade unTar error")
  207. return
  208. }
  209. err = os.Rename(filepath.Join(dir, "nginx-ui"), exPath)
  210. if err != nil {
  211. err = errors.Wrap(err, "PerformCoreUpgrade rename error")
  212. return
  213. }
  214. err = os.Remove(tarPath)
  215. if err != nil {
  216. err = errors.Wrap(err, "PerformCoreUpgrade remove tar error")
  217. return
  218. }
  219. return
  220. }