main.go 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683
  1. package main
  2. import (
  3. "context"
  4. "flag"
  5. "fmt"
  6. "os"
  7. "os/signal"
  8. "sort"
  9. "strconv"
  10. "strings"
  11. "sync"
  12. "syscall"
  13. "time"
  14. "ai-status-light/internal/api"
  15. "ai-status-light/internal/ble"
  16. "ai-status-light/internal/database"
  17. "ai-status-light/internal/discovery"
  18. "ai-status-light/internal/event"
  19. "ai-status-light/internal/logger"
  20. "ai-status-light/internal/monitor"
  21. mqttcli "ai-status-light/internal/mqtt"
  22. )
  23. const defaultDBPath = "./data/config.db"
  24. var Version = "dev"
  25. func main() {
  26. if len(os.Args) < 2 {
  27. printUsage()
  28. return
  29. }
  30. switch os.Args[1] {
  31. case "monitor":
  32. runMonitor(os.Args[2:])
  33. case "config":
  34. runConfig(os.Args[2:])
  35. case "serve":
  36. runServe(os.Args[2:])
  37. case "version":
  38. fmt.Printf("opencode-monitor %s\n", Version)
  39. default:
  40. printUsage()
  41. }
  42. }
  43. func printUsage() {
  44. fmt.Printf("opencode-monitor %s\n\n", Version)
  45. fmt.Println("用法: opencode-monitor <命令> [选项]")
  46. fmt.Println("")
  47. fmt.Println("命令:")
  48. fmt.Println(" monitor 启动监控")
  49. fmt.Println(" config 管理 MQTT 配置")
  50. fmt.Println(" serve 启动 API 服务")
  51. fmt.Println(" version 显示版本信息")
  52. fmt.Println("")
  53. fmt.Println("运行 'opencode-monitor <命令> -h' 查看命令帮助")
  54. }
  55. func runServe(args []string) {
  56. fs := flag.NewFlagSet("serve", flag.ExitOnError)
  57. addr := fs.String("addr", ":8080", "监听地址")
  58. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  59. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  60. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  61. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  62. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  63. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  64. fs.Parse(args)
  65. logger.SetLevel(logger.ParseLevel(*logLevel))
  66. if err := logger.InitFileLog(*logFile); err != nil {
  67. fmt.Printf("初始化日志文件失败: %v\n", err)
  68. return
  69. }
  70. defer logger.Close()
  71. db, err := database.New(*dbPath)
  72. if err != nil {
  73. logger.Error("打开数据库失败: %v", err)
  74. fmt.Printf("打开数据库失败: %v\n", err)
  75. return
  76. }
  77. defer db.Close()
  78. logger.Info("数据库已连接: %s", *dbPath)
  79. server := api.New(db, *addr)
  80. if *tls {
  81. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  82. logger.Error("生成自签名证书失败: %v", err)
  83. fmt.Printf("生成自签名证书失败: %v\n", err)
  84. return
  85. }
  86. server.EnableTLS(*tlsCert, *tlsKey)
  87. logger.Info("HTTPS 已启用")
  88. fmt.Println("HTTPS 已启用 (自签名证书)")
  89. } else {
  90. fmt.Printf("API 服务已启动: %s\n", *addr)
  91. }
  92. fmt.Println("接口文档:")
  93. fmt.Println(" GET /api/health - 健康检查")
  94. fmt.Println(" GET /api/mqtt - 获取所有配置")
  95. fmt.Println(" POST /api/mqtt - 创建配置")
  96. fmt.Println(" GET /api/mqtt/:id - 获取单个配置")
  97. fmt.Println(" PUT /api/mqtt/:id - 更新配置")
  98. fmt.Println(" DELETE /api/mqtt/:id - 删除配置")
  99. if err := server.Start(); err != nil {
  100. logger.Error("服务启动失败: %v", err)
  101. fmt.Printf("服务启动失败: %v\n", err)
  102. }
  103. }
  104. func runMonitor(args []string) {
  105. fs := flag.NewFlagSet("monitor", flag.ExitOnError)
  106. host := fs.String("host", "127.0.0.1", "主机地址")
  107. portsFlag := fs.String("ports", "", "端口列表,逗号分隔 (如: 4096,4097,4098)")
  108. scanFlag := fs.String("scan", "", "扫描端口范围 (如: 4096-4100)")
  109. intervalFlag := fs.Int("interval", 1, "动态扫描间隔(秒), 默认1")
  110. dbPath := fs.String("db", defaultDBPath, "数据库路径")
  111. apiAddr := fs.String("api-addr", "", "API 服务地址 (如: :8080)")
  112. tls := fs.Bool("tls", false, "启用 HTTPS (使用自签名证书)")
  113. tlsCert := fs.String("tls-cert", "./data/tls/cert.pem", "TLS 证书文件路径")
  114. tlsKey := fs.String("tls-key", "./data/tls/key.pem", "TLS 私钥文件路径")
  115. logFile := fs.String("log-file", "./logs", "日志文件路径(默认 ./logs/monitor.log)")
  116. logLevel := fs.String("log-level", "info", "日志级别 (debug/info/warn/error)")
  117. fs.Parse(args)
  118. logger.SetLevel(logger.ParseLevel(*logLevel))
  119. if err := logger.InitFileLog(*logFile); err != nil {
  120. fmt.Printf("初始化日志文件失败: %v\n", err)
  121. return
  122. }
  123. defer logger.Close()
  124. var scanRange *[2]int
  125. if *scanFlag != "" {
  126. parts := strings.Split(*scanFlag, "-")
  127. if len(parts) == 2 {
  128. start, err1 := strconv.Atoi(parts[0])
  129. end, err2 := strconv.Atoi(parts[1])
  130. if err1 == nil && err2 == nil {
  131. scanRange = &[2]int{start, end}
  132. }
  133. }
  134. }
  135. ctx, cancel := context.WithCancel(context.Background())
  136. defer cancel()
  137. sigChan := make(chan os.Signal, 1)
  138. signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
  139. db, err := database.New(*dbPath)
  140. if err != nil {
  141. logger.Error("打开数据库失败: %v", err)
  142. fmt.Printf("打开数据库失败: %v\n", err)
  143. return
  144. }
  145. defer db.Close()
  146. logger.Info("数据库已连接: %s", *dbPath)
  147. var mqttClient *mqttcli.Client
  148. cfg, err := db.GetMQTTConfig()
  149. if err != nil {
  150. logger.Error("读取 MQTT 配置失败: %v", err)
  151. fmt.Printf("读取 MQTT 配置失败: %v\n", err)
  152. } else if cfg != nil {
  153. mqttClient = mqttcli.NewFromConfig(cfg)
  154. if err := mqttClient.Connect(); err != nil {
  155. logger.Error("MQTT 连接失败: %v", err)
  156. fmt.Printf("MQTT 连接失败: %v\n", err)
  157. mqttClient = nil
  158. } else {
  159. defer mqttClient.Disconnect()
  160. logger.Info("MQTT 已连接: %s (主题: %s)", cfg.Broker, cfg.Topic)
  161. fmt.Printf("MQTT 已连接: %s (主题: %s)\n", cfg.Broker, cfg.Topic)
  162. }
  163. } else {
  164. logger.Info("未配置 MQTT,跳过 MQTT 连接")
  165. }
  166. var bleClient *ble.Client
  167. bleCfg, err := db.GetBLEConfig()
  168. if err != nil {
  169. logger.Error("读取 BLE 配置失败: %v", err)
  170. } else if bleCfg != nil {
  171. bleClient = ble.New(bleCfg.DeviceName)
  172. defer bleClient.Close()
  173. logger.Info("BLE 已启用: 设备 %s", bleCfg.DeviceName)
  174. fmt.Printf("BLE 已启用: 设备 %s\n", bleCfg.DeviceName)
  175. } else {
  176. logger.Info("未配置 BLE,跳过蓝牙连接")
  177. }
  178. var apiServer *api.Server
  179. if *apiAddr != "" {
  180. apiServer = api.New(db, *apiAddr)
  181. if *tls {
  182. if err := api.EnsureSelfSignedCert(*tlsCert, *tlsKey); err != nil {
  183. logger.Error("生成自签名证书失败: %v", err)
  184. fmt.Printf("生成自签名证书失败: %v\n", err)
  185. return
  186. }
  187. apiServer.EnableTLS(*tlsCert, *tlsKey)
  188. }
  189. go func() {
  190. scheme := "http"
  191. if *tls {
  192. scheme = "https"
  193. }
  194. logger.Info("API 服务已启动: %s://%s", scheme, *apiAddr)
  195. fmt.Printf("API 服务已启动: %s://%s\n", scheme, *apiAddr)
  196. if err := apiServer.Start(); err != nil {
  197. logger.Error("API 服务失败: %v", err)
  198. fmt.Printf("API 服务失败: %v\n", err)
  199. }
  200. }()
  201. }
  202. callback := createCallback(mqttClient, apiServer, bleClient)
  203. if *portsFlag != "" {
  204. runFixedMode(ctx, *host, *portsFlag, callback, sigChan)
  205. cancel()
  206. return
  207. }
  208. if scanRange != nil {
  209. fmt.Printf("扫描端口范围 %d-%d...\n", scanRange[0], scanRange[1])
  210. } else {
  211. fmt.Println("查找 OpenCode 实例...")
  212. }
  213. runDynamicMode(ctx, *host, scanRange, *intervalFlag, callback, sigChan)
  214. cancel()
  215. }
  216. func runConfig(args []string) {
  217. if len(args) < 1 {
  218. printConfigUsage()
  219. return
  220. }
  221. dbPath := defaultDBPath
  222. logFile := "./logs"
  223. logLevel := "info"
  224. for i, arg := range args {
  225. if arg == "--db" && i+1 < len(args) {
  226. dbPath = args[i+1]
  227. }
  228. if arg == "--log-file" && i+1 < len(args) {
  229. logFile = args[i+1]
  230. }
  231. if arg == "--log-level" && i+1 < len(args) {
  232. logLevel = args[i+1]
  233. }
  234. }
  235. logger.SetLevel(logger.ParseLevel(logLevel))
  236. if err := logger.InitFileLog(logFile); err != nil {
  237. fmt.Printf("初始化日志文件失败: %v\n", err)
  238. return
  239. }
  240. defer logger.Close()
  241. // 过滤全局选项,避免传递给子命令的 FlagSet
  242. var filtered []string
  243. for i := 0; i < len(args); i++ {
  244. switch args[i] {
  245. case "--db", "--log-file", "--log-level":
  246. i++ // 跳过值
  247. default:
  248. filtered = append(filtered, args[i])
  249. }
  250. }
  251. args = filtered
  252. db, err := database.New(dbPath)
  253. if err != nil {
  254. logger.Error("打开数据库失败: %v", err)
  255. fmt.Printf("打开数据库失败: %v\n", err)
  256. return
  257. }
  258. defer db.Close()
  259. logger.Info("数据库已连接: %s", dbPath)
  260. switch args[0] {
  261. case "list":
  262. configs, err := db.ListMQTTConfigs()
  263. if err != nil {
  264. logger.Error("查询配置失败: %v", err)
  265. fmt.Printf("查询失败: %v\n", err)
  266. return
  267. }
  268. if len(configs) == 0 {
  269. fmt.Println("未配置 MQTT")
  270. return
  271. }
  272. logger.Info("查询到 %d 条 MQTT 配置", len(configs))
  273. for _, cfg := range configs {
  274. status := "禁用"
  275. if cfg.Enabled {
  276. status = "启用"
  277. }
  278. auth := ""
  279. if cfg.Username != "" {
  280. auth = fmt.Sprintf(" [认证: %s]", cfg.Username)
  281. }
  282. fmt.Printf("[%d] %s | %s | %s%s | %s\n", cfg.ID, cfg.Broker, cfg.ClientID, cfg.Topic, auth, status)
  283. }
  284. case "set":
  285. fs := flag.NewFlagSet("config set", flag.ExitOnError)
  286. broker := fs.String("broker", "", "MQTT Broker 地址 (如: tcp://127.0.0.1:1883)")
  287. clientID := fs.String("client-id", "opencode-monitor", "MQTT 客户端 ID")
  288. username := fs.String("username", "", "MQTT 用户名")
  289. password := fs.String("password", "", "MQTT 密码")
  290. topic := fs.String("topic", "opencode/status", "MQTT 主题")
  291. enabled := fs.Bool("enabled", true, "是否启用")
  292. fs.Parse(args[1:])
  293. if *broker == "" {
  294. fmt.Println("必须指定 --broker")
  295. return
  296. }
  297. cfg := &database.MQTTConfig{
  298. Broker: *broker,
  299. ClientID: *clientID,
  300. Username: *username,
  301. Password: *password,
  302. Topic: *topic,
  303. Enabled: *enabled,
  304. }
  305. if err := db.SaveMQTTConfig(cfg); err != nil {
  306. logger.Error("保存 MQTT 配置失败: %v", err)
  307. fmt.Printf("保存失败: %v\n", err)
  308. return
  309. }
  310. logger.Info("MQTT 配置已保存: %s (主题: %s)", cfg.Broker, cfg.Topic)
  311. fmt.Println("配置已保存")
  312. case "delete":
  313. if len(args) < 2 {
  314. fmt.Println("必须指定配置 ID")
  315. return
  316. }
  317. id, err := strconv.Atoi(args[1])
  318. if err != nil {
  319. logger.Warn("无效的配置 ID: %s", args[1])
  320. fmt.Println("无效的 ID")
  321. return
  322. }
  323. if err := db.DeleteMQTTConfig(id); err != nil {
  324. logger.Error("删除配置失败: id=%d, %v", id, err)
  325. fmt.Printf("删除失败: %v\n", err)
  326. return
  327. }
  328. logger.Info("MQTT 配置已删除: id=%d", id)
  329. fmt.Println("配置已删除")
  330. case "ble-list":
  331. configs, err := db.ListBLEConfigs()
  332. if err != nil {
  333. logger.Error("查询 BLE 配置失败: %v", err)
  334. fmt.Printf("查询失败: %v\n", err)
  335. return
  336. }
  337. if len(configs) == 0 {
  338. fmt.Println("未配置 BLE")
  339. return
  340. }
  341. for _, cfg := range configs {
  342. status := "禁用"
  343. if cfg.Enabled {
  344. status = "启用"
  345. }
  346. fmt.Printf("[%d] %s | %s\n", cfg.ID, cfg.DeviceName, status)
  347. }
  348. case "ble-set":
  349. fs := flag.NewFlagSet("config ble-set", flag.ExitOnError)
  350. deviceName := fs.String("device", ble.DefaultDeviceName, "蓝牙设备名称")
  351. enabled := fs.Bool("enabled", true, "是否启用")
  352. fs.Parse(args[1:])
  353. cfg := &database.BLEConfig{
  354. DeviceName: *deviceName,
  355. Enabled: *enabled,
  356. }
  357. if err := db.SaveBLEConfig(cfg); err != nil {
  358. logger.Error("保存 BLE 配置失败: %v", err)
  359. fmt.Printf("保存失败: %v\n", err)
  360. return
  361. }
  362. logger.Info("BLE 配置已保存: %s", cfg.DeviceName)
  363. fmt.Println("BLE 配置已保存")
  364. case "ble-delete":
  365. if len(args) < 2 {
  366. fmt.Println("必须指定配置 ID")
  367. return
  368. }
  369. id, err := strconv.Atoi(args[1])
  370. if err != nil {
  371. logger.Warn("无效的 BLE 配置 ID: %s", args[1])
  372. fmt.Println("无效的 ID")
  373. return
  374. }
  375. if err := db.DeleteBLEConfig(id); err != nil {
  376. logger.Error("删除 BLE 配置失败: id=%d, %v", id, err)
  377. fmt.Printf("删除失败: %v\n", err)
  378. return
  379. }
  380. logger.Info("BLE 配置已删除: id=%d", id)
  381. fmt.Println("BLE 配置已删除")
  382. default:
  383. printConfigUsage()
  384. }
  385. }
  386. func printConfigUsage() {
  387. fmt.Println("用法: opencode-monitor config <子命令> [选项]")
  388. fmt.Println("")
  389. fmt.Println("子命令:")
  390. fmt.Println(" list 列出所有 MQTT 配置")
  391. fmt.Println(" set 设置 MQTT 配置")
  392. fmt.Println(" delete <id> 删除 MQTT 配置")
  393. fmt.Println(" ble-list 列出所有 BLE 配置")
  394. fmt.Println(" ble-set 设置 BLE 配置")
  395. fmt.Println(" ble-delete <id> 删除 BLE 配置")
  396. fmt.Println("")
  397. fmt.Println("MQTT 选项:")
  398. fmt.Println(" --broker MQTT Broker 地址")
  399. fmt.Println(" --client-id MQTT 客户端 ID")
  400. fmt.Println(" --username MQTT 用户名")
  401. fmt.Println(" --password MQTT 密码")
  402. fmt.Println(" --topic MQTT 主题")
  403. fmt.Println(" --enabled 是否启用 (true/false)")
  404. fmt.Println("")
  405. fmt.Println("BLE 选项:")
  406. fmt.Println(" --device 蓝牙设备名称 (默认 AI-Light)")
  407. fmt.Println(" --enabled 是否启用 (true/false)")
  408. fmt.Println("")
  409. fmt.Println("全局选项:")
  410. fmt.Println(" --db 数据库路径")
  411. fmt.Println(" --log-file 日志文件路径(默认 ./logs/monitor.log)")
  412. fmt.Println(" --log-level 日志级别 (debug/info/warn/error)")
  413. }
  414. func createCallback(mqttClient *mqttcli.Client, apiServer *api.Server, bleClient *ble.Client) monitor.EventCallback {
  415. lastStatus := make(map[int]string)
  416. var mu sync.Mutex
  417. publish := func(port int, status string, code string) {
  418. if mqttClient != nil {
  419. payload := map[string]interface{}{
  420. "port": port,
  421. "status": status,
  422. "code": code,
  423. "timestamp": time.Now().Format(time.RFC3339),
  424. }
  425. if err := mqttClient.PublishRaw(mqttClient.GetTopic(), payload); err != nil {
  426. logger.Error("MQTT 发送失败: %v", err)
  427. fmt.Printf("MQTT 发送失败: %v\n", err)
  428. }
  429. }
  430. if apiServer != nil {
  431. apiServer.BroadcastStatus(port, status, code)
  432. }
  433. if bleClient != nil {
  434. mode := ble.MapCodeToMode(code)
  435. bleClient.SetMode(mode)
  436. }
  437. }
  438. return func(port int, evt *event.SSEEvent) {
  439. msg := event.FormatEvent(port, evt)
  440. if msg != "" {
  441. fmt.Println(msg)
  442. }
  443. if mqttClient == nil && apiServer == nil {
  444. return
  445. }
  446. var status, code string
  447. switch evt.Type {
  448. case "session.status":
  449. if s, ok := evt.Properties["status"].(map[string]interface{}); ok {
  450. if t, ok := s["type"].(string); ok {
  451. code = t
  452. }
  453. status = event.ParseStatus(s)
  454. }
  455. case "session.idle":
  456. status = "空闲"
  457. code = "idle"
  458. case "message.part.updated":
  459. if part, ok := evt.Properties["part"].(map[string]interface{}); ok {
  460. switch part["type"].(string) {
  461. case "tool":
  462. if st, ok := part["state"].(map[string]interface{}); ok {
  463. if s, ok := st["status"].(string); ok {
  464. code = s
  465. }
  466. status = event.ParseToolState(st)
  467. }
  468. case "reasoning":
  469. status = "思考中"
  470. code = "reasoning"
  471. default:
  472. status = "使用工具中"
  473. code = "using_tool"
  474. }
  475. }
  476. case "permission.updated":
  477. code = "permission"
  478. if title, ok := evt.Properties["title"].(string); ok && title != "" {
  479. status = "等待权限: " + title
  480. } else {
  481. status = "等待权限"
  482. }
  483. case "session.error":
  484. status = "错误"
  485. code = "error"
  486. }
  487. if status != "" {
  488. mu.Lock()
  489. prev := lastStatus[port]
  490. if status == "空闲" && prev != "" && prev != "空闲" {
  491. publish(port, "会话完成", "session_completed")
  492. }
  493. lastStatus[port] = status
  494. mu.Unlock()
  495. publish(port, status, code)
  496. }
  497. }
  498. }
  499. func runFixedMode(ctx context.Context, host string, portsFlag string, callback monitor.EventCallback, sigChan chan os.Signal) {
  500. var ports []int
  501. for _, p := range strings.Split(portsFlag, ",") {
  502. p = strings.TrimSpace(p)
  503. if port, err := strconv.Atoi(p); err == nil {
  504. ports = append(ports, port)
  505. }
  506. }
  507. if len(ports) == 0 {
  508. fmt.Println("未指定端口")
  509. return
  510. }
  511. logger.Info("固定模式启动,监控端口: %v", ports)
  512. fmt.Printf("监控端口: %v\n", ports)
  513. fmt.Println("Ctrl+C 停止")
  514. fmt.Println(strings.Repeat("-", 40))
  515. var wg sync.WaitGroup
  516. for _, port := range ports {
  517. wg.Add(1)
  518. go func(p int) {
  519. defer wg.Done()
  520. logger.Info("开始监控端口: %d", p)
  521. m := monitor.New(host, p, callback)
  522. m.Run(ctx)
  523. logger.Info("端口 %d 监控已停止", p)
  524. }(port)
  525. }
  526. <-sigChan
  527. logger.Info("收到停止信号,正在退出")
  528. fmt.Println("\n已停止")
  529. wg.Wait()
  530. logger.Info("所有监控协程已退出")
  531. }
  532. func runDynamicMode(ctx context.Context, host string, scanRange *[2]int, interval int, callback monitor.EventCallback, sigChan chan os.Signal) {
  533. scanner := discovery.NewScanner(host, scanRange)
  534. monitoredPorts := make(map[int]bool)
  535. runningMonitors := make(map[int]context.CancelFunc)
  536. var mu sync.Mutex
  537. startMonitor := func(port int) {
  538. mu.Lock()
  539. if _, exists := runningMonitors[port]; exists {
  540. mu.Unlock()
  541. return
  542. }
  543. monitorCtx, monitorCancel := context.WithCancel(ctx)
  544. runningMonitors[port] = monitorCancel
  545. mu.Unlock()
  546. go func() {
  547. logger.Info("开始监控端口: %d", port)
  548. fmt.Printf("开始监控端口: %d\n", port)
  549. m := monitor.New(host, port, callback)
  550. m.Run(monitorCtx)
  551. // 连接断开后清理记录,允许重新连接
  552. mu.Lock()
  553. delete(runningMonitors, port)
  554. delete(monitoredPorts, port)
  555. mu.Unlock()
  556. logger.Info("端口 %d 监控已停止,等待重新连接", port)
  557. fmt.Printf("端口 %d 监控已停止,等待重新连接\n", port)
  558. }()
  559. }
  560. initial := scanner.Discover()
  561. if len(initial) == 0 {
  562. logger.Info("未找到运行中的 OpenCode 实例,等待自动检测")
  563. fmt.Println("未找到运行中的 OpenCode 实例")
  564. fmt.Println("请先执行: opencode serve --port 4096")
  565. fmt.Println("启动后会自动检测,等待中...")
  566. }
  567. for _, port := range initial {
  568. monitoredPorts[port] = true
  569. startMonitor(port)
  570. }
  571. if len(monitoredPorts) > 0 {
  572. ports := make([]int, 0, len(monitoredPorts))
  573. for p := range monitoredPorts {
  574. ports = append(ports, p)
  575. }
  576. sort.Ints(ports)
  577. logger.Info("找到 %d 个实例: %v", len(monitoredPorts), ports)
  578. fmt.Printf("找到 %d 个实例: %v\n", len(monitoredPorts), ports)
  579. }
  580. logger.Info("动态模式启动,每 %d 秒扫描新实例", interval)
  581. fmt.Printf("每 %d 秒扫描新实例,Ctrl+C 停止\n", interval)
  582. fmt.Println(strings.Repeat("-", 40))
  583. scanTicker := time.NewTicker(time.Duration(interval) * time.Second)
  584. defer scanTicker.Stop()
  585. go func() {
  586. for {
  587. select {
  588. case <-ctx.Done():
  589. return
  590. case <-scanTicker.C:
  591. newPorts := scanner.Discover()
  592. if len(newPorts) > 0 {
  593. logger.Debug("扫描到 %d 个端口: %v", len(newPorts), newPorts)
  594. }
  595. for _, port := range newPorts {
  596. mu.Lock()
  597. alreadyMonitored := monitoredPorts[port]
  598. if !alreadyMonitored {
  599. monitoredPorts[port] = true
  600. }
  601. mu.Unlock()
  602. if !alreadyMonitored {
  603. logger.Info("发现新实例端口: %d,开始监控", port)
  604. startMonitor(port)
  605. }
  606. }
  607. }
  608. }
  609. }()
  610. <-sigChan
  611. logger.Info("收到停止信号,正在退出")
  612. fmt.Println("\n已停止")
  613. }