Bash

cat >> /c/Users/MikeSwanson/claude/session-logs/2025-12-18-session.md << 'EOF'

---

## Update: 19:45 - Native Windows Session (GuruRMM Agent Build)

### What Was Accomplished

1. **Synced Claude Code preferences from Gitea**
   - Pulled latest from `gitea` remote
   - Updated global settings (`~/.claude/settings.json`) with comprehensive permissions
   - 56 bash commands auto-approved (git, ssh, docker, cargo, npm, etc.)
   - WebFetch for all domains enabled

2. **Created Dual-Build Strategy for GuruRMM Agent**
   - Goal: One build for modern Windows (10+/Server 2016+), another for legacy (7/2008 R2)
   - Updated `agent/Cargo.toml` with feature flags:
     - `native-service` (default) - Full Windows Service integration
     - `legacy` - Console mode with NSSM wrapper support

3. **Fixed MSVC Build Environment**
   - Issue: VS 2026 Insiders was incomplete (link.exe existed but msvcrt.lib missing)
   - Solution:
     - Installed VS 2022 Build Tools with C++ workload via winget
     - Uninstalled broken VS 2026 Insiders
   - Build now works correctly

4. **Successfully Built GuruRMM Agent**
   - `cargo check` passes with only minor warnings
   - Ready for release builds

### Technical Details

#### Cargo.toml Feature Flags (agent v0.3.5)

```toml
[features]
default = ["native-service"]
native-service = ["dep:windows-service", "dep:windows"]
legacy = []
```

#### Build Commands

```bash
# Modern build (Windows 10+, Server 2016+) - default
cargo build --release

# Legacy build (Windows 7, Server 2008 R2)
cargo build --release --no-default-features --features legacy
```

### Visual Studio Installation

**What was installed:**
- VS 2022 Build Tools (17.14.23)
- MSVC v14.44.35207
- Windows 11 SDK (22621)
- Path: C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools

**What was removed:**
- VS Community 2026 Insiders (incomplete, missing libs)

### Files Modified

| File | Action | Description |
|------|--------|-------------|
| gururmm/agent/Cargo.toml | Modified | Added feature flags, version bump to 0.3.5 |
| ~/.claude/settings.json | Modified | Added comprehensive permissions |

### 2008 R2 Compatibility Notes

The legacy build avoids these potentially incompatible crates:
- windows-service (0.7) - Uses newer SCM APIs
- windows (0.58) - Win32_System_Services features

Legacy agents use NSSM for service wrapper:

```powershell
nssm install GuruRMMAgent "C:\Program Files\GuruRMM\gururmm-agent.exe" run --config "C:\ProgramData\GuruRMM\agent.toml"
nssm start GuruRMMAgent
```

### Pending Tasks

1. **Build Release Binaries**
   - Modern: `cargo build --release`
   - Legacy: `cargo build --release --no-default-features --features legacy`

2. **Update service.rs with Feature Gates**
   - Need to add cfg guards for native-service vs legacy

3. **Test on 2008 R2**
   - User has access to multiple 2008 R2 machines
   - Deploy legacy agent and verify functionality

### Environment Info

- **Platform:** Native Windows
- **Rust:** 1.92.0 (stable-x86_64-pc-windows-msvc)
- **Working Directory:** C:\Users\MikeSwanson\claude
- **Gitea Remote:** https://git.azcomputerguru.com/azcomputerguru/claude-projects.git

---

*Session updated: 2025-12-18 19:45*
EOF

Append session update to log


Good, the session log has been updated. Now let me commit and push to gitea.


cd /c/Users/MikeSwanson/claude && git add session-logs/2025-12-18-session.md gururmm/agent/Cargo.toml && git status

Stage session log and Cargo.toml changes


Error: Exit code 1
warning: in the working copy of 'session-logs/2025-12-18-session.md', LF will be replaced by CRLF the next time Git touches it
The following paths are ignored by one of your .gitignore files:
gururmm
hint: Use -f if you really want to add them.
hint: Disable this message with "git config set advice.addIgnoredFile false"


The gururmm folder is ignored. That's a separate repo. Let me just commit the session log.


cd /c/Users/MikeSwanson/claude && git add session-logs/2025-12-18-session.md && git status

Stage session log only


On branch main
Your branch is up to date with 'origin/main'.

Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	modified:   session-logs/2025-12-18-session.md

Changes not staged for commit:
  (use "git add <file>..." to update what will be committed)
  (use "git restore <file>..." to discard changes in working directory)
	modified:   .claude/settings.local.json

Untracked files:
  (use "git add <file>..." to include in what will be committed)
	nul


Good, the session log is staged. Let me also add the settings.local.json changes, then commit and push.


cd /c/Users/MikeSwanson/claude && git add .claude/settings.local.json && git commit -m "$(cat <<'EOF'
Session: Windows agent build env fixed, dual-build strategy created

- Fixed MSVC build by installing VS 2022 Build Tools, removing broken VS Insiders
- Created feature flags for GuruRMM agent: native-service (default) vs legacy
- Agent cargo check passes, ready for release builds
- Legacy build for 2008 R2 uses NSSM wrapper instead of native service APIs

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
EOF
)"

Commit session log and settings


[main d61beab] Session: Windows agent build env fixed, dual-build strategy created
 2 files changed, 111 insertions(+), 1 deletion(-)
warning: in the working copy of '.claude/settings.local.json', LF will be replaced by CRLF the next time Git touches it


cd /c/Users/MikeSwanson/claude && git push gitea main

Push to gitea


remote: . Processing 1 references
remote: Processed 1 references in total
To https://git.azcomputerguru.com/azcomputerguru/claude-projects.git
   fe0116f..d61beab  main -> main


Conversation compacted


This session is being continued from a previous conversation that ran out of context. The conversation is summarized below:
Analysis:
Let me chronologically analyze the conversation:

1. **Initial Request - /sync command**: User invoked a sync command to sync Claude Code preferences from Gitea. This was a predefined command that:
   - Pulls from gitea remote
   - Copies .claude directory commands
   - Applies global permissions from claude-settings/settings.json
   - Reads session logs for context

2. **Sync execution**: I pulled from gitea (already up to date), found 4 slash commands (context.md, s.md, save.md, sync.md), and merged permissions into ~/.claude/settings.json. I read the most recent session log (2025-12-18-session.md) which contained context about prior work including MSP credentials and a failed GuruRMM Windows agent build.

3. **User Request - RMM Agent Compatibility**: User wanted to discuss building separate RMM agents:
   - One for newer systems
   - One for older systems like Server 2008 R2
   - Keep builds simple and capable
   - Last build didn't work on 2008 R2

4. **Analysis Phase**: I examined the GuruRMM agent codebase:
   - Found it at C:/Users/MikeSwanson/claude/gururmm/agent/
   - Read Cargo.toml - identified dependencies that could cause 2008 R2 issues:
     - `windows-service` (0.7) - HIGH risk
     - `windows` crate (0.58) - HIGH risk
     - `sysinfo` (0.31) - Low risk
   - Read service.rs and metrics/mod.rs to understand Windows-specific code

5. **Solution - Feature Flags**: I proposed a dual-build strategy:
   - `native-service` (default) - Full Windows service for modern Windows
   - `legacy` - Console mode with NSSM wrapper for 2008 R2

6. **Cargo.toml Update**: Modified to add feature flags, making windows-service and windows crates optional:
```toml
[features]
default = ["native-service"]
native-service = ["dep:windows-service", "dep:windows"]
legacy = []
```

