feat: Python 版前置机 Agent 完整实现
- WebSocket 连接 B 端(注册/心跳/指数退避重连) - 命令分发(10 种远程运维命令) - 看门狗守护、日志实时流、在线升级含自动回滚 - 修复 main.py 日志格式 style 与占位符不匹配的问题 - 补充 README、配置示例、依赖清单 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,95 @@
|
||||
# -*- 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()
|
||||
Reference in New Issue
Block a user