191 lines
4.5 KiB
Go
191 lines
4.5 KiB
Go
package updater
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"net/http"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/minio/selfupdate"
|
|
"web-giao-ban-go/config"
|
|
"web-giao-ban-go/logger"
|
|
)
|
|
|
|
type GiteaAsset struct {
|
|
ID int64 `json:"id"`
|
|
Name string `json:"name"`
|
|
Size int64 `json:"size"`
|
|
DownloadURL string `json:"browser_download_url"`
|
|
}
|
|
|
|
type GiteaRelease struct {
|
|
TagName string `json:"tag_name"`
|
|
Name string `json:"name"`
|
|
Body string `json:"body"`
|
|
CreatedAt time.Time `json:"created_at"`
|
|
PublishedAt time.Time `json:"published_at"`
|
|
Assets []GiteaAsset `json:"assets"`
|
|
}
|
|
|
|
type CheckResult struct {
|
|
HasUpdate bool `json:"has_update"`
|
|
CurrentVersion string `json:"current_version"`
|
|
LatestVersion string `json:"latest_version"`
|
|
ReleaseTitle string `json:"release_title"`
|
|
ReleaseNotes string `json:"release_notes"`
|
|
AssetName string `json:"asset_name"`
|
|
AssetURL string `json:"asset_url"`
|
|
AssetSize int64 `json:"asset_size"`
|
|
}
|
|
|
|
func CheckUpdate() (*CheckResult, error) {
|
|
cfg := config.Get()
|
|
if !cfg.Updater.Enabled {
|
|
return &CheckResult{HasUpdate: false, CurrentVersion: cfg.Updater.CurrentVersion}, nil
|
|
}
|
|
|
|
baseURL := strings.TrimRight(cfg.Updater.GiteaURL, "/")
|
|
if baseURL == "" {
|
|
baseURL = "https://i.dh-his.com"
|
|
}
|
|
owner := cfg.Updater.Owner
|
|
if owner == "" {
|
|
owner = "hdhiswork"
|
|
}
|
|
repo := cfg.Updater.Repo
|
|
if repo == "" {
|
|
repo = "DUAN"
|
|
}
|
|
|
|
apiURL := fmt.Sprintf("%s/api/v1/repos/%s/%s/releases/latest", baseURL, owner, repo)
|
|
|
|
req, err := http.NewRequest("GET", apiURL, nil)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
token := cfg.DecryptedUpdaterToken
|
|
if token == "" {
|
|
token, _ = config.Decrypt(cfg.Updater.Token)
|
|
}
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "token "+token)
|
|
}
|
|
req.Header.Set("Accept", "application/json")
|
|
|
|
client := &http.Client{Timeout: 15 * time.Second}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode == http.StatusNotFound {
|
|
return &CheckResult{
|
|
HasUpdate: false,
|
|
CurrentVersion: cfg.Updater.CurrentVersion,
|
|
LatestVersion: cfg.Updater.CurrentVersion,
|
|
ReleaseNotes: "Chưa có bản phát hành nào trên Gitea",
|
|
}, nil
|
|
}
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
bodyBytes, _ := io.ReadAll(resp.Body)
|
|
return nil, fmt.Errorf("Gitea API trả về mã lỗi %d: %s", resp.StatusCode, string(bodyBytes))
|
|
}
|
|
|
|
var rel GiteaRelease
|
|
if err := json.NewDecoder(resp.Body).Decode(&rel); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
curVer := cfg.Updater.CurrentVersion
|
|
hasNew := isNewerVersion(rel.TagName, curVer)
|
|
|
|
// Pick asset according to OS
|
|
var matchedAsset *GiteaAsset
|
|
for _, a := range rel.Assets {
|
|
lowerName := strings.ToLower(a.Name)
|
|
if runtime.GOOS == "windows" {
|
|
if strings.HasSuffix(lowerName, ".exe") {
|
|
matchedAsset = &a
|
|
break
|
|
}
|
|
} else {
|
|
if strings.Contains(lowerName, "linux") || (!strings.HasSuffix(lowerName, ".exe") && !strings.HasSuffix(lowerName, ".zip")) {
|
|
matchedAsset = &a
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
res := &CheckResult{
|
|
HasUpdate: hasNew,
|
|
CurrentVersion: curVer,
|
|
LatestVersion: rel.TagName,
|
|
ReleaseTitle: rel.Name,
|
|
ReleaseNotes: rel.Body,
|
|
}
|
|
|
|
if matchedAsset != nil {
|
|
res.AssetName = matchedAsset.Name
|
|
res.AssetURL = matchedAsset.DownloadURL
|
|
res.AssetSize = matchedAsset.Size
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
func isNewerVersion(latest, current string) bool {
|
|
latest = strings.TrimPrefix(strings.TrimSpace(latest), "v")
|
|
current = strings.TrimPrefix(strings.TrimSpace(current), "v")
|
|
if latest == "" {
|
|
return false
|
|
}
|
|
if current == "" {
|
|
return true
|
|
}
|
|
return latest != current
|
|
}
|
|
|
|
func ApplyUpdate(downloadURL string) error {
|
|
cfg := config.Get()
|
|
logger.Info("Bắt đầu tải bản cập nhật từ: %s", downloadURL)
|
|
|
|
req, err := http.NewRequest("GET", downloadURL, nil)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
token := cfg.DecryptedUpdaterToken
|
|
if token == "" {
|
|
token, _ = config.Decrypt(cfg.Updater.Token)
|
|
}
|
|
if token != "" {
|
|
req.Header.Set("Authorization", "token "+token)
|
|
}
|
|
|
|
client := &http.Client{Timeout: 5 * time.Minute}
|
|
resp, err := client.Do(req)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
defer resp.Body.Close()
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
return fmt.Errorf("tải bản cập nhật thất bại với HTTP status %d", resp.StatusCode)
|
|
}
|
|
|
|
err = selfupdate.Apply(resp.Body, selfupdate.Options{})
|
|
if err != nil {
|
|
logger.Error("Self-update thất bại: %v", err)
|
|
return err
|
|
}
|
|
|
|
logger.Info("Đã cập nhật binary ứng dụng thành công! Vui lòng khởi động lại ứng dụng.")
|
|
return nil
|
|
}
|