Files
Nexus/server/application/services/unset_path_detect_schedule.py
T
r 9c9a51263c chore: 本地快照 — 终端 UI、服务器 site_url、后台任务与 Agent 导航
汇总未提交工作区:Servers 列表 site_url 链接、终端页与选机器 UI、SSH 连通性与 Telegram 通知、agent/offline/unset_path 后台调度与 gate_ai_review 门控;更新 AGENTS 与 agent-exploration 规则(仅本机 commit);同步 web/app 构建产物与相关文档。
2026-06-18 06:01:50 +08:00

112 lines
3.7 KiB
Python

"""Scheduled nightly detect-path for servers with unset target_path."""
from __future__ import annotations
import logging
from datetime import datetime, timezone
from typing import Any
from server.config import settings
from server.domain.models import Server
from server.infrastructure.database.server_repo import ServerRepositoryImpl
from server.infrastructure.database.session import AsyncSessionLocal
from server.infrastructure.redis import server_batch_store as sbs
from server.infrastructure.redis.client import get_redis
from server.utils.display_time import to_beijing
logger = logging.getLogger("nexus.unset_path_detect_schedule")
REDIS_LAST_RUN_KEY = "nexus:unset_path_detect:last_run_date"
SCHEDULED_OPERATOR = "system"
def _enabled() -> bool:
return str(getattr(settings, "UNSET_PATH_DETECT_ENABLED", "true")).lower() == "true"
def _cron_expr() -> str:
expr = (getattr(settings, "UNSET_PATH_DETECT_CRON", None) or "0 2 * * *").strip()
return expr or "0 2 * * *"
def server_ssh_configured(server: Server) -> bool:
auth = (server.auth_method or "key").lower()
if auth == "password":
return bool(server.password)
return bool(server.ssh_key_private or server.ssh_key_configured)
async def list_unset_server_ids(*, ssh_only: bool = True) -> list[int]:
"""Return server IDs with unset target_path (empty or ``/www/wwwroot``)."""
async with AsyncSessionLocal() as session:
repo = ServerRepositoryImpl(session)
servers, _total = await repo.get_filtered(target_path_unset=True, max_rows=5000)
ids: list[int] = []
for server in servers:
if ssh_only and not server_ssh_configured(server):
continue
ids.append(server.id)
return ids
async def _detect_path_batch_running() -> bool:
for live in await sbs.list_running_live_jobs():
if live.get("op") == "detect-path":
return True
return False
async def _mark_ran_today() -> None:
today = to_beijing(datetime.now(timezone.utc)).strftime("%Y-%m-%d")
redis = get_redis()
await redis.set(REDIS_LAST_RUN_KEY, today, ex=86400 * 2)
async def _already_ran_today() -> bool:
today = to_beijing(datetime.now(timezone.utc)).strftime("%Y-%m-%d")
redis = get_redis()
last = await redis.get(REDIS_LAST_RUN_KEY)
if last is None:
return False
if isinstance(last, bytes):
last = last.decode()
return str(last).strip() == today
async def run_scheduled_unset_path_detect(*, force: bool = False) -> dict[str, Any]:
"""Start batch detect-path for unset servers; idempotent once per Beijing calendar day."""
if not _enabled() and not force:
return {"action": "skipped", "reason": "disabled"}
if not force and await _already_ran_today():
return {"action": "skipped", "reason": "already_ran_today"}
if await _detect_path_batch_running():
return {"action": "skipped", "reason": "detect_path_batch_running"}
server_ids = await list_unset_server_ids()
if not server_ids:
await _mark_ran_today()
return {"action": "skipped", "reason": "no_unset_servers", "server_count": 0}
from server.application.services.server_batch_service import start_batch_job
started = await start_batch_job(
op="detect-path",
server_ids=server_ids,
operator=SCHEDULED_OPERATOR,
params={"scheduled": True, "cron": _cron_expr()},
)
await _mark_ran_today()
logger.info(
"Scheduled unset target_path detect started job_id=%s servers=%s",
started.get("job_id"),
len(server_ids),
)
return {
"action": "started",
"job_id": started.get("job_id"),
"server_count": len(server_ids),
"label": started.get("label"),
}