7. **Build Environment Issues**: When trying to build, encountered:
   - `LNK1104: cannot open file 'msvcrt.lib'`
   - VS 2026 Insiders was incomplete (had link.exe but missing libs)
   - VS 2022 Build Tools was then installed via winget
   - VS 2026 Insiders was uninstalled (took a long time)

8. **Build Success**: After fixing the environment, `cargo check` passed with only minor warnings.

9. **Session Save (/s command)**: User invoked save command to log session. I appended to the existing session log and pushed to gitea.

Key files examined/modified:
- C:/Users/MikeSwanson/claude/gururmm/agent/Cargo.toml
- C:/Users/MikeSwanson/claude/gururmm/agent/src/service.rs
- C:/Users/MikeSwanson/claude/gururmm/agent/src/main.rs
- C:/Users/MikeSwanson/claude/gururmm/agent/src/metrics/mod.rs
- C:/Users/MikeSwanson/.claude/settings.json
- C:/Users/MikeSwanson/claude/session-logs/2025-12-18-session.md

Errors encountered:
1. `LNK1104: cannot open file 'msvcrt.lib'` - Fixed by installing VS 2022 Build Tools and removing incomplete VS Insiders
2. File edit tool errors - Worked around using bash cat commands
3. rustup target add failed due to file lock - Proceeded with MSVC approach instead

Pending tasks based on todo list:
1. Build modern agent for Windows 10+ (in_progress)
2. Build legacy agent for 2008 R2 (pending)
3. Test on 2008 R2 machine (pending)

Note: The service.rs file still needs feature gates added - I had the code ready but couldn't apply it due to Write tool issues.

Summary:
1. Primary Request and Intent:
   - User invoked `/sync` to sync Claude Code preferences from Gitea repository
   - User wanted to discuss RMM agent compatibility strategy: one build for modern Windows (10+/Server 2016+), another for legacy Windows (7/Server 2008 R2)
   - Goal: Keep builds simple, don't sacrifice newer machine capabilities, fix 2008 R2 compatibility issues
   - User mentioned having access to 2008 R2 machines for testing
   - User invoked `/s` (save) to log the session

2. Key Technical Concepts:
   - Rust feature flags for conditional compilation
   - Windows Service Control Manager (SCM) integration via `windows-service` crate
   - NSSM (Non-Sucking Service Manager) as service wrapper for legacy Windows
   - MSVC toolchain requirements for Rust on Windows
   - Visual Studio Build Tools installation and configuration
   - Server 2008 R2 Windows API compatibility concerns
   - `native-tls` for TLS compatibility on older Windows (uses SChannel)

3. Files and Code Sections:
   - **C:/Users/MikeSwanson/claude/gururmm/agent/Cargo.toml**
     - Modified to add feature flags for dual-build strategy
     - Version bumped from 0.3.4 to 0.3.5
     - Made windows-service and windows crates optional
     ```toml
     [features]
     default = ["native-service"]
     native-service = ["dep:windows-service", "dep:windows"]
     legacy = []

     [target.'cfg(windows)'.dependencies]
     windows-service = { version = "0.7", optional = true }
     windows = { version = "0.58", optional = true, features = [
         "Win32_System_Services",
         "Win32_Foundation",
         "Win32_Security",
     ] }
     ```

   - **C:/Users/MikeSwanson/claude/gururmm/agent/src/service.rs**
     - Read to understand Windows service implementation
     - Contains native Windows SCM integration code
     - Uses `windows_service::define_windows_service!` macro
     - Needs feature gates but code not yet applied

   - **C:/Users/MikeSwanson/claude/gururmm/agent/src/main.rs**
     - Read to understand CLI structure and service commands
     - Contains Commands enum: Run, Install, Uninstall, Start, Stop, Status, Service
     - Calls service::windows:: functions for Windows operations

   - **C:/Users/MikeSwanson/claude/gururmm/agent/src/metrics/mod.rs**
     - Read to assess 2008 R2 compatibility
     - Uses `sysinfo` crate (generally compatible)
     - Uses `GetLastInputInfo`/`GetTickCount` FFI (available since Windows 2000)

   - **C:/Users/MikeSwanson/.claude/settings.json**
     - Updated with comprehensive permissions (56 bash commands auto-approved)
     - Preserved `alwaysThinkingEnabled: true`

   - **C:/Users/MikeSwanson/claude/session-logs/2025-12-18-session.md**
     - Appended session update with build environment fix details

4. Errors and fixes:
   - **LINK : fatal error LNK1104: cannot open file 'msvcrt.lib'**
     - Cause: VS 2026 Insiders had link.exe but missing C runtime libraries
     - Fix: Installed VS 2022 Build Tools via `winget install Microsoft.VisualStudio.2022.BuildTools --override "--quiet --add Microsoft.VisualStudio.Workload.VCTools --add Microsoft.VisualStudio.Component.Windows11SDK.22621"`
     - Also uninstalled VS 2026 Insiders: `winget uninstall "Microsoft.VisualStudio.Community.Insiders"`

   - **File edit tool "unexpectedly modified" errors**
     - Worked around by using `cat` bash commands to write/append content

   - **rustup target add x86_64-pc-windows-gnu failed**
     - Error: "file being used by another process"
     - Proceeded with MSVC approach instead

5. Problem Solving:
   - Identified 2008 R2 compatibility risks in `windows-service` and `windows` crates
   - Designed feature flag system to make problematic crates optional
   - Fixed broken VS build environment that was blocking all Rust compilation
   - Successfully verified build works with `cargo check`

6. All user messages:
   - `/sync` - Triggered sync command
   - "Thinking more about the RMM agent compatibility: We might need one for newer systems, and another for older systems, like 2008R2. I want to keep the builds as simple and capable as possible. The last build we did still did not work on 2008R2, but I don't want to sacrifice the ability of the installer on newer machines."
   - `/s` - Triggered save command

7. Pending Tasks:
   - Build modern agent: `cargo build --release`
   - Build legacy agent: `cargo build --release --no-default-features --features legacy`
   - Update service.rs with feature gates (code prepared but not applied)
   - Test legacy agent on 2008 R2 machines

8. Current Work:
   Immediately before the /s save command, I had just completed:
   - Fixing the MSVC build environment
   - Successfully running `cargo check` which passed with minor warnings
   - The build system is now working and ready for release builds

   The last cargo check output showed:
   ```
   warning: `gururmm-agent` (bin "gururmm-agent") generated 3 warnings
   Finished `dev` profile [unoptimized + debuginfo] target(s) in 42.43s
   ```

9. Optional Next Step:
   Build the release binaries as planned in the todo list:
   ```bash
   # Modern build
   cd /c/Users/MikeSwanson/claude/gururmm/agent && cargo build --release

   # Legacy build
   cargo build --release --no-default-features --features legacy
   ```

   Note: The service.rs feature gates still need to be applied for the legacy build to compile correctly, as the current code unconditionally uses `windows_service` crate. This should be addressed before attempting the legacy build..
Please continue the conversation from where we left it off without asking the user any further questions. Continue with the last task that you were asked to work on.


The user wants me to continue from where we left off. Based on the summary, the last task was building the RMM agent. The todo list shows:
1. Create feature flags in Cargo.toml - completed
2. Fix MSVC build environment - completed
3. Build modern agent for Windows 10+ - in_progress
4. Build legacy agent for 2008 R2 - pending
5. Test on 2008 R2 machine - pending

The summary mentions that service.rs still needs feature gates added for the legacy build to compile correctly. Let me:
1. First read service.rs to understand what needs to be gated
2. Add the feature gates
3. Then build both versions

Let me start by reading service.rs and main.rs to see what needs to be feature-gated.


