Browse Source

enhance(upgrader): gracefully replace the old exe with the new exe

Jacky 9 months ago
parent
commit
f5a0a9ed50

+ 1 - 2
api/system/upgrade.go

@@ -139,9 +139,8 @@ func PerformCoreUpgrade(c *gin.Context) {
 		return
 	}
 
-	_ = os.Remove(u.ExPath)
 	// bye, overseer will restart nginx-ui
-	err = u.PerformCoreUpgrade(u.ExPath, tarName)
+	err = u.PerformCoreUpgrade(tarName)
 	if err != nil {
 		_ = ws.WriteJSON(CoreUpgradeResp{
 			Status:  UpgradeStatusError,

+ 0 - 1
app/src/components/ChatGPT/ChatGPT.vue

@@ -143,7 +143,6 @@ async function send() {
     messages.value = []
 
   if (messages.value.length === 0) {
-    console.log(current.value)
     messages.value.push({
       role: 'user',
       content: `${props.content}\n\nCurrent Language Code: ${current.value}`,

+ 1 - 1
app/src/version.json

@@ -1 +1 @@
-{"version":"2.0.0-beta.26","build_id":142,"total_build":346}
+{"version":"2.0.0-beta.26","build_id":144,"total_build":348}

+ 5 - 0
app/src/views/environment/Environment.vue

@@ -30,6 +30,11 @@ const columns: Column[] = [{
     },
   },
 },
+{
+  title: () => $gettext('Version'),
+  dataIndex: 'version',
+  pithy: true,
+},
 {
   title: () => 'NodeSecret',
   dataIndex: 'token',

+ 6 - 1
app/src/views/system/Upgrade.vue

@@ -95,7 +95,6 @@ async function perform_upgrade() {
     const r = JSON.parse(m.data)
     if (r.message)
       log(r.message)
-    console.log(r.status)
     switch (r.status) {
       case 'info':
         progressPercent.value += 10
@@ -115,6 +114,12 @@ async function perform_upgrade() {
     }
   }
 
+  ws.onerror = () => {
+    is_fail = true
+    progressStatus.value = 'exception'
+    modalClosable.value = true
+  }
+
   ws.onclose = async () => {
     if (is_fail)
       return

+ 1 - 1
app/version.json

@@ -1 +1 @@
-{"version":"2.0.0-beta.26","build_id":142,"total_build":346}
+{"version":"2.0.0-beta.26","build_id":144,"total_build":348}

+ 2 - 0
go.mod

@@ -42,6 +42,7 @@ require (
 )
 
 require (
+	aead.dev/minisign v0.3.0 // indirect
 	cloud.google.com/go/auth v0.7.1 // indirect
 	cloud.google.com/go/auth/oauth2adapt v0.2.3 // indirect
 	cloud.google.com/go/compute/metadata v0.5.0 // indirect
@@ -183,6 +184,7 @@ require (
 	github.com/microcosm-cc/bluemonday v1.0.27 // indirect
 	github.com/miekg/dns v1.1.61 // indirect
 	github.com/mimuret/golang-iij-dpf v0.9.1 // indirect
+	github.com/minio/selfupdate v0.6.0 // indirect
 	github.com/mitchellh/go-homedir v1.1.0 // indirect
 	github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
 	github.com/modern-go/reflect2 v1.0.2 // indirect

+ 9 - 0
go.sum

@@ -1,3 +1,6 @@
+aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ=
+aead.dev/minisign v0.3.0 h1:8Xafzy5PEVZqYDNP60yJHARlW1eOQtsKNp/Ph2c0vRA=
+aead.dev/minisign v0.3.0/go.mod h1:NLvG3Uoq3skkRMDuc3YHpWUTMTrSExqm+Ij73W13F6Y=
 cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw=
 cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU=
@@ -1338,6 +1341,8 @@ github.com/mimuret/golang-iij-dpf v0.9.1 h1:Gj6EhHJkOhr+q2RnvRPJsPMcjuVnWPSccEHy
 github.com/mimuret/golang-iij-dpf v0.9.1/go.mod h1:sl9KyOkESib9+KRD3HaGpgi1xk7eoN2+d96LCLsME2M=
 github.com/minio/asm2plan9s v0.0.0-20200509001527-cdd76441f9d8/go.mod h1:mC1jAcsrzbxHt8iiaC+zU4b1ylILSosueou12R++wfY=
 github.com/minio/c2goasm v0.0.0-20190812172519-36a3d3bbc4f3/go.mod h1:RagcQ7I8IeTMnF8JTXieKnO4Z6JCsikNEzj0DwauVzE=
+github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
+github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
 github.com/mitchellh/cli v1.0.0/go.mod h1:hNIlj7HEI86fIcpObd7a0FcrxTWetlwJDGcceTlRvqc=
 github.com/mitchellh/cli v1.1.0/go.mod h1:xcISNoH86gajksDmfB23e/pu+B+GeFRMYmoHXxx3xhI=
 github.com/mitchellh/go-homedir v1.0.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0=
@@ -1731,9 +1736,11 @@ golang.org/x/crypto v0.0.0-20190923035154-9ee001bba392/go.mod h1:/lpIB1dKB+9EgE3
 golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
 golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
+golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I=
 golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4=
 golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
 golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
+golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220314234659-1baeb1ce4c0b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
 golang.org/x/crypto v0.0.0-20220829220503-c86fa9a7ed90/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
@@ -1993,6 +2000,7 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210220050731-9a76102bfb43/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
+golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210303074136-134d130e1a04/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210304124612-50617c2ba197/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210305230114-8fe3ee5dd75b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@@ -2050,6 +2058,7 @@ golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
 golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI=
 golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
+golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
 golang.org/x/term v0.1.0/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=

+ 1 - 1
internal/helper/tar.go

@@ -54,7 +54,7 @@ func UnTar(dst, src string) (err error) {
 					return errors.Wrap(err, "unTar os.OpenFile error")
 				}
 				defer file.Close()
-				_, err = file.Seek(0, os.SEEK_END)
+				_, err = file.Seek(0, io.SeekEnd)
 				if err != nil {
 					return errors.Wrap(err, "unTar file.Truncate(0) error")
 				}

+ 59 - 0
internal/upgrader/info.go

@@ -0,0 +1,59 @@
+package upgrader
+
+import (
+	"encoding/json"
+	"github.com/0xJacky/Nginx-UI/app"
+	"github.com/pkg/errors"
+	"os"
+	"path/filepath"
+	"runtime"
+)
+
+type RuntimeInfo struct {
+	OS     string `json:"os"`
+	Arch   string `json:"arch"`
+	ExPath string `json:"ex_path"`
+}
+
+type CurVersion struct {
+	Version    string `json:"version"`
+	BuildID    int    `json:"build_id"`
+	TotalBuild int    `json:"total_build"`
+}
+
+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
+}
+
+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
+}

+ 109 - 0
internal/upgrader/release.go

@@ -0,0 +1,109 @@
+package upgrader
+
+import (
+	"encoding/json"
+	"github.com/pkg/errors"
+	"io"
+	"net/http"
+	"time"
+)
+
+const (
+	GithubLatestReleaseAPI = "https://api.github.com/repos/0xJacky/nginx-ui/releases/latest"
+	GithubReleasesListAPI  = "https://api.github.com/repos/0xJacky/nginx-ui/releases"
+)
+
+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()
+	}
+}

+ 45 - 161
internal/upgrader/upgrade.go

@@ -4,171 +4,21 @@ import (
 	"encoding/json"
 	"fmt"
 	_github "github.com/0xJacky/Nginx-UI/.github"
-	"github.com/0xJacky/Nginx-UI/app"
 	"github.com/0xJacky/Nginx-UI/internal/helper"
 	"github.com/0xJacky/Nginx-UI/internal/logger"
 	"github.com/0xJacky/Nginx-UI/settings"
+	"github.com/minio/selfupdate"
 	"github.com/pkg/errors"
 	"io"
 	"net/http"
 	"net/url"
 	"os"
 	"path/filepath"
-	"runtime"
 	"strconv"
 	"strings"
-	"time"
+	"sync/atomic"
 )
 
-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
@@ -350,23 +200,57 @@ func (u *Upgrader) DownloadLatestRelease(progressChan chan float64) (tarName str
 	return
 }
 
-func (u *Upgrader) PerformCoreUpgrade(exPath string, tarPath string) (err error) {
-	dir := filepath.Dir(exPath)
-	err = helper.UnTar(dir, tarPath)
+var updateInProgress atomic.Bool
+
+func (u *Upgrader) PerformCoreUpgrade(tarPath string) (err error) {
+	if !updateInProgress.CompareAndSwap(false, true) {
+		return errors.New("update already in progress")
+	}
+	defer updateInProgress.Store(false)
+
+	opts := selfupdate.Options{}
+
+	if err = opts.CheckPermissions(); err != nil {
+		return err
+	}
+
+	tempDir, err := os.MkdirTemp("", "nginx-ui-upgrade-*")
 	if err != nil {
-		err = errors.Wrap(err, "PerformCoreUpgrade unTar error")
-		return
+		return err
 	}
-	err = os.Rename(filepath.Join(dir, "nginx-ui"), exPath)
+	defer os.RemoveAll(tempDir)
+
+	err = helper.UnTar(tempDir, tarPath)
 	if err != nil {
-		err = errors.Wrap(err, "PerformCoreUpgrade rename error")
+		err = errors.Wrap(err, "PerformCoreUpgrade unTar error")
 		return
 	}
 
-	err = os.Remove(tarPath)
+	f, err := os.Open(filepath.Join(tempDir, "nginx-ui"))
 	if err != nil {
-		err = errors.Wrap(err, "PerformCoreUpgrade remove tar error")
+		err = errors.Wrap(err, "PerformCoreUpgrade open error")
 		return
 	}
+	defer f.Close()
+
+	if err = selfupdate.PrepareAndCheckBinary(f, opts); err != nil {
+		var pathErr *os.PathError
+		if errors.As(err, &pathErr) {
+			return pathErr.Err
+		}
+		return err
+	}
+
+	if err = selfupdate.CommitBinary(opts); err != nil {
+		if rerr := selfupdate.RollbackError(err); rerr != nil {
+			return rerr
+		}
+		var pathErr *os.PathError
+		if errors.As(err, &pathErr) {
+			return pathErr.Err
+		}
+		return err
+	}
+
 	return
 }

+ 65 - 65
router/routers.go

@@ -1,79 +1,79 @@
 package router
 
 import (
-    "github.com/0xJacky/Nginx-UI/api/analytic"
-    "github.com/0xJacky/Nginx-UI/api/certificate"
-    "github.com/0xJacky/Nginx-UI/api/cluster"
-    "github.com/0xJacky/Nginx-UI/api/config"
-    "github.com/0xJacky/Nginx-UI/api/nginx"
-    "github.com/0xJacky/Nginx-UI/api/notification"
-    "github.com/0xJacky/Nginx-UI/api/openai"
-    "github.com/0xJacky/Nginx-UI/api/settings"
-    "github.com/0xJacky/Nginx-UI/api/sites"
-    "github.com/0xJacky/Nginx-UI/api/streams"
-    "github.com/0xJacky/Nginx-UI/api/system"
-    "github.com/0xJacky/Nginx-UI/api/template"
-    "github.com/0xJacky/Nginx-UI/api/terminal"
-    "github.com/0xJacky/Nginx-UI/api/upstream"
-    "github.com/0xJacky/Nginx-UI/api/user"
-    "github.com/gin-contrib/static"
-    "github.com/gin-gonic/gin"
-    "net/http"
+	"github.com/0xJacky/Nginx-UI/api/analytic"
+	"github.com/0xJacky/Nginx-UI/api/certificate"
+	"github.com/0xJacky/Nginx-UI/api/cluster"
+	"github.com/0xJacky/Nginx-UI/api/config"
+	"github.com/0xJacky/Nginx-UI/api/nginx"
+	"github.com/0xJacky/Nginx-UI/api/notification"
+	"github.com/0xJacky/Nginx-UI/api/openai"
+	"github.com/0xJacky/Nginx-UI/api/settings"
+	"github.com/0xJacky/Nginx-UI/api/sites"
+	"github.com/0xJacky/Nginx-UI/api/streams"
+	"github.com/0xJacky/Nginx-UI/api/system"
+	"github.com/0xJacky/Nginx-UI/api/template"
+	"github.com/0xJacky/Nginx-UI/api/terminal"
+	"github.com/0xJacky/Nginx-UI/api/upstream"
+	"github.com/0xJacky/Nginx-UI/api/user"
+	"github.com/gin-contrib/static"
+	"github.com/gin-gonic/gin"
+	"net/http"
 )
 
 func InitRouter() *gin.Engine {
-    r := gin.New()
-    r.Use(gin.Logger())
-    r.Use(recovery())
-    r.Use(cacheJs())
-    r.Use(ipWhiteList())
+	r := gin.New()
+	r.Use(gin.Logger())
+	r.Use(recovery())
+	r.Use(cacheJs())
+	r.Use(ipWhiteList())
 
-    //r.Use(OperationSync())
+	//r.Use(OperationSync())
 
-    r.Use(static.Serve("/", mustFS("")))
+	r.Use(static.Serve("/", mustFS("")))
 
-    r.NoRoute(func(c *gin.Context) {
-        c.JSON(http.StatusNotFound, gin.H{
-            "message": "not found",
-        })
-    })
+	r.NoRoute(func(c *gin.Context) {
+		c.JSON(http.StatusNotFound, gin.H{
+			"message": "not found",
+		})
+	})
 
-    root := r.Group("/api")
-    {
-        system.InitPublicRouter(root)
-        user.InitAuthRouter(root)
+	root := r.Group("/api")
+	{
+		system.InitPublicRouter(root)
+		user.InitAuthRouter(root)
 
-        // Authorization required not websocket request
-        g := root.Group("/", authRequired(), proxy())
-        {
-            analytic.InitRouter(g)
-            user.InitManageUserRouter(g)
-            nginx.InitRouter(g)
-            sites.InitRouter(g)
-            streams.InitRouter(g)
-            config.InitRouter(g)
-            template.InitRouter(g)
-            certificate.InitCertificateRouter(g)
-            certificate.InitDNSCredentialRouter(g)
-            certificate.InitAcmeUserRouter(g)
-            system.InitPrivateRouter(g)
-            settings.InitRouter(g)
-            openai.InitRouter(g)
-            cluster.InitRouter(g)
-            notification.InitRouter(g)
-        }
+		// Authorization required not websocket request
+		g := root.Group("/", authRequired(), proxy())
+		{
+			analytic.InitRouter(g)
+			user.InitManageUserRouter(g)
+			nginx.InitRouter(g)
+			sites.InitRouter(g)
+			streams.InitRouter(g)
+			config.InitRouter(g)
+			template.InitRouter(g)
+			certificate.InitCertificateRouter(g)
+			certificate.InitDNSCredentialRouter(g)
+			certificate.InitAcmeUserRouter(g)
+			system.InitPrivateRouter(g)
+			settings.InitRouter(g)
+			openai.InitRouter(g)
+			cluster.InitRouter(g)
+			notification.InitRouter(g)
+		}
 
-        // Authorization required and websocket request
-        w := root.Group("/", authRequired(), proxyWs())
-        {
-            analytic.InitWebSocketRouter(w)
-            certificate.InitCertificateWebSocketRouter(w)
-            terminal.InitRouter(w)
-            nginx.InitNginxLogRouter(w)
-            upstream.InitRouter(w)
-            system.InitWebSocketRouter(w)
-        }
-    }
+		// Authorization required and websocket request
+		w := root.Group("/", authRequired(), proxyWs())
+		{
+			analytic.InitWebSocketRouter(w)
+			certificate.InitCertificateWebSocketRouter(w)
+			terminal.InitRouter(w)
+			nginx.InitNginxLogRouter(w)
+			upstream.InitRouter(w)
+			system.InitWebSocketRouter(w)
+		}
+	}
 
-    return r
+	return r
 }