From 05ef18ebba123192c82a6b49bc08c278d98f8a94 Mon Sep 17 00:00:00 2001 From: Nicole Rappe Date: Sat, 4 Oct 2025 15:27:53 -0600 Subject: [PATCH] Improve silent update targeting and logging --- Data/Agent/Roles/role_ScriptExec_SYSTEM.py | 68 +++++++++++++++++++++- Data/Server/server.py | 27 +++++++-- 2 files changed, 90 insertions(+), 5 deletions(-) diff --git a/Data/Agent/Roles/role_ScriptExec_SYSTEM.py b/Data/Agent/Roles/role_ScriptExec_SYSTEM.py index 39d0240..e211406 100644 --- a/Data/Agent/Roles/role_ScriptExec_SYSTEM.py +++ b/Data/Agent/Roles/role_ScriptExec_SYSTEM.py @@ -1,6 +1,7 @@ import os import re import asyncio +import datetime import tempfile import uuid import time @@ -39,6 +40,42 @@ def _find_borealis_root() -> Optional[str]: return None +def _agent_logs_root() -> str: + root = _find_borealis_root() or _project_root() + return os.path.abspath(os.path.join(root, 'Logs', 'Agent')) + + +def _rotate_daily(path: str): + try: + if os.path.isfile(path): + mtime = os.path.getmtime(path) + dt = datetime.datetime.fromtimestamp(mtime) + today = datetime.datetime.now().date() + if dt.date() != today: + base, ext = os.path.splitext(path) + suffix = dt.strftime('%Y-%m-%d') + newp = f"{base}.{suffix}{ext}" + try: + os.replace(path, newp) + except Exception: + pass + except Exception: + pass + + +def _write_updater_log(message: str): + try: + log_dir = _agent_logs_root() + os.makedirs(log_dir, exist_ok=True) + path = os.path.join(log_dir, 'updater.log') + _rotate_daily(path) + ts = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') + with open(path, 'a', encoding='utf-8') as fh: + fh.write(f'[{ts}] {message}\n') + except Exception: + pass + + def _launch_silent_update_task(): if os.name != 'nt': raise RuntimeError('Silent update is supported on Windows hosts only.') @@ -86,6 +123,7 @@ def _launch_silent_update_task(): if proc.returncode != 0: stderr = proc.stderr or proc.stdout or 'scheduled task registration failed' raise RuntimeError(stderr.strip()) + return task_name def _canonical_env_key(name: str) -> str: cleaned = re.sub(r"[^A-Za-z0-9_]", "_", (name or "").strip()) @@ -341,7 +379,25 @@ class Role: return loop = asyncio.get_running_loop() - await loop.run_in_executor(None, _launch_silent_update_task) + request_id = (details.get('request_id') or '').strip() + req_disp = request_id or 'n/a' + host_disp = hostname or 'unknown' + _write_updater_log(f"Silent update request received for host '{host_disp}' (request_id={req_disp})") + try: + task_name = await loop.run_in_executor(None, _launch_silent_update_task) + except Exception as exc: + _write_updater_log( + f"Silent update launch failed for host '{host_disp}' (request_id={req_disp}): {exc}" + ) + try: + details['_silent_update_error_logged'] = True + except Exception: + pass + raise + + _write_updater_log( + f"Silent update scheduled via task '{task_name}' for host '{host_disp}' (request_id={req_disp})" + ) try: await sio.emit( @@ -355,6 +411,16 @@ class Role: except Exception: pass except Exception as e: + if not isinstance(details, dict) or not details.get('_silent_update_error_logged'): + try: + request_id = (details.get('request_id') or '').strip() + req_disp = request_id or 'n/a' + host_disp = hostname or 'unknown' + _write_updater_log( + f"Silent update encountered error on host '{host_disp}' (request_id={req_disp}): {e}" + ) + except Exception: + pass try: await sio.emit( 'agent_silent_update_status', diff --git a/Data/Server/server.py b/Data/Server/server.py index 12ad8a1..8a05597 100644 --- a/Data/Server/server.py +++ b/Data/Server/server.py @@ -3150,15 +3150,34 @@ def agent_silent_update(): return jsonify({"error": "No valid hostnames provided"}), 400 request_id = uuid.uuid4().hex - results: List[Dict[str, str]] = [] + now_ts = int(time.time()) + results: List[Dict[str, Any]] = [] + + # Map hostname -> connected agent_id(s) so we can target specific rooms. + host_to_agents: Dict[str, List[str]] = {} + for agent_id, info in (registered_agents or {}).items(): + try: + hostname = str(info.get("hostname") or "").strip().lower() + except Exception: + hostname = "" + if not hostname: + continue + host_to_agents.setdefault(hostname, []).append(agent_id) + for host in hostnames: payload = { "target_hostname": host, "request_id": request_id, - "requested_at": int(time.time()), + "requested_at": now_ts, } - socketio.emit("agent_silent_update", payload) - results.append({"hostname": host, "status": "queued"}) + target_agents = host_to_agents.get(host.strip().lower(), []) + if target_agents: + for agent_id in target_agents: + socketio.emit("agent_silent_update", payload, room=agent_id) + else: + # Fallback broadcast for legacy agents or if hostname lookup failed. + socketio.emit("agent_silent_update", payload) + results.append({"hostname": host, "status": "queued", "agent_ids": target_agents}) _write_service_log( "server",