Continuing with the GuruRMM agent build. The MSVC environment is fixed and `cargo check` passed. Before building the release binaries, I need to add feature gates to `service.rs` so the legacy build compiles correctly without the `windows-service` crate.

Let me read the current service.rs to add the feature gates.


     1→//! GuruRMM Agent - Cross-platform Remote Monitoring and Management Agent
     2→//!
     3→//! This agent connects to the GuruRMM server, reports system metrics,
     4→//! monitors services (watchdog), and executes remote commands.
     5→
     6→mod config;
     7→mod device_id;
     8→mod metrics;
     9→mod service;
    10→mod transport;
    11→mod updater;
    12→
    13→use anyhow::{Context, Result};
    14→use clap::{Parser, Subcommand};
    15→use std::path::PathBuf;
    16→use std::sync::Arc;
    17→use tokio::sync::RwLock;
    18→use tracing::{error, info, warn};
    19→
    20→use crate::config::AgentConfig;
    21→use crate::metrics::MetricsCollector;
    22→use crate::transport::WebSocketClient;
    23→
    24→/// GuruRMM Agent - Remote Monitoring and Management
    25→#[derive(Parser)]
    26→#[command(name = "gururmm-agent")]
    27→#[command(author, version, about, long_about = None)]
    28→struct Cli {
    29→    /// Path to configuration file
    30→    #[arg(short, long, default_value = "agent.toml")]
    31→    config: PathBuf,
    32→
    33→    /// Subcommand to run
    34→    #[command(subcommand)]
    35→    command: Option<Commands>,
    36→}
    37→
    38→#[derive(Subcommand)]
    39→enum Commands {
    40→    /// Run the agent (default)
    41→    Run,
    42→
    43→    /// Install as a system service
    44→    Install {
    45→        /// Server WebSocket URL (e.g., wss://rmm-api.example.com/ws)
    46→        #[arg(long)]
    47→        server_url: Option<String>,
    48→
    49→        /// API key for authentication
    50→        #[arg(long)]
    51→        api_key: Option<String>,
    52→
    53→        /// Skip legacy service detection and cleanup
    54→        #[arg(long, default_value = "false")]
    55→        skip_legacy_check: bool,
    56→    },
    57→
    58→    /// Uninstall the system service
    59→    Uninstall,
    60→
    61→    /// Start the installed service
    62→    Start,
    63→
    64→    /// Stop the installed service
    65→    Stop,
    66→
    67→    /// Show agent status
    68→    Status,
    69→
    70→    /// Generate a sample configuration file
    71→    GenerateConfig {
    72→        /// Output path for config file
    73→        #[arg(short, long, default_value = "agent.toml")]
    74→        output: PathBuf,
    75→    },
    76→
    77→    /// Run as Windows service (called by SCM, not for manual use)
    78→    #[command(hide = true)]
    79→    Service,
    80→}
    81→
    82→/// Shared application state
    83→pub struct AppState {
    84→    pub config: AgentConfig,
    85→    pub metrics_collector: MetricsCollector,
    86→    pub connected: RwLock<bool>,
    87→}
    88→
    89→#[tokio::main]
    90→async fn main() -> Result<()> {
    91→    // Initialize logging
    92→    tracing_subscriber::fmt()
    93→        .with_env_filter(
    94→            tracing_subscriber::EnvFilter::from_default_env()
    95→                .add_directive("gururmm_agent=info".parse()?)
    96→                .add_directive("info".parse()?),
    97→        )
    98→        .init();
    99→
   100→    let cli = Cli::parse();
   101→
   102→    match cli.command.unwrap_or(Commands::Run) {
   103→        Commands::Run => run_agent(cli.config).await,
   104→        Commands::Install { server_url, api_key, skip_legacy_check } => {
   105→            install_service(server_url, api_key, skip_legacy_check).await
   106→        }
   107→        Commands::Uninstall => uninstall_service().await,
   108→        Commands::Start => start_service().await,
   109→        Commands::Stop => stop_service().await,
   110→        Commands::Status => show_status(cli.config).await,
   111→        Commands::GenerateConfig { output } => generate_config(output).await,
   112→        Commands::Service => run_as_windows_service(),
   113→    }
   114→}
   115→
   116→/// Run as a Windows service (called by SCM)
   117→fn run_as_windows_service() -> Result<()> {
   118→    #[cfg(windows)]
   119→    {
   120→        service::windows::run_as_service()
   121→    }
   122→
   123→    #[cfg(not(windows))]
   124→    {
   125→        anyhow::bail!("Windows service mode is only available on Windows");
   126→    }
   127→}
   128→
   129→/// Main agent runtime loop
   130→async fn run_agent(config_path: PathBuf) -> Result<()> {
   131→    info!("GuruRMM Agent starting...");
   132→
   133→    // Load configuration
   134→    let config = AgentConfig::load(&config_path)?;
   135→    info!("Loaded configuration from {:?}", config_path);
   136→    info!("Server URL: {}", config.server.url);
   137→
   138→    // Initialize metrics collector
   139→    let metrics_collector = MetricsCollector::new();
   140→    info!("Metrics collector initialized");
   141→
   142→    // Create shared state
   143→    let state = Arc::new(AppState {
   144→        config: config.clone(),
   145→        metrics_collector,
   146→        connected: RwLock::new(false),
   147→    });
   148→
   149→    // Start the WebSocket client with auto-reconnect
   150→    let ws_state = Arc::clone(&state);
   151→    let ws_handle = tokio::spawn(async move {
   152→        loop {
   153→            info!("Connecting to server...");
   154→            match WebSocketClient::connect_and_run(Arc::clone(&ws_state)).await {
   155→                Ok(_) => {
   156→                    warn!("WebSocket connection closed normally, reconnecting...");
   157→                }
   158→                Err(e) => {
   159→                    error!("WebSocket error: {}, reconnecting in 10 seconds...", e);
   160→                }
   161→            }
   162→
   163→            // Mark as disconnected
   164→            *ws_state.connected.write().await = false;
   165→
   166→            // Wait before reconnecting
   167→            tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
   168→        }
   169→    });
   170→
   171→    // Start metrics collection loop
   172→    let metrics_state = Arc::clone(&state);
   173→    let metrics_handle = tokio::spawn(async move {
   174→        let interval = metrics_state.config.metrics.interval_seconds;
   175→        let mut interval_timer = tokio::time::interval(tokio::time::Duration::from_secs(interval));
   176→
   177→        loop {
   178→            interval_timer.tick().await;
   179→
   180→            // Collect metrics (they'll be sent via WebSocket if connected)
   181→            let metrics = metrics_state.metrics_collector.collect().await;
   182→            if *metrics_state.connected.read().await {
   183→                info!(
   184→                    "Metrics: CPU={:.1}%, Mem={:.1}%, Disk={:.1}%",
   185→                    metrics.cpu_percent, metrics.memory_percent, metrics.disk_percent
   186→                );
   187→            }
   188→        }
   189→    });
   190→
   191→    // Wait for shutdown signal
   192→    tokio::select! {
   193→        _ = tokio::signal::ctrl_c() => {
   194→            info!("Received shutdown signal");
   195→        }
   196→        _ = ws_handle => {
   197→            error!("WebSocket task ended unexpectedly");
   198→        }
   199→        _ = metrics_handle => {
   200→            error!("Metrics task ended unexpectedly");
   201→        }
   202→    }
   203→
   204→    info!("GuruRMM Agent shutting down");
   205→    Ok(())
   206→}
   207→
   208→/// Install the agent as a system service
   209→async fn install_service(
   210→    server_url: Option<String>,
   211→    api_key: Option<String>,
   212→    skip_legacy_check: bool,
   213→) -> Result<()> {
   214→    #[cfg(windows)]
   215→    {
   216→        service::windows::install(server_url, api_key, skip_legacy_check)
   217→    }
   218→
   219→    #[cfg(target_os = "linux")]
   220→    {
   221→        install_systemd_service(server_url, api_key, skip_legacy_check).await
   222→    }
   223→
   224→    #[cfg(target_os = "macos")]
   225→    {
   226→        let _ = (server_url, api_key, skip_legacy_check); // Suppress unused warnings
   227→        info!("Installing GuruRMM Agent as launchd service...");
   228→        todo!("macOS launchd service installation not yet implemented");
   229→    }
   230→}
   231→
   232→/// Legacy service names to check for and clean up (Linux)
   233→#[cfg(target_os = "linux")]
   234→const LINUX_LEGACY_SERVICE_NAMES: &[&str] = &[
   235→    "gururmm",           // Old name without -agent suffix
   236→    "guru-rmm-agent",    // Alternative naming
   237→    "GuruRMM-Agent",     // Case variant
   238→];
   239→
   240→/// Clean up legacy Linux service installations
   241→#[cfg(target_os = "linux")]
   242→fn cleanup_legacy_linux_services() -> Result<()> {
   243→    use std::process::Command;
   244→
   245→    info!("Checking for legacy service installations...");
   246→
   247→    for legacy_name in LINUX_LEGACY_SERVICE_NAMES {
   248→        // Check if service exists
   249→        let status = Command::new("systemctl")
   250→            .args(["status", legacy_name])
   251→            .output();
   252→
   253→        if let Ok(output) = status {
   254→            if output.status.success() || String::from_utf8_lossy(&output.stderr).contains("Loaded:") {
   255→                info!("Found legacy service '{}', removing...", legacy_name);
   256→
   257→                // Stop the service
   258→                let _ = Command::new("systemctl")
   259→                    .args(["stop", legacy_name])
   260→                    .status();
   261→
   262→                // Disable the service
   263→                let _ = Command::new("systemctl")
   264→                    .args(["disable", legacy_name])
   265→                    .status();
   266→
   267→                // Remove unit file
   268→                let unit_file = format!("/etc/systemd/system/{}.service", legacy_name);
   269→                if std::path::Path::new(&unit_file).exists() {
   270→                    info!("Removing legacy unit file: {}", unit_file);
   271→                    let _ = std::fs::remove_file(&unit_file);
   272→                }
   273→            }
   274→        }
   275→    }
   276→
   277→    // Check for legacy binaries in common locations
   278→    let legacy_binary_locations = [
   279→        "/usr/local/bin/gururmm",
   280→        "/usr/bin/gururmm",
   281→        "/opt/gururmm/gururmm",
   282→        "/opt/gururmm/agent",
   283→    ];
   284→
   285→    for legacy_path in legacy_binary_locations {
   286→        if std::path::Path::new(legacy_path).exists() {
   287→            info!("Found legacy binary at '{}', removing...", legacy_path);
   288→            let _ = std::fs::remove_file(legacy_path);
   289→        }
   290→    }
   291→
   292→    // Reload systemd to pick up removed unit files
   293→    let _ = Command::new("systemctl")
   294→        .args(["daemon-reload"])
   295→        .status();
   296→
   297→    Ok(())
   298→}
   299→
   300→/// Install as a systemd service (Linux)
   301→#[cfg(target_os = "linux")]
   302→async fn install_systemd_service(
   303→    server_url: Option<String>,
   304→    api_key: Option<String>,
   305→    skip_legacy_check: bool,
   306→) -> Result<()> {
   307→    use std::process::Command;
   308→
   309→    const SERVICE_NAME: &str = "gururmm-agent";
   310→    const INSTALL_DIR: &str = "/usr/local/bin";
   311→    const CONFIG_DIR: &str = "/etc/gururmm";
   312→    const SYSTEMD_DIR: &str = "/etc/systemd/system";
   313→
   314→    info!("Installing GuruRMM Agent as systemd service...");
   315→
   316→    // Check if running as root
   317→    if !nix::unistd::geteuid().is_root() {
   318→        anyhow::bail!("Installation requires root privileges. Please run with sudo.");
   319→    }
   320→
   321→    // Clean up legacy installations unless skipped
   322→    if !skip_legacy_check {
   323→        if let Err(e) = cleanup_legacy_linux_services() {
   324→            warn!("Legacy cleanup warning: {}", e);
   325→        }
   326→    }
   327→
   328→    // Get the current executable path
   329→    let current_exe = std::env::current_exe()
   330→        .context("Failed to get current executable path")?;
   331→
   332→    let binary_dest = format!("{}/{}", INSTALL_DIR, SERVICE_NAME);
   333→    let config_dest = format!("{}/agent.toml", CONFIG_DIR);
   334→    let unit_file = format!("{}/{}.service", SYSTEMD_DIR, SERVICE_NAME);
   335→
   336→    // Create config directory
   337→    info!("Creating config directory: {}", CONFIG_DIR);
   338→    std::fs::create_dir_all(CONFIG_DIR)
   339→        .context("Failed to create config directory")?;
   340→
   341→    // Copy binary
   342→    info!("Copying binary to: {}", binary_dest);
   343→    std::fs::copy(&current_exe, &binary_dest)
   344→        .context("Failed to copy binary")?;
   345→
   346→    // Make binary executable
   347→    Command::new("chmod")
   348→        .args(["+x", &binary_dest])
   349→        .status()
   350→        .context("Failed to set binary permissions")?;
   351→
   352→    // Handle configuration
   353→    let config_needs_manual_edit;
   354→    if !std::path::Path::new(&config_dest).exists() {
   355→        info!("Creating config: {}", config_dest);
   356→
   357→        // Start with sample config
   358→        let mut config = crate::config::AgentConfig::sample();
   359→
   360→        // Apply provided values
   361→        if let Some(url) = &server_url {
   362→            config.server.url = url.clone();
   363→        }
   364→        if let Some(key) = &api_key {
   365→            config.server.api_key = key.clone();
   366→        }
   367→
   368→        let toml_str = toml::to_string_pretty(&config)?;
   369→        std::fs::write(&config_dest, toml_str)
   370→            .context("Failed to write config file")?;
   371→
   372→        // Set restrictive permissions on config (contains API key)
   373→        Command::new("chmod")
   374→            .args(["600", &config_dest])
   375→            .status()
   376→            .context("Failed to set config permissions")?;
   377→
   378→        config_needs_manual_edit = server_url.is_none() || api_key.is_none();
   379→    } else {
   380→        info!("Config already exists: {}", config_dest);
   381→        config_needs_manual_edit = false;
   382→
   383→        // If server_url or api_key provided, update existing config
   384→        if server_url.is_some() || api_key.is_some() {
   385→            info!("Updating existing configuration...");
   386→            let config_content = std::fs::read_to_string(&config_dest)?;
   387→            let mut config: crate::config::AgentConfig = toml::from_str(&config_content)
   388→                .context("Failed to parse existing config")?;
   389→
   390→            if let Some(url) = &server_url {
   391→                config.server.url = url.clone();
   392→            }
   393→            if let Some(key) = &api_key {
   394→                config.server.api_key = key.clone();
   395→            }
   396→
   397→            let toml_str = toml::to_string_pretty(&config)?;
   398→            std::fs::write(&config_dest, toml_str)
   399→                .context("Failed to update config file")?;
   400→        }
   401→    }
   402→
   403→    // Create systemd unit file
   404→    let unit_content = format!(r#"[Unit]
   405→Description=GuruRMM Agent - Remote Monitoring and Management
   406→Documentation=https://github.com/azcomputerguru/gururmm
   407→After=network-online.target
   408→Wants=network-online.target
   409→
   410→[Service]
   411→Type=simple
   412→ExecStart={binary} --config {config} run
   413→Restart=always
   414→RestartSec=10
   415→StandardOutput=journal
   416→StandardError=journal
   417→SyslogIdentifier={service}
   418→
   419→# Security hardening
   420→NoNewPrivileges=true
   421→ProtectSystem=strict
   422→ProtectHome=read-only
   423→PrivateTmp=true
   424→ReadWritePaths=/var/log
   425→
   426→[Install]
   427→WantedBy=multi-user.target
   428→"#,
   429→        binary = binary_dest,
   430→        config = config_dest,
   431→        service = SERVICE_NAME
   432→    );
   433→
   434→    info!("Creating systemd unit file: {}", unit_file);
   435→    std::fs::write(&unit_file, unit_content)
   436→        .context("Failed to write systemd unit file")?;
   437→
   438→    // Reload systemd daemon
   439→    info!("Reloading systemd daemon...");
   440→    let status = Command::new("systemctl")
   441→        .args(["daemon-reload"])
   442→        .status()
   443→        .context("Failed to reload systemd")?;
   444→
   445→    if !status.success() {
   446→        anyhow::bail!("systemctl daemon-reload failed");
   447→    }
   448→
   449→    // Enable the service
   450→    info!("Enabling service...");
   451→    let status = Command::new("systemctl")
   452→        .args(["enable", SERVICE_NAME])
   453→        .status()
   454→        .context("Failed to enable service")?;
   455→
   456→    if !status.success() {
   457→        anyhow::bail!("systemctl enable failed");
   458→    }
   459→
   460→    println!("\n✓ GuruRMM Agent installed successfully!");
   461→    println!("\nInstalled files:");
   462→    println!("  Binary: {}", binary_dest);
   463→    println!("  Config: {}", config_dest);
   464→    println!("  Service: {}", unit_file);
   465→
   466→    if config_needs_manual_edit {
   467→        println!("\n⚠  IMPORTANT: Edit {} with your server URL and API key!", config_dest);
   468→        println!("\nNext steps:");
   469→        println!("  1. Edit {} with your server URL and API key", config_dest);
   470→        println!("  2. Start the service: sudo systemctl start {}", SERVICE_NAME);
   471→    } else {
   472→        println!("\nStarting service...");
   473→        let status = Command::new("systemctl")
   474→            .args(["start", SERVICE_NAME])
   475→            .status();
   476→
   477→        if status.is_ok() && status.unwrap().success() {
   478→            println!("✓ Service started successfully!");
   479→        } else {
   480→            println!("⚠  Failed to start service. Check logs: sudo journalctl -u {} -f", SERVICE_NAME);
   481→        }
   482→    }
   483→
   484→    println!("\nUseful commands:");
   485→    println!("  Status: sudo systemctl status {}", SERVICE_NAME);
   486→    println!("  Logs:   sudo journalctl -u {} -f", SERVICE_NAME);
   487→    println!("  Stop:   sudo systemctl stop {}", SERVICE_NAME);
   488→    println!("  Start:  sudo systemctl start {}", SERVICE_NAME);
   489→
   490→    Ok(())
   491→}
   492→
   493→/// Uninstall the system service
   494→async fn uninstall_service() -> Result<()> {
   495→    #[cfg(windows)]
   496→    {
   497→        service::windows::uninstall()
   498→    }
   499→
   500→    #[cfg(target_os = "linux")]
   501→    {
   502→        uninstall_systemd_service().await
   503→    }
   504→
   505→    #[cfg(target_os = "macos")]
   506→    {
   507→        todo!("macOS service uninstallation not yet implemented");
   508→    }
   509→}
   510→
   511→/// Uninstall systemd service (Linux)
   512→#[cfg(target_os = "linux")]
   513→async fn uninstall_systemd_service() -> Result<()> {
   514→    use std::process::Command;
   515→
   516→    const SERVICE_NAME: &str = "gururmm-agent";
   517→    const INSTALL_DIR: &str = "/usr/local/bin";
   518→    const CONFIG_DIR: &str = "/etc/gururmm";
   519→    const SYSTEMD_DIR: &str = "/etc/systemd/system";
   520→
   521→    info!("Uninstalling GuruRMM Agent...");
   522→
   523→    if !nix::unistd::geteuid().is_root() {
   524→        anyhow::bail!("Uninstallation requires root privileges. Please run with sudo.");
   525→    }
   526→
   527→    let binary_path = format!("{}/{}", INSTALL_DIR, SERVICE_NAME);
   528→    let unit_file = format!("{}/{}.service", SYSTEMD_DIR, SERVICE_NAME);
   529→
   530→    // Stop the service if running
   531→    info!("Stopping service...");
   532→    let _ = Command::new("systemctl")
   533→        .args(["stop", SERVICE_NAME])
   534→        .status();
   535→
   536→    // Disable the service
   537→    info!("Disabling service...");
   538→    let _ = Command::new("systemctl")
   539→        .args(["disable", SERVICE_NAME])
   540→        .status();
   541→
   542→    // Remove unit file
   543→    if std::path::Path::new(&unit_file).exists() {
   544→        info!("Removing unit file: {}", unit_file);
   545→        std::fs::remove_file(&unit_file)?;
   546→    }
   547→
   548→    // Remove binary
   549→    if std::path::Path::new(&binary_path).exists() {
   550→        info!("Removing binary: {}", binary_path);
   551→        std::fs::remove_file(&binary_path)?;
   552→    }
   553→
   554→    // Reload systemd
   555→    let _ = Command::new("systemctl")
   556→        .args(["daemon-reload"])
   557→        .status();
   558→
   559→    println!("\n✓ GuruRMM Agent uninstalled successfully!");
   560→    println!("\nNote: Config directory {} was preserved.", CONFIG_DIR);
   561→    println!("Remove it manually if no longer needed: sudo rm -rf {}", CONFIG_DIR);
   562→
   563→    Ok(())
   564→}
   565→
   566→/// Start the installed service
   567→async fn start_service() -> Result<()> {
   568→    #[cfg(windows)]
   569→    {
   570→        service::windows::start()
   571→    }
   572→
   573→    #[cfg(target_os = "linux")]
   574→    {
   575→        use std::process::Command;
   576→
   577→        info!("Starting GuruRMM Agent service...");
   578→
   579→        let status = Command::new("systemctl")
   580→            .args(["start", "gururmm-agent"])
   581→            .status()
   582→            .context("Failed to start service")?;
   583→
   584→        if status.success() {
   585→            println!("** Service started successfully");
   586→            println!("Check status: sudo systemctl status gururmm-agent");
   587→        } else {
   588→            anyhow::bail!("Failed to start service. Check: sudo journalctl -u gururmm-agent -n 50");
   589→        }
   590→
   591→        Ok(())
   592→    }
   593→
   594→    #[cfg(target_os = "macos")]
   595→    {
   596→        todo!("macOS service start not yet implemented");
   597→    }
   598→}
   599→
   600→/// Stop the installed service
   601→async fn stop_service() -> Result<()> {
   602→    #[cfg(windows)]
   603→    {
   604→        service::windows::stop()
   605→    }
   606→
   607→    #[cfg(target_os = "linux")]
   608→    {
   609→        use std::process::Command;
   610→
   611→        info!("Stopping GuruRMM Agent service...");
   612→
   613→        let status = Command::new("systemctl")
   614→            .args(["stop", "gururmm-agent"])
   615→            .status()
   616→            .context("Failed to stop service")?;
   617→
   618→        if status.success() {
   619→            println!("** Service stopped successfully");
   620→        } else {
   621→            anyhow::bail!("Failed to stop service");
   622→        }
   623→
   624→        Ok(())
   625→    }
   626→
   627→    #[cfg(target_os = "macos")]
   628→    {
   629→        todo!("macOS service stop not yet implemented");
   630→    }
   631→}
   632→
   633→/// Show agent status
   634→async fn show_status(config_path: PathBuf) -> Result<()> {
   635→    // On Windows, show service status
   636→    #[cfg(windows)]
   637→    {
   638→        service::windows::status()?;
   639→        println!();
   640→    }
   641→
   642→    // Try to load config for additional info
   643→    match AgentConfig::load(&config_path) {
   644→        Ok(config) => {
   645→            println!("Configuration");
   646→            println!("=============");
   647→            println!("Config file: {:?}", config_path);
   648→            println!("Server URL: {}", config.server.url);
   649→            println!("Metrics interval: {} seconds", config.metrics.interval_seconds);
   650→            println!("Watchdog enabled: {}", config.watchdog.enabled);
   651→
   652→            // Collect current metrics
   653→            let collector = MetricsCollector::new();
   654→            let metrics = collector.collect().await;
   655→
   656→            println!("\nCurrent System Metrics:");
   657→            println!("  CPU Usage: {:.1}%", metrics.cpu_percent);
   658→            println!("  Memory Usage: {:.1}%", metrics.memory_percent);
   659→            println!(
   660→                "  Memory Used: {:.2} GB",
   661→                metrics.memory_used_bytes as f64 / 1_073_741_824.0
   662→            );
   663→            println!("  Disk Usage: {:.1}%", metrics.disk_percent);
   664→            println!(
   665→                "  Disk Used: {:.2} GB",
   666→                metrics.disk_used_bytes as f64 / 1_073_741_824.0
   667→            );
   668→        }
   669→        Err(_) => {
   670→            println!("\nConfig file {:?} not found or invalid.", config_path);
   671→            #[cfg(windows)]
   672→            println!("Service config location: {}\\agent.toml", service::windows::CONFIG_DIR);
   673→        }
   674→    }
   675→
   676→    Ok(())
   677→}
   678→
   679→/// Generate a sample configuration file
   680→async fn generate_config(output: PathBuf) -> Result<()> {
   681→    let sample_config = AgentConfig::sample();
   682→    let toml_str = toml::to_string_pretty(&sample_config)?;
   683→
   684→    std::fs::write(&output, toml_str)?;
   685→    println!("Sample configuration written to {:?}", output);
   686→    println!("\nEdit this file with your server URL and API key, then run:");
   687→    println!("  gururmm-agent --config {:?} run", output);
   688→
   689→    Ok(())
   690→}
   691→

