"""Watchdog service: report loading, process checks, trigger bookkeeping. Mostly pure (no Flask imports). `trigger_watchdog_run` uses subprocess only; the caller (route) is responsible for flash/redirect. """ import logging import os import sqlite3 import subprocess import time import config from repositories import watchdog_repo logger = logging.getLogger(__name__) # 简单防重: 模块级变量记录上次触发时间戳, cooldown 秒内重复 POST 返回失败 _last_trigger_ts = 0.0 def parse_days(raw, default=7): """Clamp days query param to 1-90; non-numeric falls back to default.""" try: value = int(raw) except (TypeError, ValueError): return default return max(1, min(90, value)) def load_watchdog_reports(watchdog_conn, days=7): """Return recent watchdog reports; [] when db/table missing or unreadable.""" if not os.path.exists(config.WATCHDOG_DB): return [] try: return watchdog_repo.list_reports(watchdog_conn, days=days) except sqlite3.Error: logger.exception("failed to read watchdog db") return [] def is_watchdog_running(): """True if a python process running daily_watchdog.py already exists.""" try: out = subprocess.run( ["pgrep", "-f", "daily_watchdog.py"], capture_output=True, text=True, timeout=5, ).stdout except Exception: logger.exception("watchdog pgrep failed") return False for pid in out.split(): try: with open(f"/proc/{pid}/cmdline", "rb") as f: argv0 = f.read().split(b"\x00")[0].decode(errors="replace") except OSError: continue if os.path.basename(argv0).startswith("python"): return True return False def trigger_watchdog_run(cooldown=None): """Attempt to launch scripts/daily_watchdog.py in the background. Returns (ok, status, message): - (True, "started", msg) launched successfully - (False, "cooldown", msg) hit the anti-repeat cooldown - (False, "running", msg) an instance is already running - (False, "error", msg) subprocess launch failed """ global _last_trigger_ts cooldown = config.WATCHDOG_RUN_COOLDOWN if cooldown is None else cooldown now = time.time() if now - _last_trigger_ts < cooldown: return False, "cooldown", "操作太频繁, 请稍后再试" _last_trigger_ts = now if is_watchdog_running(): return False, "running", "daily_watchdog.py 已在运行中, 本次触发被忽略" try: os.makedirs(os.path.dirname(config.WATCHDOG_RUN_LOG), exist_ok=True) log_f = open(config.WATCHDOG_RUN_LOG, "a", encoding="utf-8") env = dict(os.environ) env["HOME"] = "/home/yi" subprocess.Popen( ["/usr/bin/python3", config.WATCHDOG_SCRIPT], stdout=log_f, stderr=subprocess.STDOUT, cwd=config.BASE_DIR, env=env, start_new_session=True, ) return True, "started", "已触发, 约 1-2 分钟后刷新页面查看新报告" except Exception as exc: logger.exception("failed to launch daily_watchdog.py") return False, "error", f"触发失败: {exc}"