_ops_start_and_open.py 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263
  1. #!/usr/bin/env python3
  2. # -*- coding: utf-8 -*-
  3. """启动全部服务, 起来后**自动打开** http://127.0.0.1:<gateway>/ —— 给运维控制台用 (2026-09-12)。
  4. 顺序: ① 跑 guanlan.py serve (它自己会等 healthz) → ② 等服务真正可访问(轮询) → ③ 打开浏览器。
  5. `--no-open` 时只启动不开浏览器(用于"完整重启"这类不该弹窗的场景)。
  6. 打开浏览器用 webbrowser (标准库): 本机单用户离线环境, 网关进程与用户同一会话, 能正常拉起默认浏览器。
  7. """
  8. from __future__ import annotations
  9. import pathlib
  10. import subprocess
  11. import sys
  12. import time
  13. import urllib.request
  14. import webbrowser
  15. ROOT = pathlib.Path(__file__).resolve().parents[1]
  16. sys.path.insert(0, str(ROOT))
  17. import guanlan as G # noqa: E402
  18. from src import proc as _proc # noqa: E402 无窗口子进程
  19. def main() -> int:
  20. no_open = '--no-open' in sys.argv
  21. c = G.cfg()
  22. url = f"http://{c['host']}:{c['gateway']}/"
  23. print(f'① 启动服务 (guanlan.py serve) …', flush=True)
  24. # src.proc.run: 本脚本常由**无控制台**的运维任务/网关侧拉起, 裸 spawn 会让 serve 拿到一个
  25. # 可见控制台 (2026-09-16 实测: 现场会攒下标题为 .venv\Scripts\python.exe 的黑窗)。
  26. rc = _proc.run([sys.executable, 'guanlan.py', 'serve'], cwd=str(ROOT)).returncode
  27. if rc == 2: # serve 的退出码 2 = 网关 60 s 没就绪
  28. print(f'[X] 网关未就绪 (serve 退出码 {rc}) —— 看 logs/gateway.log')
  29. return rc
  30. if rc == 1:
  31. print(' (退出码 1 = 有模块未就绪, degraded 是正常状态 —— 例如本机模型 Ollama 未启动)')
  32. print(f'② 等 {url} 可访问 …', flush=True)
  33. for i in range(30):
  34. try:
  35. with urllib.request.urlopen(url, timeout=10) as r:
  36. if r.status == 200:
  37. print(f' 门户已就绪 (第 {i + 1} 次探测)')
  38. break
  39. except Exception:
  40. time.sleep(1)
  41. else:
  42. print(' [!] 门户 30 s 内没起来 —— 仍尝试打开浏览器, 页面可能需要刷新')
  43. if no_open:
  44. print('③ (--no-open) 不打开浏览器; 入口: ' + url)
  45. return 0
  46. print(f'③ 打开浏览器: {url}')
  47. ok = webbrowser.open(url)
  48. print(f' webbrowser.open → {ok} ({"" if ok else "未成功拉起, 请手工访问上面的地址"})')
  49. return 0
  50. if __name__ == '__main__':
  51. for _s in (sys.stdout, sys.stderr):
  52. try: _s.reconfigure(errors='replace')
  53. except Exception: pass
  54. sys.exit(main())