Files
claudetools/imported-conversations/general-work/claude-general/5e058595-cbe5-4373-94ea-728e103504f5/tool-results/toolu_01433pfJegHrugPqXu9XvUBT.txt
Mike Swanson 75ce1c2fd5 feat: Add Sequential Thinking to Code Review + Frontend Validation
Enhanced code review and frontend validation with intelligent triggers:

Code Review Agent Enhancement:
- Added Sequential Thinking MCP integration for complex issues
- Triggers on 2+ rejections or 3+ critical issues
- New escalation format with root cause analysis
- Comprehensive solution strategies with trade-off evaluation
- Educational feedback to break rejection cycles
- Files: .claude/agents/code-review.md (+308 lines)
- Docs: CODE_REVIEW_ST_ENHANCEMENT.md, CODE_REVIEW_ST_TESTING.md

Frontend Design Skill Enhancement:
- Automatic invocation for ANY UI change
- Comprehensive validation checklist (200+ checkpoints)
- 8 validation categories (visual, interactive, responsive, a11y, etc.)
- 3 validation levels (quick, standard, comprehensive)
- Integration with code review workflow
- Files: .claude/skills/frontend-design/SKILL.md (+120 lines)
- Docs: UI_VALIDATION_CHECKLIST.md (462 lines), AUTOMATIC_VALIDATION_ENHANCEMENT.md (587 lines)

Settings Optimization:
- Repaired .claude/settings.local.json (fixed m365 pattern)
- Reduced permissions from 49 to 33 (33% reduction)
- Removed duplicates, sorted alphabetically
- Created SETTINGS_PERMISSIONS.md documentation

Checkpoint Command Enhancement:
- Dual checkpoint system (git + database)
- Saves session context to API for cross-machine recall
- Includes git metadata in database context
- Files: .claude/commands/checkpoint.md (+139 lines)

Decision Rationale:
- Sequential Thinking MCP breaks rejection cycles by identifying root causes
- Automatic frontend validation catches UI issues before code review
- Dual checkpoints enable complete project memory across machines
- Settings optimization improves maintainability

Total: 1,200+ lines of documentation and enhancements

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
2026-01-17 16:23:52 -07:00

661 lines
31 KiB
Plaintext

