updater.go 3.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. package main
  2. import (
  3. "context"
  4. "errors"
  5. "fmt"
  6. "os/exec"
  7. "strings"
  8. "time"
  9. )
  10. const allowScripts = "@deepseek-ai/dsh-subprocess-local,koffi,node-pty,@google/genai,protobufjs"
  11. // beginUpdate 进入更新态(防重入,同时让 UI 显示"正在更新");已在更新中返回 false。
  12. func (s *Supervisor) beginUpdate() bool {
  13. s.mu.Lock()
  14. defer s.mu.Unlock()
  15. if s.updating {
  16. return false
  17. }
  18. s.updating = true
  19. return true
  20. }
  21. func (s *Supervisor) endUpdate() {
  22. s.mu.Lock()
  23. s.updating = false
  24. s.mu.Unlock()
  25. }
  26. // upToDate 判断已安装版本是否已是最新(查询失败时按"非最新"处理)。
  27. func (s *Supervisor) upToDate() (bool, string) {
  28. current := s.installedVersion()
  29. if current == "" {
  30. return false, ""
  31. }
  32. latest, err := s.LatestVersion()
  33. if err != nil {
  34. return false, current
  35. }
  36. return latest == current, current
  37. }
  38. // UpdateAndRestart 更新流程的唯一入口:
  39. // 检查版本 -> 必要时停服务 -> 安装 -> 恢复运行。
  40. // 全程处于"更新中"状态(UI 可见);已是最新则既不安装也不重启。
  41. func (s *Supervisor) UpdateAndRestart() (string, error) {
  42. if !s.beginUpdate() {
  43. return "", errors.New("更新已在进行中")
  44. }
  45. defer s.endUpdate()
  46. s.log.Printf("更新:检查最新版本…")
  47. if fresh, current := s.upToDate(); fresh {
  48. s.log.Printf("更新:已是最新(%s),跳过安装与重启", current)
  49. return "已是最新版本 " + current, nil
  50. }
  51. wasDesired := s.isDesired()
  52. if wasDesired {
  53. s.Stop()
  54. }
  55. out, err := s.installLatest()
  56. if wasDesired {
  57. s.Start()
  58. }
  59. return out, err
  60. }
  61. // installLatest 执行 npm 全局安装(不做版本检查,由调用方负责)。
  62. func (s *Supervisor) installLatest() (string, error) {
  63. exe, args := npmCommandFor("install", "-g", dshPackage+"@latest", "--allow-scripts="+allowScripts)
  64. ctx, cancel := context.WithTimeout(context.Background(), 15*time.Minute)
  65. defer cancel()
  66. cmd := exec.CommandContext(ctx, exe, args...)
  67. hideWindow(cmd)
  68. s.log.Printf("执行更新: %s %s", exe, strings.Join(args, " "))
  69. out, err := cmd.CombinedOutput()
  70. text := strings.TrimSpace(string(out))
  71. if text != "" {
  72. s.log.Printf("更新输出: %s", text)
  73. }
  74. if err != nil {
  75. return text, fmt.Errorf("更新失败: %w", err)
  76. }
  77. s.invalidateProbe()
  78. return text, nil
  79. }
  80. // LatestVersion 查询 npm 上最新版本。
  81. func (s *Supervisor) LatestVersion() (string, error) {
  82. exe, args := npmCommandFor("view", dshPackage, "version")
  83. ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
  84. defer cancel()
  85. cmd := exec.CommandContext(ctx, exe, args...)
  86. hideWindow(cmd)
  87. out, err := cmd.Output()
  88. if err != nil {
  89. return "", err
  90. }
  91. return strings.TrimSpace(string(out)), nil
  92. }
  93. // startAutoUpdate 启动定期更新循环(进程内单例、常驻)。
  94. // 循环不因开关关闭而退出——关闭时仅暂停计时,再次开启立即恢复,
  95. // 避免"关掉再打开"后因旧循环退出而导致定期更新永久失效。
  96. func (s *Supervisor) startAutoUpdate() {
  97. s.autoMu.Lock()
  98. if s.autoLoop {
  99. s.autoMu.Unlock()
  100. return
  101. }
  102. s.autoLoop = true
  103. s.autoMu.Unlock()
  104. go func() {
  105. var elapsed time.Duration
  106. ticker := time.NewTicker(time.Minute)
  107. defer ticker.Stop()
  108. for range ticker.C {
  109. s.mu.Lock()
  110. enabled := s.cfg.AutoUpdate
  111. interval := time.Duration(s.cfg.UpdateIntervalH) * time.Hour
  112. s.mu.Unlock()
  113. if !enabled {
  114. elapsed = 0 // 关闭期间不计时
  115. continue
  116. }
  117. if interval <= 0 {
  118. interval = 24 * time.Hour
  119. }
  120. elapsed += time.Minute
  121. if elapsed < interval {
  122. continue
  123. }
  124. elapsed = 0
  125. s.runScheduledUpdate()
  126. }
  127. }()
  128. }
  129. // runScheduledUpdate 定期检查并更新(版本比对在 UpdateAndRestart 内完成一次)。
  130. func (s *Supervisor) runScheduledUpdate() {
  131. s.log.Printf("定期更新:检查最新版本…")
  132. text, err := s.UpdateAndRestart()
  133. if err != nil {
  134. s.log.Printf("定期更新失败: %v", err)
  135. return
  136. }
  137. s.log.Printf("定期更新完成: %s", firstLine(text))
  138. }