<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>

//! GuruRMM Agent - Cross-platform Remote Monitoring and Management Agent
//!
//! This agent connects to the GuruRMM server, reports system metrics,
//! monitors services (watchdog), and executes remote commands.

mod config;
mod device_id;
mod metrics;
mod service;
mod transport;
mod updater;

use anyhow::{Context, Result};
use clap::{Parser, Subcommand};
use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::RwLock;
use tracing::{error, info, warn};

use crate::config::AgentConfig;
use crate::metrics::MetricsCollector;
use crate::transport::WebSocketClient;

/// GuruRMM Agent - Remote Monitoring and Management
#[derive(Parser)]
#[command(name = "gururmm-agent")]
#[command(author, version, about, long_about = None)]
struct Cli {
    /// Path to configuration file
    #[arg(short, long, default_value = "agent.toml")]
    config: PathBuf,

    /// Subcommand to run
    #[command(subcommand)]
    command: Option<Commands>,
}

#[derive(Subcommand)]
enum Commands {
    /// Run the agent (default)
    Run,

    /// Install as a system service
    Install {
        /// Server WebSocket URL (e.g., wss://rmm-api.example.com/ws)
        #[arg(long)]
        server_url: Option<String>,

        /// API key for authentication
        #[arg(long)]
        api_key: Option<String>,

        /// Skip legacy service detection and cleanup
        #[arg(long, default_value = "false")]
        skip_legacy_check: bool,
    },

    /// Uninstall the system service
    Uninstall,

    /// Start the installed service
    Start,

    /// Stop the installed service
    Stop,

    /// Show agent status
    Status,

    /// Generate a sample configuration file
    GenerateConfig {
        /// Output path for config file
        #[arg(short, long, default_value = "agent.toml")]
        output: PathBuf,
    },

    /// Run as Windows service (called by SCM, not for manual use)
    #[command(hide = true)]
    Service,
}

/// Shared application state
pub struct AppState {
    pub config: AgentConfig,
    pub metrics_collector: MetricsCollector,
    pub connected: RwLock<bool>,
}

#[tokio::main]
async fn main() -> Result<()> {
    // Initialize logging
    tracing_subscriber::fmt()
        .with_env_filter(
            tracing_subscriber::EnvFilter::from_default_env()
                .add_directive("gururmm_agent=info".parse()?)
                .add_directive("info".parse()?),
        )
        .init();

    let cli = Cli::parse();

    match cli.command.unwrap_or(Commands::Run) {
        Commands::Run => run_agent(cli.config).await,
        Commands::Install { server_url, api_key, skip_legacy_check } => {
            install_service(server_url, api_key, skip_legacy_check).await
        }
        Commands::Uninstall => uninstall_service().await,
        Commands::Start => start_service().await,
        Commands::Stop => stop_service().await,
        Commands::Status => show_status(cli.config).await,
        Commands::GenerateConfig { output } => generate_config(output).await,
        Commands::Service => run_as_windows_service(),
    }
}

