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 }