"""Nexus — Server Service (Business logic for server management) Application layer — orchestrates Repository + SSH pool + Redis cache. """ import asyncio import json import logging from typing import Optional, List, Dict from server.domain.models import Server from server.domain.repositories import ServerRepository, SyncLogRepository logger = logging.getLogger("nexus.server_service") class ServerService: """Business logic for server CRUD, health check, and push operations""" def __init__(self, server_repo: ServerRepository, sync_log_repo: SyncLogRepository): self.server_repo = server_repo self.sync_log_repo = sync_log_repo async def get_server(self, id: int) -> Optional[Server]: return await self.server_repo.get_by_id(id) async def create_server(self, server: Server) -> Server: return await self.server_repo.create(server) async def update_server(self, server: Server) -> Server: return await self.server_repo.update(server) async def delete_server(self, id: int) -> bool: return await self.server_repo.delete(id) async def update_heartbeat(self, id: int, data: dict) -> None: """Process Agent heartbeat data""" is_online = data.get("is_online", True) system_info = json.dumps(data.get("system_info", {})) agent_version = data.get("agent_version", "") await self.server_repo.update_heartbeat(id, is_online, system_info, agent_version) async def check_all_servers(self, server_ids: List[int]) -> Dict[int, dict]: """Check health of multiple servers via SSH (no Agent port 8601 required).""" from server.application.server_connectivity import write_ssh_health_heartbeat from server.infrastructure.redis.client import get_redis from server.infrastructure.ssh.remote_probe import ssh_health_probe results = {} redis = get_redis() async def _check_one(server: Server) -> tuple: try: probe = await ssh_health_probe(server, timeout=15) try: last_hb = await write_ssh_health_heartbeat(redis, server, probe) probe = {**probe, "last_heartbeat": last_hb} except Exception as redis_err: logger.warning( "health check heartbeat write failed server=%s: %s", server.id, redis_err, ) return server.id, probe except Exception as e: return server.id, {"status": "offline", "error": str(e)[:200], "channel": "ssh"} servers = [] for sid in server_ids: server = await self.server_repo.get_by_id(sid) if server: servers.append(server) else: results[sid] = {"status": "not_found"} if servers: check_results = await asyncio.gather(*[_check_one(s) for s in servers]) for sid, result in check_results: results[sid] = result return results