main.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  1. package main
  2. import (
  3. "fmt"
  4. "os"
  5. "os/signal"
  6. "strings"
  7. "syscall"
  8. )
  9. // appVersion 由构建脚本注入(go build -ldflags "-X main.appVersion=x.y.z")。
  10. // 之所以用变量:避免"代码改了、版本号忘改"(v2.4.0 曾出现该疏漏)。
  11. var appVersion = "dev"
  12. // 会话级命名互斥体(Local 前缀无需特权;Global 前缀普通用户常创建失败,会导致单实例失效)
  13. const mutexName = "Local\\DeepSeekTraySingleton"
  14. func main() {
  15. args := os.Args[1:]
  16. cfg := loadConfig()
  17. log := NewLogger()
  18. defer log.Close()
  19. log.Printf("=== deepseek-tray %s args=%v ===", appVersion, args)
  20. if len(args) > 0 {
  21. switch strings.ToLower(strings.TrimLeft(args[0], "-/")) {
  22. case "version":
  23. attachConsole()
  24. fmt.Println(appVersion)
  25. return
  26. case "status":
  27. attachConsole()
  28. printStatus(cfg, log)
  29. return
  30. case "autostart":
  31. attachConsole()
  32. runAutoStartCommand(args, log)
  33. return
  34. case "daemon":
  35. // 无 UI 守护模式(可注册为 Windows 服务):不弹终端、不打开浏览器
  36. runDaemon(cfg, log)
  37. return
  38. case "silent":
  39. // 静默启动(开机自启使用):等同托盘模式,全程无窗口、不打开浏览器
  40. }
  41. }
  42. inst, ok := acquireSingleInstance(mutexName)
  43. if !ok {
  44. attachConsole()
  45. fmt.Println("deepseek-tray 已在运行(托盘或守护进程)")
  46. return
  47. }
  48. defer inst.release()
  49. runTray(cfg, log)
  50. }
  51. // runDaemon 无 UI 守护模式(适合 NSSM 注册为 Windows 服务)。
  52. func runDaemon(cfg *Config, log *Logger) {
  53. log.Printf("以守护模式启动(无托盘)")
  54. inst, ok := acquireSingleInstance(mutexName)
  55. if !ok {
  56. log.Printf("已有实例在运行,退出")
  57. return
  58. }
  59. defer inst.release()
  60. sup := NewSupervisor(cfg, log)
  61. sup.Start()
  62. if cfg.AutoUpdate {
  63. sup.startAutoUpdate()
  64. }
  65. // 优雅退出:Ctrl+C / 服务停止信号(NSSM 停止服务时生效)
  66. sig := make(chan os.Signal, 1)
  67. signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
  68. <-sig
  69. log.Printf("收到退出信号,停止服务")
  70. sup.Stop()
  71. }
  72. // printStatus 命令行状态查询(同时写日志,便于无控制台场景回溯)。
  73. func printStatus(cfg *Config, log *Logger) {
  74. sup := NewSupervisor(cfg, log)
  75. version := sup.installedVersion()
  76. if version == "" {
  77. version = "未知(未找到 dsh)"
  78. }
  79. lines := []string{
  80. fmt.Sprintf("deepseek-tray %s", appVersion),
  81. fmt.Sprintf("DSH 版本 : %s", version),
  82. fmt.Sprintf("Web 地址 : http://%s:%d", cfg.WebHost, cfg.WebPort),
  83. fmt.Sprintf("端口监听 : %v", isPortOpen(cfg.WebHost, cfg.WebPort)),
  84. fmt.Sprintf("开机自启 : %v", isAutoStartEnabled()),
  85. fmt.Sprintf("定期自动更新: %v(间隔 %d 小时)", cfg.AutoUpdate, cfg.UpdateIntervalH),
  86. fmt.Sprintf("配置文件 : %s", configPath()),
  87. fmt.Sprintf("日志目录 : %s", logDir()),
  88. }
  89. for _, line := range lines {
  90. fmt.Println(line)
  91. log.Printf("status | %s", line)
  92. }
  93. }
  94. // runAutoStartCommand 处理 autostart [on|off] 子命令(无 UI 场景配置自启)。
  95. func runAutoStartCommand(args []string, log *Logger) {
  96. sub := ""
  97. if len(args) > 1 {
  98. sub = strings.ToLower(args[1])
  99. }
  100. switch sub {
  101. case "on":
  102. if err := setAutoStart(true); err != nil {
  103. fmt.Println("设置开机自启失败:", err)
  104. log.Printf("autostart on 失败: %v", err)
  105. return
  106. }
  107. fmt.Println("开机自启:已开启(静默启动,无窗口、不打开浏览器)")
  108. log.Printf("autostart on")
  109. case "off":
  110. if err := setAutoStart(false); err != nil {
  111. fmt.Println("取消开机自启失败:", err)
  112. log.Printf("autostart off 失败: %v", err)
  113. return
  114. }
  115. fmt.Println("开机自启:已关闭")
  116. log.Printf("autostart off")
  117. default:
  118. fmt.Printf("开机自启:%v\n", isAutoStartEnabled())
  119. fmt.Println("用法: deepseek-tray.exe autostart [on|off]")
  120. log.Printf("autostart status: %v", isAutoStartEnabled())
  121. }
  122. }