//! Transport layer for agent-server communication //! //! Handles WebSocket connection to the GuruRMM server with: //! - Auto-reconnection on disconnect //! - Authentication via API key //! - Sending metrics and receiving commands //! - Heartbeat to maintain connection mod websocket; pub use websocket::WebSocketClient; use serde::{Deserialize, Serialize}; use uuid::Uuid; /// Messages sent from agent to server #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", content = "payload")] #[serde(rename_all = "snake_case")] pub enum AgentMessage { /// Authentication message (sent on connect) Auth(AuthPayload), /// Metrics report Metrics(crate::metrics::SystemMetrics), /// Network state update (sent on connect and when interfaces change) NetworkState(crate::metrics::NetworkState), /// Command execution result CommandResult(CommandResultPayload), /// Watchdog event (service stopped, restarted, etc.) WatchdogEvent(WatchdogEventPayload), /// Update result (success, failure, rollback) UpdateResult(UpdateResultPayload), /// Heartbeat to keep connection alive Heartbeat, } /// Authentication payload #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthPayload { /// API key for this agent (or site) pub api_key: String, /// Unique device identifier (hardware-derived) pub device_id: String, /// Hostname of this machine pub hostname: String, /// Operating system type pub os_type: String, /// Operating system version pub os_version: String, /// Agent version pub agent_version: String, /// Architecture (amd64, arm64, etc.) #[serde(default = "default_arch")] pub architecture: String, /// Previous version if reconnecting after update #[serde(skip_serializing_if = "Option::is_none")] pub previous_version: Option, /// Update ID if reconnecting after update #[serde(skip_serializing_if = "Option::is_none")] pub pending_update_id: Option, } fn default_arch() -> String { #[cfg(target_arch = "x86_64")] { "amd64".to_string() } #[cfg(target_arch = "aarch64")] { "arm64".to_string() } #[cfg(not(any(target_arch = "x86_64", target_arch = "aarch64")))] { "unknown".to_string() } } /// Command execution result payload #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommandResultPayload { /// Command ID (from the server) pub command_id: Uuid, /// Exit code (0 = success) pub exit_code: i32, /// Standard output pub stdout: String, /// Standard error pub stderr: String, /// Execution duration in milliseconds pub duration_ms: u64, } /// Watchdog event payload #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WatchdogEventPayload { /// Service or process name pub name: String, /// Event type pub event: WatchdogEvent, /// Additional details pub details: Option, } /// Types of watchdog events #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum WatchdogEvent { /// Service/process was found stopped Stopped, /// Service/process was restarted by the agent Restarted, /// Restart attempt failed RestartFailed, /// Max restart attempts reached MaxRestartsReached, /// Service/process recovered on its own Recovered, } /// Messages sent from server to agent #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(tag = "type", content = "payload")] #[serde(rename_all = "snake_case")] pub enum ServerMessage { /// Authentication acknowledgment AuthAck(AuthAckPayload), /// Command to execute Command(CommandPayload), /// Configuration update ConfigUpdate(ConfigUpdatePayload), /// Agent update command Update(UpdatePayload), /// Acknowledgment of received message Ack { message_id: Option }, /// Error message Error { code: String, message: String }, } /// Authentication acknowledgment payload #[derive(Debug, Clone, Serialize, Deserialize)] pub struct AuthAckPayload { /// Whether authentication was successful pub success: bool, /// Agent ID assigned by server pub agent_id: Option, /// Error message if authentication failed pub error: Option, } /// Command payload from server #[derive(Debug, Clone, Serialize, Deserialize)] pub struct CommandPayload { /// Unique command ID pub id: Uuid, /// Type of command pub command_type: CommandType, /// Command text to execute pub command: String, /// Optional timeout in seconds pub timeout_seconds: Option, /// Whether to run as elevated/admin pub elevated: bool, } /// Types of commands #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum CommandType { /// Shell command (cmd on Windows, bash on Unix) Shell, /// PowerShell command (Windows) PowerShell, /// Python script Python, /// Raw script (requires interpreter path) Script { interpreter: String }, /// Claude Code task execution ClaudeTask { /// Task description for Claude Code task: String, /// Optional working directory (defaults to C:\Shares\test) working_directory: Option, /// Optional context files to provide to Claude context_files: Option>, }, } /// Configuration update payload #[derive(Debug, Clone, Serialize, Deserialize)] pub struct ConfigUpdatePayload { /// New metrics interval (if changed) pub metrics_interval_seconds: Option, /// Updated watchdog config pub watchdog: Option, } /// Watchdog configuration update #[derive(Debug, Clone, Serialize, Deserialize)] pub struct WatchdogConfigUpdate { /// Enable/disable watchdog pub enabled: Option, /// Check interval pub check_interval_seconds: Option, // Services and processes would be included here for remote config updates } /// Update command payload from server #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdatePayload { /// Unique update ID for tracking pub update_id: Uuid, /// Target version to update to pub target_version: String, /// Download URL for the new binary pub download_url: String, /// SHA256 checksum of the binary pub checksum_sha256: String, /// Whether to force update (skip version check) #[serde(default)] pub force: bool, } /// Update result payload sent back to server #[derive(Debug, Clone, Serialize, Deserialize)] pub struct UpdateResultPayload { /// Update ID (from the server) pub update_id: Uuid, /// Update status pub status: UpdateStatus, /// Old version before update pub old_version: String, /// New version after update (if successful) pub new_version: Option, /// Error message if failed pub error: Option, } /// Update status codes #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "snake_case")] pub enum UpdateStatus { /// Update starting Starting, /// Downloading new binary Downloading, /// Download complete, verifying Verifying, /// Installing (replacing binary) Installing, /// Restarting service Restarting, /// Update completed successfully Completed, /// Update failed Failed, /// Rolled back to previous version RolledBack, }