Add VPN configuration tools and agent documentation
Created comprehensive VPN setup tooling for Peaceful Spirit L2TP/IPsec connection and enhanced agent documentation framework. VPN Configuration (PST-NW-VPN): - Setup-PST-L2TP-VPN.ps1: Automated L2TP/IPsec setup with split-tunnel and DNS - Connect-PST-VPN.ps1: Connection helper with PPP adapter detection, DNS (192.168.0.2), and route config (192.168.0.0/24) - Connect-PST-VPN-Standalone.ps1: Self-contained connection script for remote deployment - Fix-PST-VPN-Auth.ps1: Authentication troubleshooting for CHAP/MSChapv2 - Diagnose-VPN-Interface.ps1: Comprehensive VPN interface and routing diagnostic - Quick-Test-VPN.ps1: Fast connectivity verification (DNS/router/routes) - Add-PST-VPN-Route-Manual.ps1: Manual route configuration helper - vpn-connect.bat, vpn-disconnect.bat: Simple batch file shortcuts - OpenVPN config files (Windows-compatible, abandoned for L2TP) Key VPN Implementation Details: - L2TP creates PPP adapter with connection name as interface description - UniFi auto-configures DNS (192.168.0.2) but requires manual route to 192.168.0.0/24 - Split-tunnel enabled (only remote traffic through VPN) - All-user connection for pre-login auto-connect via scheduled task - Authentication: CHAP + MSChapv2 for UniFi compatibility Agent Documentation: - AGENT_QUICK_REFERENCE.md: Quick reference for all specialized agents - documentation-squire.md: Documentation and task management specialist agent - Updated all agent markdown files with standardized formatting Project Organization: - Moved conversation logs to dedicated directories (guru-connect-conversation-logs, guru-rmm-conversation-logs) - Cleaned up old session JSONL files from projects/msp-tools/ - Added guru-connect infrastructure (agent, dashboard, proto, scripts, .gitea workflows) - Added guru-rmm server components and deployment configs Technical Notes: - VPN IP pool: 192.168.4.x (client gets 192.168.4.6) - Remote network: 192.168.0.0/24 (router at 192.168.0.10) - PSK: rrClvnmUeXEFo90Ol+z7tfsAZHeSK6w7 - Credentials: pst-admin / 24Hearts$ Files: 15 VPN scripts, 2 agent docs, conversation log reorganization, guru-connect/guru-rmm infrastructure additions Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
690
projects/msp-tools/guru-rmm/agent/src/main.rs
Normal file
690
projects/msp-tools/guru-rmm/agent/src/main.rs
Normal file
@@ -0,0 +1,690 @@
|
||||
//! 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(¤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✓ 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(())
|
||||
}
|
||||
Reference in New Issue
Block a user