main.go 3.6 KB

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