diff --git a/services/openwrt.py b/services/openwrt.py index b6d366d..40ffee4 100644 --- a/services/openwrt.py +++ b/services/openwrt.py @@ -1,7 +1,7 @@ import json from typing import Any -from services.runner import run_cmd +from services.runner import run_cmd, run_cmd_full def _format_uptime(seconds: int | float | None) -> str: @@ -205,74 +205,52 @@ async def get_openwrt_status(cfg: dict[str, Any]) -> str: ssh_cmd += ["-i", str(identity_file)] ssh_cmd += ["-p", str(port), f"{user}@{host}"] - async def _ssh_cmd(cmd: str) -> tuple[int, str]: - run = ssh_cmd + ["sh", "-c", cmd] - return await run_cmd(run, timeout=timeout_sec + 15) + remote = ( + "ubus call system info 2>/dev/null || (cat /proc/uptime; echo; cat /proc/loadavg); " + "echo __SEP__;" + "ubus call network.interface.wan status 2>/dev/null; echo __SEP__;" + "ubus call network.wireless status 2>/dev/null; echo __SEP__;" + "ubus call luci-rpc getDHCPLeases '{\"family\":4}' 2>/dev/null || cat /tmp/dhcp.leases" + ) + cmd = ssh_cmd + ["sh", "-c", remote] + rc, out = await run_cmd_full(cmd, timeout=timeout_sec + 15) + if rc == 124: + return "⚠️ OpenWrt SSH error: timeout" + if rc != 0: + return f"⚠️ OpenWrt SSH error: {out.strip() or 'unknown error'}" + + parts = [p.strip() for p in out.split("__SEP__")] + if len(parts) < 4: + return "⚠️ OpenWrt response incomplete" sys_info = None - rc, out = await _ssh_cmd("ubus call system info 2>/dev/null") - if rc == 124: - return "⚠️ OpenWrt SSH error (system info): timeout" - if rc == 0 and out.strip(): - try: - sys_info = json.loads(out) - except json.JSONDecodeError: - sys_info = None - if sys_info is None: - rc_u, out_u = await _ssh_cmd("cat /proc/uptime; echo; cat /proc/loadavg") - if rc_u == 124: - return "⚠️ OpenWrt SSH error (uptime/load): timeout" - if rc_u != 0: - return f"⚠️ OpenWrt SSH error: {out_u.strip() or 'unknown error'}" - fallback_raw = out_u - else: - fallback_raw = "" - - rc, out = await _ssh_cmd("ubus call network.interface.wan status 2>/dev/null") - if rc == 124: - return "⚠️ OpenWrt SSH error (wan status): timeout" - if rc == 0 and out.strip(): - try: - wan_status = json.loads(out) - except json.JSONDecodeError: - wan_status = {} - else: + wan_status = None + wireless = None + leases = None + leases_fallback = "" + try: + sys_info = json.loads(parts[0]) + except json.JSONDecodeError: + sys_info = None + try: + wan_status = json.loads(parts[1]) + except json.JSONDecodeError: wan_status = {} - - rc, out = await _ssh_cmd("ubus call network.wireless status 2>/dev/null") - if rc == 124: - return "⚠️ OpenWrt SSH error (wireless status): timeout" - if rc == 0 and out.strip(): - try: - wireless = json.loads(out) - except json.JSONDecodeError: - wireless = {} - else: + try: + wireless = json.loads(parts[2]) + except json.JSONDecodeError: wireless = {} - - rc, out = await _ssh_cmd("ubus call luci-rpc getDHCPLeases '{\"family\":4}' 2>/dev/null") - if rc == 124: - return "⚠️ OpenWrt SSH error (dhcp leases): timeout" - if rc == 0 and out.strip(): - try: - leases = json.loads(out) - except json.JSONDecodeError: - leases = None - else: + try: + leases = json.loads(parts[3]) + except json.JSONDecodeError: leases = None - rc_l, out_l = await _ssh_cmd("cat /tmp/dhcp.leases 2>/dev/null || true") - if rc_l == 124: - return "⚠️ OpenWrt SSH error (leases fallback): timeout" - if rc_l == 0: - leases_fallback = out_l - else: - leases_fallback = "" + leases_fallback = parts[3] if isinstance(sys_info, dict): uptime_raw = sys_info.get("uptime") load_raw = sys_info.get("load") else: - uptime_raw, load_raw = _parse_proc_fallback(fallback_raw) + uptime_raw, load_raw = _parse_proc_fallback(parts[0]) uptime = _format_uptime(uptime_raw) load = _format_load(load_raw) wan_ip = _extract_wan_ip(wan_status) or "unknown" diff --git a/services/runner.py b/services/runner.py index 4cf1b45..0ee707c 100644 --- a/services/runner.py +++ b/services/runner.py @@ -22,3 +22,24 @@ async def run_cmd(cmd: list[str], *, use_restic_env: bool = False, timeout: int except asyncio.TimeoutError: proc.kill() return 124, "❌ timeout" + + +async def run_cmd_full(cmd: list[str], *, use_restic_env: bool = False, timeout: int = 60): + env = os.environ.copy() + env["PATH"] = "/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" + if use_restic_env: + env.update(RESTIC_ENV) + + proc = await asyncio.create_subprocess_exec( + *cmd, + stdout=asyncio.subprocess.PIPE, + stderr=asyncio.subprocess.STDOUT, + env=env, + ) + + try: + out, _ = await asyncio.wait_for(proc.communicate(), timeout=timeout) + return proc.returncode, out.decode(errors="ignore") + except asyncio.TimeoutError: + proc.kill() + return 124, "❌ timeout"