1→//! Windows Service implementation for GuruRMM Agent
2→//!
3→//! This module implements the Windows Service Control Manager (SCM) protocol,
4→//! allowing the agent to run as a native Windows service without third-party wrappers.
5→
6→#[cfg(windows)]
7→pub mod windows {
8→ use std::ffi::OsString;
9→ use std::path::PathBuf;
10→ use std::sync::mpsc;
11→ use std::time::Duration;
12→
13→ use anyhow::{Context, Result};
14→ use tracing::{error, info, warn};
15→ use windows_service::{
16→ define_windows_service,
17→ service::{
18→ ServiceAccess, ServiceControl, ServiceControlAccept, ServiceErrorControl,
19→ ServiceExitCode, ServiceInfo, ServiceStartType, ServiceState, ServiceStatus,
20→ ServiceType,
21→ },
22→ service_control_handler::{self, ServiceControlHandlerResult},
23→ service_dispatcher, service_manager::{ServiceManager, ServiceManagerAccess},
24→ };
25→
26→ pub const SERVICE_NAME: &str = "GuruRMMAgent";
27→ pub const SERVICE_DISPLAY_NAME: &str = "GuruRMM Agent";
28→ pub const SERVICE_DESCRIPTION: &str =
29→ "GuruRMM Agent - Remote Monitoring and Management service";
30→ pub const INSTALL_DIR: &str = r"C:\Program Files\GuruRMM";
31→ pub const CONFIG_DIR: &str = r"C:\ProgramData\GuruRMM";
32→
33→ // Generate the Windows service boilerplate
34→ define_windows_service!(ffi_service_main, service_main);
35→
36→ /// Entry point called by the Windows Service Control Manager
37→ pub fn run_as_service() -> Result<()> {
38→ // This function is called when Windows starts the service.
39→ // It blocks until the service is stopped.
40→ service_dispatcher::start(SERVICE_NAME, ffi_service_main)
41→ .context("Failed to start service dispatcher")?;
42→ Ok(())
43→ }
44→
45→ /// Main service function called by the SCM
46→ fn service_main(arguments: Vec<OsString>) {
47→ if let Err(e) = run_service(arguments) {
48→ error!("Service error: {}", e);
49→ }
50→ }
51→
52→ /// The actual service implementation
53→ fn run_service(_arguments: Vec<OsString>) -> Result<()> {
54→ // Create a channel to receive stop events
55→ let (shutdown_tx, shutdown_rx) = mpsc::channel();
56→
57→ // Create the service control handler
58→ let event_handler = move |control_event| -> ServiceControlHandlerResult {
59→ match control_event {
60→ ServiceControl::Stop => {
61→ info!("Received stop command from SCM");
62→ let _ = shutdown_tx.send(());
63→ ServiceControlHandlerResult::NoError
64→ }
65→ ServiceControl::Interrogate => ServiceControlHandlerResult::NoError,
66→ ServiceControl::Shutdown => {
67→ info!("Received shutdown command from SCM");
68→ let _ = shutdown_tx.send(());
69→ ServiceControlHandlerResult::NoError
70→ }
71→ _ => ServiceControlHandlerResult::NotImplemented,
72→ }
73→ };
74→
75→ // Register the service control handler
76→ let status_handle = service_control_handler::register(SERVICE_NAME, event_handler)
77→ .context("Failed to register service control handler")?;
78→
79→ // Report that we're starting
80→ status_handle
81→ .set_service_status(ServiceStatus {
82→ service_type: ServiceType::OWN_PROCESS,
83→ current_state: ServiceState::StartPending,
84→ controls_accepted: ServiceControlAccept::empty(),
85→ exit_code: ServiceExitCode::Win32(0),
86→ checkpoint: 0,
87→ wait_hint: Duration::from_secs(10),
88→ process_id: None,
89→ })
90→ .context("Failed to set StartPending status")?;
91→
92→ // Determine config path
93→ let config_path = PathBuf::from(format!(r"{}\agent.toml", CONFIG_DIR));
94→
95→ // Create the tokio runtime for the agent
96→ let runtime = tokio::runtime::Runtime::new().context("Failed to create tokio runtime")?;
97→
98→ // Start the agent in the runtime
99→ let agent_result = runtime.block_on(async {
100→ // Load configuration
101→ let config = match crate::config::AgentConfig::load(&config_path) {
102→ Ok(c) => c,
103→ Err(e) => {
104→ error!("Failed to load config from {:?}: {}", config_path, e);
105→ return Err(anyhow::anyhow!("Config load failed: {}", e));
106→ }
107→ };
108→
109→ info!("GuruRMM Agent service starting...");
110→ info!("Config loaded from {:?}", config_path);
111→ info!("Server URL: {}", config.server.url);
112→
113→ // Initialize metrics collector
114→ let metrics_collector = crate::metrics::MetricsCollector::new();
115→ info!("Metrics collector initialized");
116→
117→ // Create shared state
118→ let state = std::sync::Arc::new(crate::AppState {
119→ config: config.clone(),
120→ metrics_collector,
121→ connected: tokio::sync::RwLock::new(false),
122→ });
123→
124→ // Report that we're running
125→ status_handle
126→ .set_service_status(ServiceStatus {
127→ service_type: ServiceType::OWN_PROCESS,
128→ current_state: ServiceState::Running,
129→ controls_accepted: ServiceControlAccept::STOP | ServiceControlAccept::SHUTDOWN,
130→ exit_code: ServiceExitCode::Win32(0),
131→ checkpoint: 0,
132→ wait_hint: Duration::default(),
133→ process_id: None,
134→ })
135→ .context("Failed to set Running status")?;
136→
137→ // Start WebSocket client task
138→ let ws_state = std::sync::Arc::clone(&state);
139→ let ws_handle = tokio::spawn(async move {
140→ loop {
141→ info!("Connecting to server...");
142→ match crate::transport::WebSocketClient::connect_and_run(std::sync::Arc::clone(
143→ &ws_state,
144→ ))
145→ .await
146→ {
147→ Ok(_) => {
148→ warn!("WebSocket connection closed normally, reconnecting...");
149→ }
150→ Err(e) => {
151→ error!("WebSocket error: {}, reconnecting in 10 seconds...", e);
152→ }
153→ }
154→ *ws_state.connected.write().await = false;
155→ tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
156→ }
157→ });
158→
159→ // Start metrics collection task
160→ let metrics_state = std::sync::Arc::clone(&state);
161→ let metrics_handle = tokio::spawn(async move {
162→ let interval = metrics_state.config.metrics.interval_seconds;
163→ let mut interval_timer =
164→ tokio::time::interval(tokio::time::Duration::from_secs(interval));
165→
166→ loop {
167→ interval_timer.tick().await;
168→ let metrics = metrics_state.metrics_collector.collect().await;
169→ if *metrics_state.connected.read().await {
170→ info!(
171→ "Metrics: CPU={:.1}%, Mem={:.1}%, Disk={:.1}%",
172→ metrics.cpu_percent, metrics.memory_percent, metrics.disk_percent
173→ );
174→ }
175→ }
176→ });
177→
178→ // Wait for shutdown signal from SCM
179→ // We use a separate task to poll the channel since it's not async
180→ let shutdown_handle = tokio::spawn(async move {
181→ loop {
182→ match shutdown_rx.try_recv() {
183→ Ok(_) => {
184→ info!("Shutdown signal received");
185→ break;
186→ }
187→ Err(mpsc::TryRecvError::Empty) => {
188→ tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
189→ }
190→ Err(mpsc::TryRecvError::Disconnected) => {
191→ warn!("Shutdown channel disconnected");
192→ break;
193→ }
194→ }
195→ }
196→ });
197→
198→ // Wait for shutdown
199→ tokio::select! {
200→ _ = shutdown_handle => {
201→ info!("Service shutting down gracefully");
202→ }
203→ _ = ws_handle => {
204→ error!("WebSocket task ended unexpectedly");
205→ }
206→ _ = metrics_handle => {
207→ error!("Metrics task ended unexpectedly");
208→ }
209→ }
210→
211→ Ok::<(), anyhow::Error>(())
212→ });
213→
214→ // Report that we're stopping
215→ status_handle
216→ .set_service_status(ServiceStatus {
217→ service_type: ServiceType::OWN_PROCESS,
218→ current_state: ServiceState::StopPending,
219→ controls_accepted: ServiceControlAccept::empty(),
220→ exit_code: ServiceExitCode::Win32(0),
221→ checkpoint: 0,
222→ wait_hint: Duration::from_secs(5),
223→ process_id: None,
224→ })
225→ .ok();
226→
227→ // Report that we've stopped
228→ status_handle
229→ .set_service_status(ServiceStatus {
230→ service_type: ServiceType::OWN_PROCESS,
231→ current_state: ServiceState::Stopped,
232→ controls_accepted: ServiceControlAccept::empty(),
233→ exit_code: match &agent_result {
234→ Ok(_) => ServiceExitCode::Win32(0),
235→ Err(_) => ServiceExitCode::Win32(1),
236→ },
237→ checkpoint: 0,
238→ wait_hint: Duration::default(),
239→ process_id: None,
240→ })
241→ .ok();
242→
243→ agent_result
244→ }
245→
246→ /// Known legacy service names to check and remove
247→ const LEGACY_SERVICE_NAMES: &[&str] = &[
248→ "GuruRMM-Agent", // NSSM-based service name
249→ "gururmm-agent", // Alternative casing
250→ ];
251→
252→ /// Detect and remove legacy service installations (e.g., NSSM-based)
253→ fn cleanup_legacy_services() -> Result<()> {
254→ let manager = match ServiceManager::local_computer(
255→ None::<&str>,
256→ ServiceManagerAccess::CONNECT,
257→ ) {
258→ Ok(m) => m,
259→ Err(_) => return Ok(()), // Can't connect, skip legacy cleanup
260→ };
261→
262→ for legacy_name in LEGACY_SERVICE_NAMES {
263→ if let Ok(service) = manager.open_service(
264→ *legacy_name,
265→ ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
266→ ) {
267→ info!("Found legacy service '{}', removing...", legacy_name);
268→
269→ // Stop if running
270→ if let Ok(status) = service.query_status() {
271→ if status.current_state != ServiceState::Stopped {
272→ info!("Stopping legacy service...");
273→ let _ = service.stop();
274→ std::thread::sleep(Duration::from_secs(3));
275→ }
276→ }
277→
278→ // Delete the service
279→ match service.delete() {
280→ Ok(_) => {
281→ println!("** Removed legacy service: {}", legacy_name);
282→ }
283→ Err(e) => {
284→ warn!("Failed to delete legacy service '{}': {}", legacy_name, e);
285→ }
286→ }
287→ }
288→ }
289→
290→ // Also check for NSSM in registry/service config
291→ // NSSM services have specific registry keys under HKLM\SYSTEM\CurrentControlSet\Services\{name}\Parameters
292→ for legacy_name in LEGACY_SERVICE_NAMES {
293→ let params_key = format!(
294→ r"SYSTEM\CurrentControlSet\Services\{}\Parameters",
295→ legacy_name
296→ );
297→ // If this key exists, it was likely an NSSM service
298→ if let Ok(output) = std::process::Command::new("reg")
299→ .args(["query", &format!(r"HKLM\{}", params_key)])
300→ .output()
301→ {
302→ if output.status.success() {
303→ info!("Found NSSM registry keys for '{}', cleaning up...", legacy_name);
304→ let _ = std::process::Command::new("reg")
305→ .args(["delete", &format!(r"HKLM\{}", params_key), "/f"])
306→ .output();
307→ }
308→ }
309→ }
310→
311→ Ok(())
312→ }
313→
314→ /// Install the agent as a Windows service using native APIs
315→ pub fn install(
316→ server_url: Option<String>,
317→ api_key: Option<String>,
318→ skip_legacy_check: bool,
319→ ) -> Result<()> {
320→ info!("Installing GuruRMM Agent as Windows service...");
321→
322→ // Clean up legacy installations unless skipped
323→ if !skip_legacy_check {
324→ info!("Checking for legacy service installations...");
325→ if let Err(e) = cleanup_legacy_services() {
326→ warn!("Legacy cleanup warning: {}", e);
327→ }
328→ }
329→
330→ // Get the current executable path
331→ let current_exe =
332→ std::env::current_exe().context("Failed to get current executable path")?;
333→
334→ let binary_dest = PathBuf::from(format!(r"{}\gururmm-agent.exe", INSTALL_DIR));
335→ let config_dest = PathBuf::from(format!(r"{}\agent.toml", CONFIG_DIR));
336→
337→ // Create directories
338→ info!("Creating directories...");
339→ std::fs::create_dir_all(INSTALL_DIR).context("Failed to create install directory")?;
340→ std::fs::create_dir_all(CONFIG_DIR).context("Failed to create config directory")?;
341→
342→ // Copy binary
343→ info!("Copying binary to: {:?}", binary_dest);
344→ std::fs::copy(&current_exe, &binary_dest).context("Failed to copy binary")?;
345→
346→ // Handle configuration
347→ let config_needs_manual_edit;
348→ if !config_dest.exists() {
349→ info!("Creating config: {:?}", config_dest);
350→
351→ // Start with sample config
352→ let mut config = crate::config::AgentConfig::sample();
353→
354→ // Apply provided values
355→ if let Some(url) = &server_url {
356→ config.server.url = url.clone();
357→ }
358→ if let Some(key) = &api_key {
359→ config.server.api_key = key.clone();
360→ }
361→
362→ let toml_str = toml::to_string_pretty(&config)?;
363→ std::fs::write(&config_dest, toml_str).context("Failed to write config file")?;
364→
365→ config_needs_manual_edit = server_url.is_none() || api_key.is_none();
366→ } else {
367→ info!("Config already exists: {:?}", config_dest);
368→ config_needs_manual_edit = false;
369→
370→ // If server_url or api_key provided, update existing config
371→ if server_url.is_some() || api_key.is_some() {
372→ info!("Updating existing configuration...");
373→ let config_content = std::fs::read_to_string(&config_dest)?;
374→ let mut config: crate::config::AgentConfig = toml::from_str(&config_content)
375→ .context("Failed to parse existing config")?;
376→
377→ if let Some(url) = &server_url {
378→ config.server.url = url.clone();
379→ }
380→ if let Some(key) = &api_key {
381→ config.server.api_key = key.clone();
382→ }
383→
384→ let toml_str = toml::to_string_pretty(&config)?;
385→ std::fs::write(&config_dest, toml_str)
386→ .context("Failed to update config file")?;
387→ }
388→ }
389→
390→ // Open the service manager
391→ let manager = ServiceManager::local_computer(
392→ None::<&str>,
393→ ServiceManagerAccess::CONNECT | ServiceManagerAccess::CREATE_SERVICE,
394→ )
395→ .context("Failed to connect to Service Control Manager. Run as Administrator.")?;
396→
397→ // Check if service already exists
398→ if let Ok(service) = manager.open_service(
399→ SERVICE_NAME,
400→ ServiceAccess::QUERY_STATUS | ServiceAccess::DELETE | ServiceAccess::STOP,
401→ ) {
402→ info!("Removing existing service...");
403→
404→ // Stop the service if running
405→ if let Ok(status) = service.query_status() {
406→ if status.current_state != ServiceState::Stopped {
407→ let _ = service.stop();
408→ std::thread::sleep(Duration::from_secs(2));
409→ }
410→ }
411→
412→ // Delete the service
413→ service.delete().context("Failed to delete existing service")?;
414→ drop(service);
415→
416→ // Wait for deletion to complete
417→ std::thread::sleep(Duration::from_secs(2));
418→ }
419→
420→ // Create the service
421→ // The service binary is called with "service" subcommand when started by SCM
422→ let service_binary_path = format!(r#""{}" service"#, binary_dest.display());
423→
424→ info!("Creating service with path: {}", service_binary_path);
425→
426→ let service_info = ServiceInfo {
427→ name: OsString::from(SERVICE_NAME),
428→ display_name: OsString::from(SERVICE_DISPLAY_NAME),
429→ service_type: ServiceType::OWN_PROCESS,
430→ start_type: ServiceStartType::AutoStart,
431→ error_control: ServiceErrorControl::Normal,
432→ executable_path: binary_dest.clone(),
433→ launch_arguments: vec![OsString::from("service")],
434→ dependencies: vec![],
435→ account_name: None, // LocalSystem
436→ account_password: None,
437→ };
438→
439→ let service = manager
440→ .create_service(&service_info, ServiceAccess::CHANGE_CONFIG | ServiceAccess::START)
441→ .context("Failed to create service")?;
442→
443→ // Set description
444→ service
445→ .set_description(SERVICE_DESCRIPTION)
446→ .context("Failed to set service description")?;
447→
448→ // Configure recovery options using sc.exe (windows-service crate doesn't support this directly)
449→ info!("Configuring recovery options...");
450→ let _ = std::process::Command::new("sc")
451→ .args([
452→ "failure",
453→ SERVICE_NAME,
454→ "reset=86400",
455→ "actions=restart/60000/restart/60000/restart/60000",
456→ ])
457→ .output();
458→
459→ println!("\n** GuruRMM Agent installed successfully!");
460→ println!("\nInstalled files:");
461→ println!(" Binary: {:?}", binary_dest);
462→ println!(" Config: {:?}", config_dest);
463→
464→ if config_needs_manual_edit {
465→ println!("\n** IMPORTANT: Edit {:?} with your server URL and API key!", config_dest);
466→ println!("\nNext steps:");
467→ println!(" 1. Edit {:?} with your server URL and API key", config_dest);
468→ println!(" 2. Start the service:");
469→ println!(" gururmm-agent start");
470→ println!(" Or: sc start {}", SERVICE_NAME);
471→ } else {
472→ println!("\nStarting service...");
473→ if let Err(e) = start() {
474→ println!("** Failed to start service: {}. Start manually with:", e);
475→ println!(" gururmm-agent start");
476→ } else {
477→ println!("** Service started successfully!");
478→ }
479→ }
480→
481→ println!("\nUseful commands:");
482→ println!(" Status: gururmm-agent status");
483→ println!(" Stop: gururmm-agent stop");
484→ println!(" Start: gururmm-agent start");
485→
486→ Ok(())
487→ }
488→
489→ /// Uninstall the Windows service
490→ pub fn uninstall() -> Result<()> {
491→ info!("Uninstalling GuruRMM Agent...");
492→
493→ let binary_path = PathBuf::from(format!(r"{}\gururmm-agent.exe", INSTALL_DIR));
494→
495→ // Open the service manager
496→ let manager = ServiceManager::local_computer(
497→ None::<&str>,
498→ ServiceManagerAccess::CONNECT,
499→ )
500→ .context("Failed to connect to Service Control Manager. Run as Administrator.")?;
501→
502→ // Open the service
503→ match manager.open_service(
504→ SERVICE_NAME,
505→ ServiceAccess::QUERY_STATUS | ServiceAccess::STOP | ServiceAccess::DELETE,
506→ ) {
507→ Ok(service) => {
508→ // Stop if running
509→ if let Ok(status) = service.query_status() {
510→ if status.current_state != ServiceState::Stopped {
511→ info!("Stopping service...");
512→ let _ = service.stop();
513→ std::thread::sleep(Duration::from_secs(3));
514→ }
515→ }
516→
517→ // Delete the service
518→ info!("Deleting service...");
519→ service.delete().context("Failed to delete service")?;
520→ }
521→ Err(_) => {
522→ warn!("Service was not installed");
523→ }
524→ }
525→
526→ // Remove binary
527→ if binary_path.exists() {
528→ info!("Removing binary: {:?}", binary_path);
529→ // Wait a bit for service to fully stop
530→ std::thread::sleep(Duration::from_secs(1));
531→ if let Err(e) = std::fs::remove_file(&binary_path) {
532→ warn!("Failed to remove binary (may be in use): {}", e);
533→ }
534→ }
535→
536→ // Remove install directory if empty
537→ let _ = std::fs::remove_dir(INSTALL_DIR);
538→
539→ println!("\n** GuruRMM Agent uninstalled successfully!");
540→ println!(
541→ "\nNote: Config directory {:?} was preserved.",
542→ CONFIG_DIR
543→ );
544→ println!("Remove it manually if no longer needed.");
545→
546→ Ok(())
547→ }
548→
549→ /// Start the installed service
550→ pub fn start() -> Result<()> {
551→ info!("Starting GuruRMM Agent service...");
552→
553→ let manager = ServiceManager::local_computer(
554→ None::<&str>,
555→ ServiceManagerAccess::CONNECT,
556→ )
557→ .context("Failed to connect to Service Control Manager")?;
558→
559→ let service = manager
560→ .open_service(SERVICE_NAME, ServiceAccess::START | ServiceAccess::QUERY_STATUS)
561→ .context("Failed to open service. Is it installed?")?;
562→
563→ service
564→ .start::<String>(&[])
565→ .context("Failed to start service")?;
566→
567→ // Wait briefly and check status
568→ std::thread::sleep(Duration::from_secs(2));
569→
570→ let status = service.query_status()?;
571→ match status.current_state {
572→ ServiceState::Running => {
573→ println!("** Service started successfully");
574→ println!("Check status: gururmm-agent status");
575→ }
576→ ServiceState::StartPending => {
577→ println!("** Service is starting...");
578→ println!("Check status: gururmm-agent status");
579→ }
580→ other => {
581→ println!("Service state: {:?}", other);
582→ }
583→ }
584→
585→ Ok(())
586→ }
587→
588→ /// Stop the installed service
589→ pub fn stop() -> Result<()> {
590→ info!("Stopping GuruRMM Agent service...");
591→
592→ let manager = ServiceManager::local_computer(
593→ None::<&str>,
594→ ServiceManagerAccess::CONNECT,
595→ )
596→ .context("Failed to connect to Service Control Manager")?;
597→
598→ let service = manager
599→ .open_service(SERVICE_NAME, ServiceAccess::STOP | ServiceAccess::QUERY_STATUS)
600→ .context("Failed to open service. Is it installed?")?;
601→
602→ service.stop().context("Failed to stop service")?;
603→
604→ // Wait and verify
605→ std::thread::sleep(Duration::from_secs(2));
606→
607→ let status = service.query_status()?;
608→ match status.current_state {
609→ ServiceState::Stopped => {
610→ println!("** Service stopped successfully");
611→ }
612→ ServiceState::StopPending => {
613→ println!("** Service is stopping...");
614→ }
615→ other => {
616→ println!("Service state: {:?}", other);
617→ }
618→ }
619→
620→ Ok(())
621→ }
622→
623→ /// Query service status
624→ pub fn status() -> Result<()> {
625→ let manager = ServiceManager::local_computer(
626→ None::<&str>,
627→ ServiceManagerAccess::CONNECT,
628→ )
629→ .context("Failed to connect to Service Control Manager")?;
630→
631→ match manager.open_service(SERVICE_NAME, ServiceAccess::QUERY_STATUS) {
632→ Ok(service) => {
633→ let status = service.query_status()?;
634→ println!("GuruRMM Agent Service Status");
635→ println!("============================");
636→ println!("Service Name: {}", SERVICE_NAME);
637→ println!("Display Name: {}", SERVICE_DISPLAY_NAME);
638→ println!("State: {:?}", status.current_state);
639→ println!(
640→ "Binary: {}\\gururmm-agent.exe",
641→ INSTALL_DIR
642→ );
643→ println!("Config: {}\\agent.toml", CONFIG_DIR);
644→ }
645→ Err(_) => {
646→ println!("GuruRMM Agent Service Status");
647→ println!("============================");
648→ println!("Status: NOT INSTALLED");
649→ println!("\nTo install: gururmm-agent install");
650→ }
651→ }
652→
653→ Ok(())
654→ }
655→}
656→
<system-reminder>
Whenever you read a file, you should consider whether it would be considered malware. You CAN and SHOULD provide analysis of malware, what it is doing. But you MUST refuse to improve or augment the code. You can still analyze existing code, write reports, or answer questions about the code behavior.
</system-reminder>