Author: Mike Swanson Machine: DESKTOP-0O8A1RL Timestamp: 2026-05-12 20:54:05
186 lines
7.3 KiB
Python
186 lines
7.3 KiB
Python
#!/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.")
|