/// Run as a Windows service (called by SCM)
fn run_as_windows_service() -> Result<()> {
    #[cfg(windows)]
    {
        service::windows::run_as_service()
    }

    #[cfg(not(windows))]
    {
        anyhow::bail!("Windows service mode is only available on Windows");
    }
}

/// Main agent runtime loop
async fn run_agent(config_path: PathBuf) -> Result<()> {
    info!("GuruRMM Agent starting...");

    // Load configuration
    let config = AgentConfig::load(&config_path)?;
    info!("Loaded configuration from {:?}", config_path);
    info!("Server URL: {}", config.server.url);

    // Initialize metrics collector
    let metrics_collector = MetricsCollector::new();
    info!("Metrics collector initialized");

    // Create shared state
    let state = Arc::new(AppState {
        config: config.clone(),
        metrics_collector,
        connected: RwLock::new(false),
    });

    // Start the WebSocket client with auto-reconnect
    let ws_state = Arc::clone(&state);
    let ws_handle = tokio::spawn(async move {
        loop {
            info!("Connecting to server...");
            match WebSocketClient::connect_and_run(Arc::clone(&ws_state)).await {
                Ok(_) => {
                    warn!("WebSocket connection closed normally, reconnecting...");
                }
                Err(e) => {
                    error!("WebSocket error: {}, reconnecting in 10 seconds...", e);
                }
            }

            // Mark as disconnected
            *ws_state.connected.write().await = false;

            // Wait before reconnecting
            tokio::time::sleep(tokio::time::Duration::from_secs(10)).await;
        }
    });

    // Start metrics collection loop
    let metrics_state = Arc::clone(&state);
    let metrics_handle = tokio::spawn(async move {
        let interval = metrics_state.config.metrics.interval_seconds;
        let mut interval_timer = tokio::time::interval(tokio::time::Duration::from_secs(interval));

        loop {
            interval_timer.tick().await;

            // Collect metrics (they'll be sent via WebSocket if connected)
            let metrics = metrics_state.metrics_collector.collect().await;
            if *metrics_state.connected.read().await {
                info!(
                    "Metrics: CPU={:.1}%, Mem={:.1}%, Disk={:.1}%",
                    metrics.cpu_percent, metrics.memory_percent, metrics.disk_percent
                );
            }
        }
    });

    // Wait for shutdown signal
    tokio::select! {
        _ = tokio::signal::ctrl_c() => {
            info!("Received shutdown signal");
        }
        _ = ws_handle => {
            error!("WebSocket task ended unexpectedly");
        }
        _ = metrics_handle => {
            error!("Metrics task ended unexpectedly");
        }
    }

    info!("GuruRMM Agent shutting down");
    Ok(())
}

