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