From e2a05f1ce7fe6d774287f19ee12b5e36a0830754 Mon Sep 17 00:00:00 2001 From: Mike Swanson Date: Tue, 12 May 2026 20:54:06 -0700 Subject: [PATCH] sync: auto-sync from DESKTOP-0O8A1RL at 2026-05-12 20:54:05 Author: Mike Swanson Machine: DESKTOP-0O8A1RL Timestamp: 2026-05-12 20:54:05 --- .../tmp/watchdog-impl/api_watchdog_alerts.rs | 1 + projects/msp-tools/guru-rmm | 2 +- tmp_check_args.py | 19 ++ tmp_patch_updater.py | 185 ++++++++++++++++++ 4 files changed, 206 insertions(+), 1 deletion(-) create mode 100644 .claire/tmp/watchdog-impl/api_watchdog_alerts.rs create mode 100644 tmp_check_args.py create mode 100644 tmp_patch_updater.py diff --git a/.claire/tmp/watchdog-impl/api_watchdog_alerts.rs b/.claire/tmp/watchdog-impl/api_watchdog_alerts.rs new file mode 100644 index 0000000..b3a4252 --- /dev/null +++ b/.claire/tmp/watchdog-impl/api_watchdog_alerts.rs @@ -0,0 +1 @@ +placeholder \ No newline at end of file diff --git a/projects/msp-tools/guru-rmm b/projects/msp-tools/guru-rmm index 58e5d6b..a878556 160000 --- a/projects/msp-tools/guru-rmm +++ b/projects/msp-tools/guru-rmm @@ -1 +1 @@ -Subproject commit 58e5d6b46be5a3c7f6c70504d70d64bd2151b521 +Subproject commit a878556207bd4e7b2be313af86a3857ffdcb774b diff --git a/tmp_check_args.py b/tmp_check_args.py new file mode 100644 index 0000000..85829f4 --- /dev/null +++ b/tmp_check_args.py @@ -0,0 +1,19 @@ +#!/usr/bin/env python3 +with open('/home/guru/gururmm/agent/src/updater/mod.rs') as f: + src = f.read() + +# Find the Windows watchdog function +idx = src.find("async fn create_windows_rollback_watchdog") +if idx == -1: + print("NOT FOUND") +else: + # Find the format args block within the Windows watchdog + chunk = src[idx:idx+3000] + # Look for the replace call + ri = chunk.find("replace") + if ri != -1: + print("FORMAT ARGS AREA:") + print(repr(chunk[ri-20:ri+300])) + else: + print("replace not found in chunk") + print(repr(chunk[:500])) diff --git a/tmp_patch_updater.py b/tmp_patch_updater.py new file mode 100644 index 0000000..d23f1d7 --- /dev/null +++ b/tmp_patch_updater.py @@ -0,0 +1,185 @@ +#!/usr/bin/env python3 +"""Patch /home/guru/gururmm/agent/src/updater/mod.rs with two fixes: +1. Replace restart_service() Windows block with detached-cmd approach. +2. Fix hardcoded "gururmm-agent" service name in create_windows_rollback_watchdog. +""" + +import sys + +PATH = "/home/guru/gururmm/agent/src/updater/mod.rs" + +with open(PATH, "r") as f: + src = f.read() + +original = src + +# --------------------------------------------------------------------------- +# Fix 1: restart_service() +# --------------------------------------------------------------------------- + +OLD_RESTART = """\ + /// Restart the agent service + async fn restart_service(&self) -> Result<()> { + #[cfg(unix)] + { + info!("Exiting for service restart by systemd"); + std::process::exit(0); + } + + #[cfg(windows)] + { + use crate::service::windows::SERVICE_NAME; + // Restart Windows service + tokio::process::Command::new("sc.exe") + .args(["stop", SERVICE_NAME]) + .status() + .await?; + + tokio::time::sleep(std::time::Duration::from_secs(2)).await; + + tokio::process::Command::new("sc.exe") + .args(["start", SERVICE_NAME]) + .status() + .await?; + } + + // Give the new process a moment to start + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + + // Exit this process - the new version should be running now + std::process::exit(0); + }""" + +NEW_RESTART = """\ + /// Restart the agent service + async fn restart_service(&self) -> Result<()> { + #[cfg(unix)] + { + info!("Exiting for service restart by systemd"); + std::process::exit(0); + } + + #[cfg(windows)] + { + use crate::service::windows::SERVICE_NAME; + + // Spawn a detached cmd.exe that waits for this process to exit, + // then starts the service. We cannot await sc start here because + // calling sc stop on our own service causes the SCM to send + // SERVICE_CONTROL_STOP back to this process, which tears down the + // tokio runtime and cancels any pending awaits before sc start runs. + let restart_cmd = format!( + "timeout /t 5 /nobreak >nul 2>&1 & sc.exe start {}", + SERVICE_NAME + ); + match std::process::Command::new("cmd") + .args(["/c", &restart_cmd]) + .creation_flags(0x08000000) // CREATE_NO_WINDOW + .spawn() + { + Ok(_) => info!("Restart helper spawned, exiting for service stop/start"), + Err(e) => error!("Failed to spawn restart helper: {} — service will stay stopped", e), + } + + // Exit this process. SCM detects the exit and marks the service + // Stopped. The detached cmd above will sc start after 5 seconds. + std::process::exit(0); + } + + // Unreachable on Windows (process::exit above), but needed for + // Unix path and to satisfy the return type. + #[allow(unreachable_code)] + Ok(()) + }""" + +if OLD_RESTART not in src: + print("ERROR: restart_service old block not found in file", file=sys.stderr) + idx = src.find("async fn restart_service") + if idx != -1: + print(f"restart_service found at offset {idx}:", file=sys.stderr) + print(repr(src[idx:idx+1000]), file=sys.stderr) + sys.exit(1) + +src = src.replace(OLD_RESTART, NEW_RESTART, 1) +print("[OK] Fix 1 applied: restart_service() replaced with detached-cmd approach") + +# --------------------------------------------------------------------------- +# Fix 2: create_windows_rollback_watchdog +# --------------------------------------------------------------------------- + +# 2a: Replace three hardcoded service name strings in the PS script template +replacements = [ + ('$service = Get-Service -Name "gururmm-agent" -ErrorAction SilentlyContinue', + '$service = Get-Service -Name $ServiceName -ErrorAction SilentlyContinue', + 'Get-Service line'), + ('Stop-Service -Name "gururmm-agent" -Force -ErrorAction SilentlyContinue', + 'Stop-Service -Name $ServiceName -Force -ErrorAction SilentlyContinue', + 'Stop-Service line'), + ('Start-Service -Name "gururmm-agent"', + 'Start-Service -Name $ServiceName', + 'Start-Service line'), +] + +for old, new, label in replacements: + if old not in src: + print(f"ERROR: {label} not found", file=sys.stderr) + sys.exit(1) + src = src.replace(old, new, 1) + print(f"[OK] Fix 2a: replaced {label}") + +# 2b: Add $ServiceName PS variable after $Timeout assignment in the script template +OLD_TIMEOUT_LINE = '$Timeout = {timeout}\n\nStart-Sleep -Seconds $Timeout' +NEW_TIMEOUT_LINE = '$Timeout = {timeout}\n$ServiceName = "{service_name}"\n\nStart-Sleep -Seconds $Timeout' + +if OLD_TIMEOUT_LINE not in src: + print("ERROR: $Timeout line context not found", file=sys.stderr) + sys.exit(1) +src = src.replace(OLD_TIMEOUT_LINE, NEW_TIMEOUT_LINE, 1) +print("[OK] Fix 2b: $ServiceName PS variable added to script template") + +# 2c: Add service_name = SERVICE_NAME to format! args +# The actual bytes in the file for the format args closing (confirmed via repr): +# replace('\', "\\"),\n timeout = timeout\n ); +# In Python string literals (raw bytes via repr): +# replace(\'\\\\', "\\\\\\\\"),\n timeout = timeout\n ); +OLD_FORMAT_TAIL = " timeout = timeout\n );" +NEW_FORMAT_TAIL = " timeout = timeout,\n service_name = SERVICE_NAME\n );" + +# There are two format! calls in this file (Unix watchdog and Windows watchdog). +# The Unix one ends with: script = script_path.display()\n ); +# The Windows one ends with: timeout = timeout\n ); +# So OLD_FORMAT_TAIL is unique to the Windows block. Verify: +count = src.count(OLD_FORMAT_TAIL) +if count != 1: + print(f"ERROR: expected 1 match for format tail, got {count}", file=sys.stderr) + sys.exit(1) +src = src.replace(OLD_FORMAT_TAIL, NEW_FORMAT_TAIL, 1) +print("[OK] Fix 2c: service_name added to format! args") + +# 2d: Add SERVICE_NAME import at top of the Windows watchdog function body +# The function opens with these lines (no prior use statement): +OLD_WATCHDOG_OPEN = ( + " async fn create_windows_rollback_watchdog(&self) -> Result<()> {\n" + " let backup_path = self.config.backup_path();\n" +) +NEW_WATCHDOG_OPEN = ( + " async fn create_windows_rollback_watchdog(&self) -> Result<()> {\n" + " use crate::service::windows::SERVICE_NAME;\n" + " let backup_path = self.config.backup_path();\n" +) + +if OLD_WATCHDOG_OPEN not in src: + print("ERROR: watchdog function opening not found", file=sys.stderr) + sys.exit(1) +src = src.replace(OLD_WATCHDOG_OPEN, NEW_WATCHDOG_OPEN, 1) +print("[OK] Fix 2d: SERVICE_NAME import added to watchdog function") + +# --------------------------------------------------------------------------- +# Write result +# --------------------------------------------------------------------------- +with open(PATH, "w") as f: + f.write(src) + +lines_changed = sum(1 for a, b in zip(original.splitlines(), src.splitlines()) if a != b) +lines_added = len(src.splitlines()) - len(original.splitlines()) +print(f"\nFile written. ~{lines_changed} lines changed, {lines_added:+d} net lines.")