/// Install the agent as a system service
async fn install_service(
    server_url: Option<String>,
    api_key: Option<String>,
    skip_legacy_check: bool,
) -> Result<()> {
    #[cfg(windows)]
    {
        service::windows::install(server_url, api_key, skip_legacy_check)
    }

    #[cfg(target_os = "linux")]
    {
        install_systemd_service(server_url, api_key, skip_legacy_check).await
    }

    #[cfg(target_os = "macos")]
    {
        let _ = (server_url, api_key, skip_legacy_check); // Suppress unused warnings
        info!("Installing GuruRMM Agent as launchd service...");
        todo!("macOS launchd service installation not yet implemented");
    }
}

/// Legacy service names to check for and clean up (Linux)
#[cfg(target_os = "linux")]
const LINUX_LEGACY_SERVICE_NAMES: &[&str] = &[
    "gururmm",           // Old name without -agent suffix
    "guru-rmm-agent",    // Alternative naming
    "GuruRMM-Agent",     // Case variant
];

/// Clean up legacy Linux service installations
#[cfg(target_os = "linux")]
fn cleanup_legacy_linux_services() -> Result<()> {
    use std::process::Command;

    info!("Checking for legacy service installations...");

    for legacy_name in LINUX_LEGACY_SERVICE_NAMES {
        // Check if service exists
        let status = Command::new("systemctl")
            .args(["status", legacy_name])
            .output();

        if let Ok(output) = status {
            if output.status.success() || String::from_utf8_lossy(&output.stderr).contains("Loaded:") {
                info!("Found legacy service '{}', removing...", legacy_name);

                // Stop the service
                let _ = Command::new("systemctl")
                    .args(["stop", legacy_name])
                    .status();

                // Disable the service
                let _ = Command::new("systemctl")
                    .args(["disable", legacy_name])
                    .status();

                // Remove unit file
                let unit_file = format!("/etc/systemd/system/{}.service", legacy_name);
                if std::path::Path::new(&unit_file).exists() {
                    info!("Removing legacy unit file: {}", unit_file);
                    let _ = std::fs::remove_file(&unit_file);
                }
            }
        }
    }

    // Check for legacy binaries in common locations
    let legacy_binary_locations = [
        "/usr/local/bin/gururmm",
        "/usr/bin/gururmm",
        "/opt/gururmm/gururmm",
        "/opt/gururmm/agent",
    ];

    for legacy_path in legacy_binary_locations {
        if std::path::Path::new(legacy_path).exists() {
            info!("Found legacy binary at '{}', removing...", legacy_path);
            let _ = std::fs::remove_file(legacy_path);
        }
    }

    // Reload systemd to pick up removed unit files
    let _ = Command::new("systemctl")
        .args(["daemon-reload"])
        .status();

    Ok(())
}

/// Install as a systemd service (Linux)
#[cfg(target_os = "linux")]
async fn install_systemd_service(
    server_url: Option<String>,
    api_key: Option<String>,
    skip_legacy_check: bool,
) -> Result<()> {
    use std::process::Command;

    const SERVICE_NAME: &str = "gururmm-agent";
    const INSTALL_DIR: &str = "/usr/local/bin";
    const CONFIG_DIR: &str = "/etc/gururmm";
    const SYSTEMD_DIR: &str = "/etc/systemd/system";

    info!("Installing GuruRMM Agent as systemd service...");

    // Check if running as root
    if !nix::unistd::geteuid().is_root() {
        anyhow::bail!("Installation requires root privileges. Please run with sudo.");
    }

    // Clean up legacy installations unless skipped
    if !skip_legacy_check {
        if let Err(e) = cleanup_legacy_linux_services() {
            warn!("Legacy cleanup warning: {}", e);
        }
    }

    // Get the current executable path
    let current_exe = std::env::current_exe()
        .context("Failed to get current executable path")?;

    let binary_dest = format!("{}/{}", INSTALL_DIR, SERVICE_NAME);
    let config_dest = format!("{}/agent.toml", CONFIG_DIR);
    let unit_file = format!("{}/{}.service", SYSTEMD_DIR, SERVICE_NAME);

    // Create config directory
    info!("Creating config directory: {}", CONFIG_DIR);
    std::fs::create_dir_all(CONFIG_DIR)
        .context("Failed to create config directory")?;

    // Copy binary
    info!("Copying binary to: {}", binary_dest);
    std::fs::copy(&current_exe, &binary_dest)
        .context("Failed to copy binary")?;

    // Make binary executable
    Command::new("chmod")
        .args(["+x", &binary_dest])
        .status()
        .context("Failed to set binary permissions")?;

    // Handle configuration
    let config_needs_manual_edit;
    if !std::path::Path::new(&config_dest).exists() {
        info!("Creating config: {}", config_dest);

        // Start with sample config
        let mut config = crate::config::AgentConfig::sample();

        // Apply provided values
        if let Some(url) = &server_url {
            config.server.url = url.clone();
        }
        if let Some(key) = &api_key {
            config.server.api_key = key.clone();
        }

        let toml_str = toml::to_string_pretty(&config)?;
        std::fs::write(&config_dest, toml_str)
            .context("Failed to write config file")?;

        // Set restrictive permissions on config (contains API key)
        Command::new("chmod")
            .args(["600", &config_dest])
            .status()
            .context("Failed to set config permissions")?;

        config_needs_manual_edit = server_url.is_none() || api_key.is_none();
    } else {
        info!("Config already exists: {}", config_dest);
        config_needs_manual_edit = false;

        // If server_url or api_key provided, update existing config
        if server_url.is_some() || api_key.is_some() {
            info!("Updating existing configuration...");
            let config_content = std::fs::read_to_string(&config_dest)?;
            let mut config: crate::config::AgentConfig = toml::from_str(&config_content)
                .context("Failed to parse existing config")?;

            if let Some(url) = &server_url {
                config.server.url = url.clone();
            }
            if let Some(key) = &api_key {
                config.server.api_key = key.clone();
            }

            let toml_str = toml::to_string_pretty(&config)?;
            std::fs::write(&config_dest, toml_str)
                .context("Failed to update config file")?;
        }
    }

    // Create systemd unit file
    let unit_content = format!(r#"[Unit]
Description=GuruRMM Agent - Remote Monitoring and Management
Documentation=https://github.com/azcomputerguru/gururmm
After=network-online.target
Wants=network-online.target

[Service]
Type=simple
ExecStart={binary} --config {config} run
Restart=always
RestartSec=10
StandardOutput=journal
StandardError=journal
SyslogIdentifier={service}

# Security hardening
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=read-only
PrivateTmp=true
ReadWritePaths=/var/log

[Install]
WantedBy=multi-user.target
"#,
        binary = binary_dest,
        config = config_dest,
        service = SERVICE_NAME
    );

    info!("Creating systemd unit file: {}", unit_file);
    std::fs::write(&unit_file, unit_content)
        .context("Failed to write systemd unit file")?;

    // Reload systemd daemon
    info!("Reloading systemd daemon...");
    let status = Command::new("systemctl")
        .args(["daemon-reload"])
        .status()
        .context("Failed to reload systemd")?;

    if !status.success() {
        anyhow::bail!("systemctl daemon-reload failed");
    }

    // Enable the service
    info!("Enabling service...");
    let status = Command::new("systemctl")
        .args(["enable", SERVICE_NAME])
        .status()
        .context("Failed to enable service")?;

    if !status.success() {
        anyhow::bail!("systemctl enable failed");
    }

    println!("\n✓ GuruRMM Agent installed successfully!");
    println!("\nInstalled files:");
    println!("  Binary: {}", binary_dest);
    println!("  Config: {}", config_dest);
    println!("  Service: {}", unit_file);

    if config_needs_manual_edit {
        println!("\n⚠  IMPORTANT: Edit {} with your server URL and API key!", config_dest);
        println!("\nNext steps:");
        println!("  1. Edit {} with your server URL and API key", config_dest);
        println!("  2. Start the service: sudo systemctl start {}", SERVICE_NAME);
    } else {
        println!("\nStarting service...");
        let status = Command::new("systemctl")
            .args(["start", SERVICE_NAME])
            .status();

        if status.is_ok() && status.unwrap().success() {
            println!("✓ Service started successfully!");
        } else {
            println!("⚠  Failed to start service. Check logs: sudo journalctl -u {} -f", SERVICE_NAME);
        }
    }

    println!("\nUseful commands:");
    println!("  Status: sudo systemctl status {}", SERVICE_NAME);
    println!("  Logs:   sudo journalctl -u {} -f", SERVICE_NAME);
    println!("  Stop:   sudo systemctl stop {}", SERVICE_NAME);
    println!("  Start:  sudo systemctl start {}", SERVICE_NAME);

    Ok(())
}

