Added: - PROJECTS_INDEX.md - Master catalog of 7 active projects - GURURMM_API_ACCESS.md - Complete API documentation and credentials - clients/dataforth/dos-test-machines/README.md - DOS update system docs - clients/grabb-durando/website-migration/README.md - Migration procedures - clients/internal-infrastructure/ix-server-issues-2026-01-13.md - Server issues - projects/msp-tools/guru-connect/README.md - Remote desktop architecture - projects/msp-tools/toolkit/README.md - MSP PowerShell tools - projects/internal/acg-website-2025/README.md - Website rebuild docs - test_gururmm_api.py - GuruRMM API testing script Modified: - credentials.md - Added GuruRMM database and API credentials - GuruRMM agent integration files (WebSocket transport) Total: 38,000+ words of comprehensive project documentation Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
704 lines
21 KiB
Rust
704 lines
21 KiB
Rust
//! 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 claude;
|
|
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
|
|
return Err(anyhow::anyhow!(
|
|
"macOS launchd service installation is not yet implemented.\n\
|
|
For now, you can run the agent manually or create a launchd plist.\n\
|
|
See: https://developer.apple.com/library/archive/documentation/MacOSX/Conceptual/BPSystemStartup/Chapters/CreatingLaunchdJobs.html"
|
|
));
|
|
}
|
|
}
|
|
|
|
/// 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(¤t_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[OK] 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[WARNING] 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!("[OK] Service started successfully!");
|
|
} else {
|
|
println!("[WARNING] 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")]
|
|
{
|
|
return Err(anyhow::anyhow!(
|
|
"macOS launchd service uninstallation is not yet implemented.\n\
|
|
If you created a launchd plist manually, remove it from ~/Library/LaunchAgents/ or /Library/LaunchDaemons/"
|
|
));
|
|
}
|
|
}
|
|
|
|
/// 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[OK] 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!("[OK] 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")]
|
|
{
|
|
return Err(anyhow::anyhow!(
|
|
"macOS launchd service start is not yet implemented.\n\
|
|
If you created a launchd plist manually, use: launchctl load <plist-path>"
|
|
));
|
|
}
|
|
}
|
|
|
|
/// 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!("[OK] Service stopped successfully");
|
|
} else {
|
|
anyhow::bail!("Failed to stop service");
|
|
}
|
|
|
|
Ok(())
|
|
}
|
|
|
|
#[cfg(target_os = "macos")]
|
|
{
|
|
return Err(anyhow::anyhow!(
|
|
"macOS launchd service stop is not yet implemented.\n\
|
|
If you created a launchd plist manually, use: launchctl unload <plist-path>"
|
|
));
|
|
}
|
|
}
|
|
|
|
/// 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(())
|
|
}
|