# -*- coding: utf-8 -*- """Agent 启动入口。 启动方式: python -m agent.main (源码运行) hospital-agent.exe (PyInstaller 打包后) 职责: 1. WebSocket 主动连接 B 端管理服务器(注册/心跳/命令) 2. 守护主程序(健康探测 + 宕机拉起) 3. 写 PID 文件(pid/hospital-agent.pid) 由 WinSW/NSSM Windows 服务守护本进程。 """ import logging import os import signal import sys import threading from agent.config import AgentConfig from agent.main_app_client import MainAppClient log = logging.getLogger(__name__) # 组装阶段先声明,供信号处理器引用 _dispatcher = None _guard = None _client = None def run(): """主流程:加载配置 → 写 PID → 组装组件 → 启动 → 主线程保活。""" global _dispatcher, _guard, _client logging.basicConfig( level=logging.INFO, format="{asctime} {levelname} [{name}] {message}", style="{", ) # 源码运行时根目录即工作目录,PyInstaller 打包后同理(服务管理器负责设置工作目录) config = AgentConfig.load() log.info("Agent 启动:deviceId=%s, server=%s", config.device_id, config.server_url) # 写 PID 文件 _write_pid_file() # 组装组件(延迟导入避免环) from agent.dispatcher import CommandDispatcher from agent.process_guard import ProcessGuard from agent.ws_client import AgentClient main_app_client = MainAppClient(config) dispatcher = CommandDispatcher(config, main_app_client) client = AgentClient(config, dispatcher) dispatcher.set_client(client) guard = ProcessGuard(config, main_app_client, client) _dispatcher, _guard, _client = dispatcher, guard, client # 优雅停机信号处理 signal.signal(signal.SIGINT, _shutdown) signal.signal(signal.SIGTERM, _shutdown) client.start() guard.start() # 主线程保活 threading.Event().wait() def _shutdown(signum, frame): """优雅停机:释放日志流 → 停守护 → 断连接 → 退出。""" log.info("Agent 停机中...(signal=%s)", signum) if _dispatcher is not None: _dispatcher.shutdown_log_stream() if _guard is not None: _guard.stop() if _client is not None: _client.stop() sys.exit(0) def _write_pid_file(): """写 PID 文件(pid/hospital-agent.pid)。""" try: os.makedirs("pid", exist_ok=True) with open(os.path.join("pid", "hospital-agent.pid"), "w", encoding="utf-8") as f: f.write(str(os.getpid())) except Exception as e: log.warning("PID 文件写入失败:%s", e) if __name__ == "__main__": run()