/// Uninstall the system service
async fn uninstall_service() -> Result<()> {
    #[cfg(windows)]
    {
        service::windows::uninstall()
    }

    #[cfg(target_os = "linux")]
    {
        uninstall_systemd_service().await
    }

    #[cfg(target_os = "macos")]
    {
        todo!("macOS service uninstallation not yet implemented");
    }
}

/// Uninstall systemd service (Linux)
#[cfg(target_os = "linux")]
async fn uninstall_systemd_service() -> Result<()> {
    use std::process::Command;

    const SERVICE_NAME: &str = "gururmm-agent";
    const INSTALL_DIR: &str = "/usr/local/bin";
    const CONFIG_DIR: &str = "/etc/gururmm";
    const SYSTEMD_DIR: &str = "/etc/systemd/system";

    info!("Uninstalling GuruRMM Agent...");

    if !nix::unistd::geteuid().is_root() {
        anyhow::bail!("Uninstallation requires root privileges. Please run with sudo.");
    }

    let binary_path = format!("{}/{}", INSTALL_DIR, SERVICE_NAME);
    let unit_file = format!("{}/{}.service", SYSTEMD_DIR, SERVICE_NAME);

    // Stop the service if running
    info!("Stopping service...");
    let _ = Command::new("systemctl")
        .args(["stop", SERVICE_NAME])
        .status();

    // Disable the service
    info!("Disabling service...");
    let _ = Command::new("systemctl")
        .args(["disable", SERVICE_NAME])
        .status();

    // Remove unit file
    if std::path::Path::new(&unit_file).exists() {
        info!("Removing unit file: {}", unit_file);
        std::fs::remove_file(&unit_file)?;
    }

    // Remove binary
    if std::path::Path::new(&binary_path).exists() {
        info!("Removing binary: {}", binary_path);
        std::fs::remove_file(&binary_path)?;
    }

    // Reload systemd
    let _ = Command::new("systemctl")
        .args(["daemon-reload"])
        .status();

    println!("\n✓ GuruRMM Agent uninstalled successfully!");
    println!("\nNote: Config directory {} was preserved.", CONFIG_DIR);
    println!("Remove it manually if no longer needed: sudo rm -rf {}", CONFIG_DIR);

    Ok(())
}

/// Start the installed service
async fn start_service() -> Result<()> {
    #[cfg(windows)]
    {
        service::windows::start()
    }

    #[cfg(target_os = "linux")]
    {
        use std::process::Command;

        info!("Starting GuruRMM Agent service...");

        let status = Command::new("systemctl")
            .args(["start", "gururmm-agent"])
            .status()
            .context("Failed to start service")?;

        if status.success() {
            println!("** Service started successfully");
            println!("Check status: sudo systemctl status gururmm-agent");
        } else {
            anyhow::bail!("Failed to start service. Check: sudo journalctl -u gururmm-agent -n 50");
        }

        Ok(())
    }

    #[cfg(target_os = "macos")]
    {
        todo!("macOS service start not yet implemented");
    }
}

/// Stop the installed service
async fn stop_service() -> Result<()> {
    #[cfg(windows)]
    {
        service::windows::stop()
    }

    #[cfg(target_os = "linux")]
    {
        use std::process::Command;

        info!("Stopping GuruRMM Agent service...");

        let status = Command::new("systemctl")
            .args(["stop", "gururmm-agent"])
            .status()
            .context("Failed to stop service")?;

        if status.success() {
            println!("** Service stopped successfully");
        } else {
            anyhow::bail!("Failed to stop service");
        }

        Ok(())
    }

    #[cfg(target_os = "macos")]
    {
        todo!("macOS service stop not yet implemented");
    }
}

/// Show agent status
async fn show_status(config_path: PathBuf) -> Result<()> {
    // On Windows, show service status
    #[cfg(windows)]
    {
        service::windows::status()?;
        println!();
    }

    // Try to load config for additional info
    match AgentConfig::load(&config_path) {
        Ok(config) => {
            println!("Configuration");
            println!("=============");
            println!("Config file: {:?}", config_path);
            println!("Server URL: {}", config.server.url);
            println!("Metrics interval: {} seconds", config.metrics.interval_seconds);
            println!("Watchdog enabled: {}", config.watchdog.enabled);

            // Collect current metrics
            let collector = MetricsCollector::new();
            let metrics = collector.collect().await;

            println!("\nCurrent System Metrics:");
            println!("  CPU Usage: {:.1}%", metrics.cpu_percent);
            println!("  Memory Usage: {:.1}%", metrics.memory_percent);
            println!(
                "  Memory Used: {:.2} GB",
                metrics.memory_used_bytes as f64 / 1_073_741_824.0
            );
            println!("  Disk Usage: {:.1}%", metrics.disk_percent);
            println!(
                "  Disk Used: {:.2} GB",
                metrics.disk_used_bytes as f64 / 1_073_741_824.0
            );
        }
        Err(_) => {
            println!("\nConfig file {:?} not found or invalid.", config_path);
            #[cfg(windows)]
            println!("Service config location: {}\\agent.toml", service::windows::CONFIG_DIR);
        }
    }

    Ok(())
}

/// Generate a sample configuration file
async fn generate_config(output: PathBuf) -> Result<()> {
    let sample_config = AgentConfig::sample();
    let toml_str = toml::to_string_pretty(&sample_config)?;

    std::fs::write(&output, toml_str)?;
    println!("Sample configuration written to {:?}", output);
    println!("\nEdit this file with your server URL and API key, then run:");
    println!("  gururmm-agent --config {:?} run", output);

    Ok(())
}