diff --git a/backend/api/routes/system.py b/backend/api/routes/system.py index 701d58b..045dbd2 100644 --- a/backend/api/routes/system.py +++ b/backend/api/routes/system.py @@ -1,6 +1,7 @@ import os import re import subprocess +from pathlib import Path from typing import Any, Dict, Optional, Tuple from fastapi import APIRouter, HTTPException, Header @@ -28,6 +29,22 @@ def _build_supervisorctl_cmd(args: list[str]) -> list[str]: supervisor_conf = os.getenv("SUPERVISOR_CONF", "").strip() use_sudo = os.getenv("SUPERVISOR_USE_SUDO", "false").lower() == "true" + # 如果没显式配置 SUPERVISOR_CONF,就尝试自动探测常见路径(宝塔/系统) + if not supervisor_conf: + candidates = [ + "/www/server/panel/plugin/supervisor/supervisord.conf", + "/www/server/panel/plugin/supervisor/supervisor.conf", + "/etc/supervisor/supervisord.conf", + "/etc/supervisord.conf", + ] + for p in candidates: + try: + if Path(p).exists(): + supervisor_conf = p + break + except Exception: + continue + cmd: list[str] = [] if use_sudo: # 需要你在 sudoers 配置 NOPASSWD(sudo -n 才不会卡住) @@ -75,6 +92,89 @@ def _get_program_name() -> str: return os.getenv("SUPERVISOR_TRADING_PROGRAM", "auto_sys").strip() or "auto_sys" +def _select_best_process_name(program: str, status_all_raw: str) -> Optional[str]: + """ + 从 `supervisorctl status` 全量输出中,找到最匹配的真实进程名。 + 兼容 supervisor 的 group:process 格式,例如:auto_sys:auto_sys_00 + """ + if not status_all_raw: + return None + + lines = [ln.strip() for ln in status_all_raw.splitlines() if ln.strip()] + names: list[str] = [] + for ln in lines: + name = ln.split(None, 1)[0].strip() + if name: + names.append(name) + + # 精确优先:program / program_00 / program:program_00 + preferred = [program, f"{program}_00", f"{program}:{program}_00"] + for cand in preferred: + if cand in names: + return cand + + # 次优:任意以 program_ 开头 + for name in names: + if name.startswith(program + "_"): + return name + + # 次优:任意以 program: 开头 + for name in names: + if name.startswith(program + ":"): + return name + + return None + + +def _status_with_fallback(program: str) -> Tuple[str, Optional[str], Optional[str]]: + """ + - 优先 `status ` + - 若 no such process:返回全量 status,并尝试解析真实 name(例如 auto_sys:auto_sys_00) + 返回:(raw, resolved_name, status_all) + """ + try: + raw = _run_supervisorctl(["status", program]) + return raw, program, None + except Exception as e: + msg = str(e).lower() + if "no such process" not in msg: + raise + + status_all = _run_supervisorctl(["status"]) + resolved = _select_best_process_name(program, status_all) + if resolved: + try: + raw = _run_supervisorctl(["status", resolved]) + return raw, resolved, status_all + except Exception: + # 兜底:至少把全量输出返回,方便你确认真实进程名 + return status_all, None, status_all + return status_all, None, status_all + + +def _action_with_fallback(action: str, program: str) -> Tuple[str, Optional[str], Optional[str]]: + """ + 对 start/stop/restart 做兜底:如果 program 不存在,尝试解析真实 name 再执行。 + 返回:(output, resolved_name, status_all) + """ + try: + out = _run_supervisorctl([action, program]) + return out, program, None + except Exception as e: + msg = str(e).lower() + if "no such process" not in msg: + raise + + status_all = _run_supervisorctl(["status"]) + resolved = _select_best_process_name(program, status_all) + if not resolved: + # 没找到就把全量输出带上,方便定位 + raise RuntimeError(f"no such process: {program}. 当前 supervisor 进程列表:\n{status_all}") + + out = _run_supervisorctl([action, resolved]) + return out, resolved, status_all + + @router.post("/clear-cache") async def clear_cache(x_admin_token: Optional[str] = Header(default=None, alias="X-Admin-Token")) -> Dict[str, Any]: """ @@ -147,15 +247,17 @@ async def trading_status(x_admin_token: Optional[str] = Header(default=None, ali program = _get_program_name() try: - raw = _run_supervisorctl(["status", program]) + raw, resolved_name, status_all = _status_with_fallback(program) running, pid, state = _parse_supervisor_status(raw) return { "mode": "supervisor", "program": program, + "resolved_name": resolved_name, "running": running, "pid": pid, "state": state, "raw": raw, + "status_all": status_all, } except Exception as e: raise HTTPException( @@ -170,8 +272,8 @@ async def trading_start(x_admin_token: Optional[str] = Header(default=None, alia program = _get_program_name() try: - out = _run_supervisorctl(["start", program]) - raw = _run_supervisorctl(["status", program]) + out, resolved_name, status_all = _action_with_fallback("start", program) + raw, resolved_name2, status_all2 = _status_with_fallback(resolved_name or program) running, pid, state = _parse_supervisor_status(raw) return { "message": "交易系统已启动(supervisor)", @@ -179,10 +281,12 @@ async def trading_start(x_admin_token: Optional[str] = Header(default=None, alia "status": { "mode": "supervisor", "program": program, + "resolved_name": resolved_name2 or resolved_name, "running": running, "pid": pid, "state": state, "raw": raw, + "status_all": status_all2 or status_all, }, } except Exception as e: @@ -195,8 +299,8 @@ async def trading_stop(x_admin_token: Optional[str] = Header(default=None, alias program = _get_program_name() try: - out = _run_supervisorctl(["stop", program]) - raw = _run_supervisorctl(["status", program]) + out, resolved_name, status_all = _action_with_fallback("stop", program) + raw, resolved_name2, status_all2 = _status_with_fallback(resolved_name or program) running, pid, state = _parse_supervisor_status(raw) return { "message": "交易系统已停止(supervisor)", @@ -204,10 +308,12 @@ async def trading_stop(x_admin_token: Optional[str] = Header(default=None, alias "status": { "mode": "supervisor", "program": program, + "resolved_name": resolved_name2 or resolved_name, "running": running, "pid": pid, "state": state, "raw": raw, + "status_all": status_all2 or status_all, }, } except Exception as e: @@ -220,8 +326,8 @@ async def trading_restart(x_admin_token: Optional[str] = Header(default=None, al program = _get_program_name() try: - out = _run_supervisorctl(["restart", program]) - raw = _run_supervisorctl(["status", program]) + out, resolved_name, status_all = _action_with_fallback("restart", program) + raw, resolved_name2, status_all2 = _status_with_fallback(resolved_name or program) running, pid, state = _parse_supervisor_status(raw) return { "message": "交易系统已重启(supervisor)", @@ -229,10 +335,12 @@ async def trading_restart(x_admin_token: Optional[str] = Header(default=None, al "status": { "mode": "supervisor", "program": program, + "resolved_name": resolved_name2 or resolved_name, "running": running, "pid": pid, "state": state, "raw": raw, + "status_all": status_all2 or status_all, }, } except Exception as e: