Files
guru-connect/agent/src/config.rs
Mike Swanson 1d2ca47771 Link support codes to agent sessions
- Server: Accept support_code param in WebSocket connection
- Server: Link code to session when agent connects, mark as connected
- Server: Mark code as completed when agent disconnects
- Agent: Accept support code from command line argument
- Agent: Send hostname and support_code in WebSocket params
- Portal: Trigger agent download with code in filename
- Portal: Show code reminder in download instructions
- Dashboard: Add machines list fetching (Access tab)
- Add TODO.md for feature tracking

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

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2025-12-28 14:11:52 -07:00

233 lines
6.0 KiB
Rust

//! Agent configuration management
use anyhow::{Context, Result};
use serde::{Deserialize, Serialize};
use std::path::PathBuf;
use uuid::Uuid;
/// Agent configuration
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Config {
/// Server WebSocket URL (e.g., wss://connect.example.com/ws)
pub server_url: String,
/// Agent API key for authentication
pub api_key: String,
/// Unique agent identifier (generated on first run)
#[serde(default = "generate_agent_id")]
pub agent_id: String,
/// Optional hostname override
pub hostname_override: Option<String>,
/// Support code for one-time support sessions (set via command line)
#[serde(skip)]
pub support_code: Option<String>,
/// Capture settings
#[serde(default)]
pub capture: CaptureConfig,
/// Encoding settings
#[serde(default)]
pub encoding: EncodingConfig,
}
fn generate_agent_id() -> String {
Uuid::new_v4().to_string()
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CaptureConfig {
/// Target frames per second (1-60)
#[serde(default = "default_fps")]
pub fps: u32,
/// Use DXGI Desktop Duplication (recommended)
#[serde(default = "default_true")]
pub use_dxgi: bool,
/// Fall back to GDI if DXGI fails
#[serde(default = "default_true")]
pub gdi_fallback: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct EncodingConfig {
/// Preferred codec (auto, raw, vp9, h264)
#[serde(default = "default_codec")]
pub codec: String,
/// Quality (1-100, higher = better quality, more bandwidth)
#[serde(default = "default_quality")]
pub quality: u32,
/// Use hardware encoding if available
#[serde(default = "default_true")]
pub hardware_encoding: bool,
}
fn default_fps() -> u32 {
30
}
fn default_true() -> bool {
true
}
fn default_codec() -> String {
"auto".to_string()
}
fn default_quality() -> u32 {
75
}
impl Default for CaptureConfig {
fn default() -> Self {
Self {
fps: default_fps(),
use_dxgi: true,
gdi_fallback: true,
}
}
}
impl Default for EncodingConfig {
fn default() -> Self {
Self {
codec: default_codec(),
quality: default_quality(),
hardware_encoding: true,
}
}
}
impl Config {
/// Load configuration from file or environment
pub fn load() -> Result<Self> {
// Try loading from config file
let config_path = Self::config_path();
if config_path.exists() {
let contents = std::fs::read_to_string(&config_path)
.with_context(|| format!("Failed to read config from {:?}", config_path))?;
let mut config: Config = toml::from_str(&contents)
.with_context(|| "Failed to parse config file")?;
// Ensure agent_id is set and saved
if config.agent_id.is_empty() {
config.agent_id = generate_agent_id();
let _ = config.save();
}
// support_code is always None when loading from file (set via CLI)
config.support_code = None;
return Ok(config);
}
// Fall back to environment variables
let server_url = std::env::var("GURUCONNECT_SERVER_URL")
.unwrap_or_else(|_| "wss://connect.azcomputerguru.com/ws/agent".to_string());
let api_key = std::env::var("GURUCONNECT_API_KEY")
.unwrap_or_else(|_| "dev-key".to_string());
let agent_id = std::env::var("GURUCONNECT_AGENT_ID")
.unwrap_or_else(|_| generate_agent_id());
let config = Config {
server_url,
api_key,
agent_id,
hostname_override: std::env::var("GURUCONNECT_HOSTNAME").ok(),
support_code: None, // Set via CLI
capture: CaptureConfig::default(),
encoding: EncodingConfig::default(),
};
// Save config with generated agent_id for persistence
let _ = config.save();
Ok(config)
}
/// Get the configuration file path
fn config_path() -> PathBuf {
// Check for config in current directory first
let local_config = PathBuf::from("guruconnect.toml");
if local_config.exists() {
return local_config;
}
// Check in program data directory (Windows)
#[cfg(windows)]
{
if let Ok(program_data) = std::env::var("ProgramData") {
let path = PathBuf::from(program_data)
.join("GuruConnect")
.join("agent.toml");
if path.exists() {
return path;
}
}
}
// Default to local config
local_config
}
/// Get the hostname to use
pub fn hostname(&self) -> String {
self.hostname_override
.clone()
.unwrap_or_else(|| {
hostname::get()
.map(|h| h.to_string_lossy().to_string())
.unwrap_or_else(|_| "unknown".to_string())
})
}
/// Save current configuration to file
pub fn save(&self) -> Result<()> {
let config_path = Self::config_path();
// Ensure parent directory exists
if let Some(parent) = config_path.parent() {
std::fs::create_dir_all(parent)?;
}
let contents = toml::to_string_pretty(self)?;
std::fs::write(&config_path, contents)?;
Ok(())
}
}
/// Example configuration file content
pub fn example_config() -> &'static str {
r#"# GuruConnect Agent Configuration
# Server connection
server_url = "wss://connect.example.com/ws"
api_key = "your-agent-api-key"
agent_id = "auto-generated-uuid"
# Optional: override hostname
# hostname_override = "custom-hostname"
[capture]
fps = 30
use_dxgi = true
gdi_fallback = true
[encoding]
codec = "auto" # auto, raw, vp9, h264
quality = 75 # 1-100
hardware_encoding = true
"#
}